Learn Functional Programming by Building a Spreadsheet - Step 99

Tell us what’s happening:

I tried to best I could, but I’m stuck. The syntax for the has2 is kind of confusing.

Your code so far

<!-- file: index.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>
/* file: styles.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;
}
/* file: script.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)];
}


// User Editable Region

const spreadsheetFunctions = {
  sum,
  average,
  median,
  even: nums => nums.filter(isEven),
  firsttwo: nums => nums.slice(0, 2),
  lasttwo: nums => nums.slice(-2),
  has2: nums => nums.contains(2),
  increment: nums
}

// User 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, (match, fn, args) => spreadsheetFunctions.hasOwnProperty(fn.toLowerCase()) ? apply(fn, args) : match);
}

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()));
  const functionExpanded = applyFunction(cellExpanded);
  return functionExpanded === x ? functionExpanded : evalFormula(functionExpanded, cells);
}

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('=')) {
    element.value = evalFormula(value.slice(1), Array.from(document.getElementById("container").children));
  }
}

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36

Challenge Information:

Learn Functional Programming by Building a Spreadsheet - Step 99

I think I got has2, but the increment is what I’m having trouble with.

I am having the same issue on this as well.
Your has2 looks a bit off as contains is not a method as far as I can tell.
increment, i tried nums => Array(nums.forEach(num => num + 1)) but that isn’t working for me.

I know, I changed ‘contains’ to ‘includes.’ I shall try increment once more. If I’m still struggling, then I’ll let you know.

1 Like

Try with .map, as it returns a new array.
.forEach always returns undefined. This is where I went wrong :frowning:

Source:
.forEach: Array.prototype.forEach() - JavaScript | MDN
.map: Array.prototype.map() - JavaScript | MDN

2 Likes

Here’s the feedback I’m getting: “Your has2 function should return true if the array has 2 in it.” I’d like to know the error based on the feedback.

Hey Kodu,

What is your code looking like now?

Here it is:

const spreadsheetFunctions = {
  sum,
  average,
  median,
  even: nums => nums.filter(isEven),
  firsttwo: nums => nums.slice(0, 2),
  lasttwo: nums => nums.slice(-2),
  has2: nums => nums.map(2) ? true : false,
  increment: i => nums[i]++
}

Hey Kodu,

For has2: We don’t need to use the .map() function because we don’t need to return an array. Based off your previous comments, you had it right to be nums.includes(arg). This will check the array and return true if arg is found in the array, otherwise false.

For increment: Your arrow function is a bit off. I see that you are wanting to increment each value in the array, however this would throw a syntax error as nums is not defined to this particular function. nums is a parameter for some of the previous functions, but they are local. We would want to use a .map() method for incrementing each index of an array because .map() would allow us to iterate through an array and return a new one based on the callback function provided as a parameter.

Something like this: array.map(index => index + 1)source
Of course, replacing with the right names!

Happy coding (:

1 Like

so we have to use the .includes on nums or the call back function => nums

I tried firsttwo: nums => (nums[0], nums[1]), but it’s not working. I’d like to know the mistake I’m making.

Hi there!
You need to use slice() method on nums. It will take the first two numbers form the array. You need 0, 2 as a arguments within slice method.

Sorry, wrong question. The has2 and increment are what I’m having trouble with.

For has2 property you need to use .includes() method on callback nums array.
For increment property you need to use .every() method on callback nums array.

It’s a bit better now, but I still have trouble.

Here’s my code:

const spreadsheetFunctions = {
  sum,
  average,
  median,
  even: nums => nums.filter(isEven),
  firsttwo: nums => nums.slice(0, 2),
  lasttwo: nums => nums.slice(-2),
  has2: nums => nums.includes(2),
  increment: nums => nums.every(1),
}

.every method need a callback function. In the callback function implicitly return the nums array incremented by one.

That code didn’t concern with the topic.

Sorry, wrong code. Here it is:

const spreadsheetFunctions = {
  sum,
  average,
  median,
  even: nums => nums.filter(isEven),
  firsttwo: nums => nums.slice(0, 2),
  lasttwo: nums => nums.slice(-2),
  has2: nums => nums.includes(values, 2),
  increment: nums => nums.every(value, 1),
}

value in the includes() method is unnecessary.
.every() method need a callback function to implicitly return the num array increasing by 1.