Back to Freecodecamp

Step 89

curriculum/challenges/english/blocks/learn-functional-programming-by-building-a-spreadsheet/646d4626420eeecd51f241c2.md

latest10.3 KB
Original Source

--description--

Update the callback function to take match, fn, and args as parameters. It should implicitly return the result of checking whether spreadsheetFunctions has its own property of fn.

Remember to make fn lower case.

To check if a property on a given object exists or not, you can use the <dfn>hasOwnProperty()</dfn> method.

The hasOwnProperty() method returns true or false depending on if the property is found on the object or not.

Here is an example of how to use the hasOwnProperty() method:

js
const developerObj = {
  name: 'John',
  age: 34,
}

developerObj.hasOwnProperty('name'); // true
developerObj.hasOwnProperty('salary'); // false

--hints--

Your callback function should have match as the first parameter.

js
assert.match(code, /const\s+applyFunction\s*=\s*(?:\(\s*str\s*\)|str)\s*=>\s*\{\s*const\s+noHigh\s*=\s*highPrecedence\(\s*str\s*\)\s*;?\s*const\s+infix\s*=\s*\/\(\[(?:\\d\.|\.\\d)\]\+\)\(\[(?:\+-|-\+)\]\)\(\[(?:\\d\.|\.\\d)\]\+\)\/\s*;?\s*const\s+str2\s*=\s*infixEval\(\s*noHigh\s*,\s*infix\s*\)\s*;?\s*const\s+functionCall\s*=\s*\/\(\[a-z0-9\]\*\)\\\(\(\[0-9\., \]\*\)\\\)\(\?!\.\*\\\(\)\/i\s*;?\s*const\s+toNumberList\s*=\s*(?:\(\s*args\s*\)|args)\s*=>\s*args\.split\(\s*('|"|`),\1\s*\)\.map\(\s*parseFloat\s*\)\s*;?\s*const\s+apply\s*=\s*\(\s*fn\s*,\s*args\s*\)\s*=>\s*spreadsheetFunctions\s*\[\s*fn\.toLowerCase\(\s*\)\s*\]\s*\(\s*toNumberList\(\s*args\s*\)\s*\)\s*;?\s*return\s+str2\.replace\(\s*functionCall\s*,\s*\(\s*match/);

Your callback function should have fn as the second parameter.

js
assert.match(code, /const\s+applyFunction\s*=\s*(?:\(\s*str\s*\)|str)\s*=>\s*\{\s*const\s+noHigh\s*=\s*highPrecedence\(\s*str\s*\)\s*;?\s*const\s+infix\s*=\s*\/\(\[(?:\\d\.|\.\\d)\]\+\)\(\[(?:\+-|-\+)\]\)\(\[(?:\\d\.|\.\\d)\]\+\)\/\s*;?\s*const\s+str2\s*=\s*infixEval\(\s*noHigh\s*,\s*infix\s*\)\s*;?\s*const\s+functionCall\s*=\s*\/\(\[a-z0-9\]\*\)\\\(\(\[0-9\., \]\*\)\\\)\(\?!\.\*\\\(\)\/i\s*;?\s*const\s+toNumberList\s*=\s*(?:\(\s*args\s*\)|args)\s*=>\s*args\.split\(\s*('|"|`),\1\s*\)\.map\(\s*parseFloat\s*\)\s*;?\s*const\s+apply\s*=\s*\(\s*fn\s*,\s*args\s*\)\s*=>\s*spreadsheetFunctions\s*\[\s*fn\.toLowerCase\(\s*\)\s*\]\s*\(\s*toNumberList\(\s*args\s*\)\s*\)\s*;?\s*return\s+str2\.replace\(\s*functionCall\s*,\s*\(\s*match\s*,\s*fn/);

Your callback function should have args as the third parameter.

js
assert.match(code, /const\s+applyFunction\s*=\s*(?:\(\s*str\s*\)|str)\s*=>\s*\{\s*const\s+noHigh\s*=\s*highPrecedence\(\s*str\s*\)\s*;?\s*const\s+infix\s*=\s*\/\(\[(?:\\d\.|\.\\d)\]\+\)\(\[(?:\+-|-\+)\]\)\(\[(?:\\d\.|\.\\d)\]\+\)\/\s*;?\s*const\s+str2\s*=\s*infixEval\(\s*noHigh\s*,\s*infix\s*\)\s*;?\s*const\s+functionCall\s*=\s*\/\(\[a-z0-9\]\*\)\\\(\(\[0-9\., \]\*\)\\\)\(\?!\.\*\\\(\)\/i\s*;?\s*const\s+toNumberList\s*=\s*(?:\(\s*args\s*\)|args)\s*=>\s*args\.split\(\s*('|"|`),\1\s*\)\.map\(\s*parseFloat\s*\)\s*;?\s*const\s+apply\s*=\s*\(\s*fn\s*,\s*args\s*\)\s*=>\s*spreadsheetFunctions\s*\[\s*fn\.toLowerCase\(\s*\)\s*\]\s*\(\s*toNumberList\(\s*args\s*\)\s*\)\s*;?\s*return\s+str2\.replace\(\s*functionCall\s*,\s*\(\s*match\s*,\s*fn\s*,\s*args\s*\)\s*=>/);

Your callback function should use an implicit return.

js
assert.notMatch(code, /const\s+applyFunction\s*=\s*(?:\(\s*str\s*\)|str)\s*=>\s*\{\s*const\s+noHigh\s*=\s*highPrecedence\(\s*str\s*\)\s*;?\s*const\s+infix\s*=\s*\/\(\[(?:\\d\.|\.\\d)\]\+\)\(\[(?:\+-|-\+)\]\)\(\[(?:\\d\.|\.\\d)\]\+\)\/\s*;?\s*const\s+str2\s*=\s*infixEval\(\s*noHigh\s*,\s*infix\s*\)\s*;?\s*const\s+functionCall\s*=\s*\/\(\[a-z0-9\]\*\)\\\(\(\[0-9\., \]\*\)\\\)\(\?!\.\*\\\(\)\/i\s*;?\s*const\s+toNumberList\s*=\s*(?:\(\s*args\s*\)|args)\s*=>\s*args\.split\(\s*('|"|`),\1\s*\)\.map\(\s*parseFloat\s*\)\s*;?\s*const\s+apply\s*=\s*\(\s*fn\s*,\s*args\s*\)\s*=>\s*spreadsheetFunctions\s*\[\s*fn\.toLowerCase\(\s*\)\s*\]\s*\(\s*toNumberList\(\s*args\s*\)\s*\)\s*;?\s*return\s+str2\.replace\(\s*functionCall\s*,\s*\(\s*match\s*,\s*fn\s*,\s*args\s*\)\s*=>\s*\{/);

Your callback function should return the result of calling the .hasOwnProperty() method on the spreadsheetFunctions object.

js
assert.match(code, /const\s+applyFunction\s*=\s*(?:\(\s*str\s*\)|str)\s*=>\s*\{\s*const\s+noHigh\s*=\s*highPrecedence\(\s*str\s*\)\s*;?\s*const\s+infix\s*=\s*\/\(\[(?:\\d\.|\.\\d)\]\+\)\(\[(?:\+-|-\+)\]\)\(\[(?:\\d\.|\.\\d)\]\+\)\/\s*;?\s*const\s+str2\s*=\s*infixEval\(\s*noHigh\s*,\s*infix\s*\)\s*;?\s*const\s+functionCall\s*=\s*\/\(\[a-z0-9\]\*\)\\\(\(\[0-9\., \]\*\)\\\)\(\?!\.\*\\\(\)\/i\s*;?\s*const\s+toNumberList\s*=\s*(?:\(\s*args\s*\)|args)\s*=>\s*args\.split\(\s*('|"|`),\1\s*\)\.map\(\s*parseFloat\s*\)\s*;?\s*const\s+apply\s*=\s*\(\s*fn\s*,\s*args\s*\)\s*=>\s*spreadsheetFunctions\s*\[\s*fn\.toLowerCase\(\s*\)\s*\]\s*\(\s*toNumberList\(\s*args\s*\)\s*\)\s*;?\s*return\s+str2\.replace\(\s*functionCall\s*,\s*\(\s*match\s*,\s*fn\s*,\s*args\s*\)\s*=>\s*spreadsheetFunctions\.hasOwnProperty\(/);

You should pass fn to the .hasOwnProperty() method.

js
assert.match(code, /const\s+applyFunction\s*=\s*(?:\(\s*str\s*\)|str)\s*=>\s*\{\s*const\s+noHigh\s*=\s*highPrecedence\(\s*str\s*\)\s*;?\s*const\s+infix\s*=\s*\/\(\[(?:\\d\.|\.\\d)\]\+\)\(\[(?:\+-|-\+)\]\)\(\[(?:\\d\.|\.\\d)\]\+\)\/\s*;?\s*const\s+str2\s*=\s*infixEval\(\s*noHigh\s*,\s*infix\s*\)\s*;?\s*const\s+functionCall\s*=\s*\/\(\[a-z0-9\]\*\)\\\(\(\[0-9\., \]\*\)\\\)\(\?!\.\*\\\(\)\/i\s*;?\s*const\s+toNumberList\s*=\s*(?:\(\s*args\s*\)|args)\s*=>\s*args\.split\(\s*('|"|`),\1\s*\)\.map\(\s*parseFloat\s*\)\s*;?\s*const\s+apply\s*=\s*\(\s*fn\s*,\s*args\s*\)\s*=>\s*spreadsheetFunctions\s*\[\s*fn\.toLowerCase\(\s*\)\s*\]\s*\(\s*toNumberList\(\s*args\s*\)\s*\)\s*;?\s*return\s+str2\.replace\(\s*functionCall\s*,\s*\(\s*match\s*,\s*fn\s*,\s*args\s*\)\s*=>\s*spreadsheetFunctions\.hasOwnProperty\(\s*fn/);

You should call the .toLowerCase() method on fn.

js
assert.match(code, /const\s+applyFunction\s*=\s*(?:\(\s*str\s*\)|str)\s*=>\s*\{\s*const\s+noHigh\s*=\s*highPrecedence\(\s*str\s*\)\s*;?\s*const\s+infix\s*=\s*\/\(\[(?:\\d\.|\.\\d)\]\+\)\(\[(?:\+-|-\+)\]\)\(\[(?:\\d\.|\.\\d)\]\+\)\/\s*;?\s*const\s+str2\s*=\s*infixEval\(\s*noHigh\s*,\s*infix\s*\)\s*;?\s*const\s+functionCall\s*=\s*\/\(\[a-z0-9\]\*\)\\\(\(\[0-9\., \]\*\)\\\)\(\?!\.\*\\\(\)\/i\s*;?\s*const\s+toNumberList\s*=\s*(?:\(\s*args\s*\)|args)\s*=>\s*args\.split\(\s*('|"|`),\1\s*\)\.map\(\s*parseFloat\s*\)\s*;?\s*const\s+apply\s*=\s*\(\s*fn\s*,\s*args\s*\)\s*=>\s*spreadsheetFunctions\s*\[\s*fn\.toLowerCase\(\s*\)\s*\]\s*\(\s*toNumberList\(\s*args\s*\)\s*\)\s*;?\s*return\s+str2\.replace\(\s*functionCall\s*,\s*\(\s*match\s*,\s*fn\s*,\s*args\s*\)\s*=>\s*spreadsheetFunctions\.hasOwnProperty\(\s*fn\.toLowerCase\(\s*\)\s*\)/);

--seed--

--seed-contents--

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="./styles.css" />
    <title>Functional Programming Spreadsheet</title>
  </head>
  <body>
    <div id="container">
      <div></div>
    </div>
    <script src="./script.js"></script>
  </body>
</html>
css
#container {
  display: grid;
  grid-template-columns: 50px repeat(10, 200px);
  grid-template-rows: repeat(11, 30px);
}

.label {
  background-color: lightgray;
  text-align: center;
  vertical-align: middle;
  line-height: 30px;
}
js
const infixToFunction = {
  "+": (x, y) => x + y,
  "-": (x, y) => x - y,
  "*": (x, y) => x * y,
  "/": (x, y) => x / y,
}

const infixEval = (str, regex) => str.replace(regex, (_match, arg1, operator, arg2) => infixToFunction[operator](parseFloat(arg1), parseFloat(arg2)));

const highPrecedence = str => {
  const regex = /([\d.]+)([*\/])([\d.]+)/;
  const str2 = infixEval(str, regex);
  return str === str2 ? str : highPrecedence(str2);
}

const isEven = num => num % 2 === 0;
const sum = nums => nums.reduce((acc, el) => acc + el, 0);
const average = nums => sum(nums) / nums.length;

const median = nums => {
  const sorted = nums.slice().sort((a, b) => a - b);
  const length = sorted.length;
  const middle = length / 2 - 1;
  return isEven(length)
    ? average([sorted[middle], sorted[middle + 1]])
    : sorted[Math.ceil(middle)];
}

const spreadsheetFunctions = {
  sum,
  average,
  median
}

--fcc-editable-region--
const applyFunction = str => {
  const noHigh = highPrecedence(str);
  const infix = /([\d.]+)([+-])([\d.]+)/;
  const str2 = infixEval(noHigh, infix);
  const functionCall = /([a-z0-9]*)\(([0-9., ]*)\)(?!.*\()/i;
  const toNumberList = args => args.split(",").map(parseFloat);
  const apply = (fn, args) => spreadsheetFunctions[fn.toLowerCase()](toNumberList(args));
  return str2.replace(functionCall, () => {})
}
--fcc-editable-region--

const range = (start, end) => Array(end - start + 1).fill(start).map((element, index) => element + index);
const charRange = (start, end) => range(start.charCodeAt(0), end.charCodeAt(0)).map(code => String.fromCharCode(code));

const evalFormula = (x, cells) => {
  const idToText = id => cells.find(cell => cell.id === id).value;
  const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi;
  const rangeFromString = (num1, num2) => range(parseInt(num1), parseInt(num2));
  const elemValue = num => character => idToText(character + num);
  const addCharacters = character1 => character2 => num => charRange(character1, character2).map(elemValue(num));
  const rangeExpanded = x.replace(rangeRegex, (_match, char1, num1, char2, num2) => rangeFromString(num1, num2).map(addCharacters(char1)(char2)));
  const cellRegex = /[A-J][1-9][0-9]?/gi;
  const cellExpanded = rangeExpanded.replace(cellRegex, match => idToText(match.toUpperCase()));
}

window.onload = () => {
  const container = document.getElementById("container");
  const createLabel = (name) => {
    const label = document.createElement("div");
    label.className = "label";
    label.textContent = name;
    container.appendChild(label);
  }
  const letters = charRange("A", "J");
  letters.forEach(createLabel);
  range(1, 99).forEach(number => {
    createLabel(number);
    letters.forEach(letter => {
      const input = document.createElement("input");
      input.type = "text";
      input.id = letter + number;
      input.ariaLabel = letter + number;
      input.onchange = update;
      container.appendChild(input);
    })
  })
}

const update = event => {
  const element = event.target;
  const value = element.value.replace(/\s/g, "");
  if (!value.includes(element.id) && value.startsWith('=')) {

  }
}