I am trying to figure out what to write about today. My day was quiet, and I enjoyed coding without any confusing problems. However, I still have some clothes that need to be put away, and I have done some of it already. Every time I get up, I try to put a few things away from the pile, and at this rate, I should be done by tonight!
Tomorrow is my therapy session, which is a good thing. Since Kevin passed away on Easter (20 years ago), my head has been replaying his death over and over again. Most of that period is fuzzy for me, but I remember specific things, and that’s what my brain keeps replaying. Perhaps therapy tomorrow can help me through that.
Other than that, I’m doing well today. My mental well-being is good, and I’m physically well, too. Tommy and I will do weights tonight, and I hope I’m getting stronger. That’s my plan with the weights.
And tonight, we are having tacos. 🙂
JavaScript notes…
——————————–
Spreadsheet steps 71 – 90
Step 71
infixToFunction[operator] returns a function. Call that function directly, passing arg1 and arg2 as the arguments.
const infixEval = (str, regex) => str.replace(regex, (_match, arg1, operator, arg2) => infixToFunction[operator](arg1, arg2));
Step 72
You have a slight bug. arg1 and arg2 are strings, not numbers. infixToFunction[‘+’](“1”, “2”) would return 12, which is not mathematically correct.
Wrap each of your infixToFunction[operator] arguments in a parseFloat() call.
const infixEval = (str, regex) => str.replace(regex, (_match, arg1, operator, arg2) => infixToFunction[operator](parseFloat(arg1), parseFloat(arg2)));
Step 73
Now that you can evaluate mathematical expressions, you need to account for order of operations. Declare a highPrecedence function that takes a str parameter.
const highPrecedence = (str) => { }
Step 74
In your highPrecedence function, declare a regex variable. Assign it a regular expression that matches a number (including decimal numbers) followed by a * or / operator followed by another number.
Each number, and the operator, should be in separate capture groups.
const highPrecedence = str => { const regex = /([\d.]+)([*\/])([\d.]+)/ }
Step 75
Now that you have a regular expression to match multiplication or division, you can evaluate that expression.
Declare a str2 variable and assign it the result of calling infixEval with str and regex as arguments.
const highPrecedence = str => { const regex = /([\d.]+)([*\/])([\d.]+)/; const str2 = infixEval(str, regex); }
Step 76
Your infixEval function will only evaluate the first multiplication or division operation, because regex isn’t global. This means you’ll want to use a recursive approach to evaluate the entire string.
If infixEval does not find any matches, it will return the str value as-is. Using a ternary expression, check if str2 is equal to str. If it is, return str, otherwise return the result of calling highPrecedence() on str2.
const highPrecedence = str => { const regex = /([\d.]+)([*\/])([\d.]+)/; const str2 = infixEval(str, regex); return str2 === str ? str : highPrecedence(str2); }
Step 77
Now you can start applying your function parsing logic to a string. Declare a function called applyFunction, which takes a str parameter.
const applyFunction = (str) => { }
Step 78
First you need to handle the higher precedence operators. Declare a noHigh variable, and assign it the result of calling highPrecedence() with str as an argument.
const applyFunction = str => { const noHigh = highPrecedence(str); }
Step 79
Now that you’ve parsed and evaluated the multiplication and division operators, you need to do the same with the addition and subtraction operators.
Declare an infix variable, and assign it a regular expression that matches a number (including decimal numbers) followed by a + or – operator followed by another number.
const applyFunction = str => { const noHigh = highPrecedence(str); const infix = /([\d.]+)([+-])([\d.]+)/; }
Step 80
Declare a str2 variable, and assign it the result of calling infixEval() with noHigh and infix as arguments.
const applyFunction = str => { const noHigh = highPrecedence(str); const infix = /([\d.]+)([+-])([\d.]+)/; const str2 = infixEval(noHigh, infix); }
Step 81
Declare a functionCall variable, and assign it this regular expression: /([a-z0-9]*)\(([0-9., ]*)\)(?!.*\()/i
This expression will look for function calls like sum(1, 4).
const applyFunction = str => { const noHigh = highPrecedence(str); const infix = /([\d.]+)([+-])([\d.]+)/; const str2 = infixEval(noHigh, infix); const functionCall = /([a-z0-9]*)\(([0-9., ]*)\)(?!.*\()/i; }
Step 82
Declare a toNumberList function that takes an args parameter and implicitly returns the result of splitting the args by commas. Then chain a map method to your split method and pass in parseFloat as the argument to the map method.
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); }
Step 83
Declare an apply function that takes a fn and args parameter.
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) => { } }
Step 84
The fn parameter will be the name of a function, such as SUM. Update apply to implicitly return the function found at the fn property of your spreadsheetFunctions object.
Remember that fn might not be lowercase, so you’ll need to convert it to a lowercase string.
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()] }
Step 85
Your apply function is returning the spreadsheet function, but not actually applying it. Update apply to call the function. Pass in the result of calling toNumberList with args as an argument.
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)); }
Step 86
Now your applyFunction needs to return a result. Return the result of calling the .replace() method on str2. Pass your functionCall regex and an empty callback.
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, () => {}) }
Step 87
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.
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())) }
Step 88
Use the ternary operator to turn your .hasOwnProperty() call into the condition. If the object has the property, return the result of calling apply with fn and args as arguments. Otherwise, return match.
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); }
Step 89
Now you can start applying your function parser to your evalFormula logic. Declare a functionExpanded variable, and assign it the result of calling applyFunction with your cellExpanded string.
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) }
Step 90
Like you did with your highPrecedence() function, your evalFormula() function needs to ensure it has evaluated and replaced everything.
Use a ternary to check if functionExpanded is equal to the original string x. If it is, return functionExpanded, otherwise return the result of calling evalFormula() again with functionExpanded and cells as arguments.
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); }