freeCodeCamp/curriculum/challenges/english/02-javascript-algorithms-an.../functional-programming-spre.../part-140.md

8.8 KiB

id title challengeType dashedName
5d79253ad297a31cbe073718 Part 140 0 part-140

--description--

The function in the map method can actually take a second argument: the index of the element.

This is why you need an arrow function in charRange - if you don't use one, then the index will be passed to String.fromCharCode as the second argument, leading to unexpected results. However, it is safe for functions like parseFloat which take only one argument (but not for parseInt).

Chain .map((x, i) => x + i) to .fill(start) to add its index to every element in the array in range.

--hints--

See description above for instructions.

assert(
  code
    .replace(/\s/g, '')
    .includes(
      'constrange=(start,end)=>Array(end-start+1).fill(start).map((x,i)=>x+i)'
    )
);

--seed--

--before-user-code--

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Spreadsheet</title>
  <style>
    #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;
    }
  </style>
</head>
<body>
<div id="container">
  <div></div>
</div>

--after-user-code--

</body>
</html>

--seed-contents--

<script>

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, (_, arg1, fn, arg2) =>
    infixToFunction[fn](parseFloat(arg1), parseFloat(arg2))
  );

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

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

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

const spreadsheetFunctions = {
  "": x => x,
  random: ([x, y]) => Math.floor(Math.random() * y + x),
  increment: nums => nums.map(x => x + 1),
  firsttwo: arr => arr.slice(0, 2),
  lasttwo: arr => arr.slice(-2),
  even: nums => nums.filter(isEven),
  sum,
  average,
  median,
  has2: arr => arr.includes(2),
  nodups: arr => arr.reduce((a, x) => a.includes(x) ? a : a.concat(x), []),
  range: arr => range(...arr),
  someeven: arr => arr.some(isEven),
  everyeven: arr => arr.every(isEven)
};

const applyFn = str => {
  const noHigh = highPrecedence(str);
  const infix = /([0-9.]+)([+-])([0-9.]+)/;
  const str2 = infixEval(noHigh, infix);
  const regex = /([a-z]*)\(([0-9., ]*)\)(?!.*\()/i;
  const toNumberList = args => args.split(",").map(parseFloat);
  const applyFunction = (fn, args) =>
    spreadsheetFunctions[fn.toLowerCase()](toNumberList(args));
  return str2.replace(
    regex,
    (match, fn, args) =>
      spreadsheetFunctions.hasOwnProperty(fn.toLowerCase()) ? applyFunction(fn, args) : match
  );
};

const range = (start, end) => Array(end - start + 1).fill(start);



const charRange = (start, end) =>
  range(start.charCodeAt(0), end.charCodeAt(0)).map(x =>
    String.fromCharCode(x)
  );

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 = (n1, n2) => range(parseInt(n1), parseInt(n2));
  const elemValue = n => c => idToText(c + n);
  const addChars = c1 => c2 => n => charRange(c1, c2).map(elemValue(n));
  const varRangeExpanded = x.replace(rangeRegex, (_, c1, n1, c2, n2) =>
    rangeFromString(n1, n2).map(addChars(c1)(c2))
  );
  const varRegex = /[A-J][1-9][0-9]?/gi;
  const varExpanded = varRangeExpanded.replace(
    varRegex,
    match => idToText(match.toUpperCase())
  );
  const functionExpanded = applyFn(varExpanded);
  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(x => {
    createLabel(x);
    letters.forEach(y => {
      const input = document.createElement("input");
      input.type = "text";
      input.id = y + x;
      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[0] === "=") {
    element.value = evalFormula(
      value.slice(1),
      Array.from(document.getElementById("container").children)
    );
  }
};
</script>

--solutions--

<script>
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, (_, arg1, fn, arg2) =>
    infixToFunction[fn](parseFloat(arg1), parseFloat(arg2))
  );

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

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

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

const spreadsheetFunctions = {
  "": x => x,
  random: ([x, y]) => Math.floor(Math.random() * y + x),
  increment: nums => nums.map(x => x + 1),
  firsttwo: arr => arr.slice(0, 2),
  lasttwo: arr => arr.slice(-2),
  even: nums => nums.filter(isEven),
  sum,
  average,
  median,
  has2: arr => arr.includes(2),
  nodups: arr => arr.reduce((a, x) => a.includes(x) ? a : a.concat(x), []),
  range: arr => range(...arr),
  someeven: arr => arr.some(isEven),
  everyeven: arr => arr.every(isEven)
};

const applyFn = str => {
  const noHigh = highPrecedence(str);
  const infix = /([0-9.]+)([+-])([0-9.]+)/;
  const str2 = infixEval(noHigh, infix);
  const regex = /([a-z]*)\(([0-9., ]*)\)(?!.*\()/i;
  const toNumberList = args => args.split(",").map(parseFloat);
  const applyFunction = (fn, args) =>
    spreadsheetFunctions[fn.toLowerCase()](toNumberList(args));
  return str2.replace(
    regex,
    (match, fn, args) =>
      spreadsheetFunctions.hasOwnProperty(fn.toLowerCase()) ? applyFunction(fn, args) : match
  );
};

const range = (start, end) => Array(end - start + 1).fill(start).map((x, i) => x + i);



const charRange = (start, end) =>
  range(start.charCodeAt(0), end.charCodeAt(0)).map(x =>
    String.fromCharCode(x)
  );

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 = (n1, n2) => range(parseInt(n1), parseInt(n2));
  const elemValue = n => c => idToText(c + n);
  const addChars = c1 => c2 => n => charRange(c1, c2).map(elemValue(n));
  const varRangeExpanded = x.replace(rangeRegex, (_, c1, n1, c2, n2) =>
    rangeFromString(n1, n2).map(addChars(c1)(c2))
  );
  const varRegex = /[A-J][1-9][0-9]?/gi;
  const varExpanded = varRangeExpanded.replace(
    varRegex,
    match => idToText(match.toUpperCase())
  );
  const functionExpanded = applyFn(varExpanded);
  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(x => {
    createLabel(x);
    letters.forEach(y => {
      const input = document.createElement("input");
      input.type = "text";
      input.id = y + x;
      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[0] === "=") {
    element.value = evalFormula(
      value.slice(1),
      Array.from(document.getElementById("container").children)
    );
  }
};
</script>