loading...

Mastering Hard Parts of JavaScript: Callbacks IV

internettradie profile image Ryan Ameri ・4 min read

Exercise 17

Create a function commutative that accepts two callbacks and a value. commutative will return a boolean indicating if the passing the value into the first function, and then passing the resulting output into the second function, yields the same output as the same operation with the order of the functions reversed (passing the value into the second function, and then passing the output into the first function).

const multBy3 = (n) => n * 3;
const divBy4 = (n) => n / 4;
const subtract5 = (n) => n - 5;
console.log(commutative(multBy3, divBy4, 11));

should log true

console.log(commutative(multBy3, subtract5, 10));

should log false

console.log(commutative(divBy4, subtract5, 48));

should log false

Solution 17

function commutative(func1, func2, value) {
  return func1(func2(value)) === func2(func1(value)) ? true : false;
}

The explanation might look daunting, but once you delve into it, you'll realise that it's actually a rather simple exercise, it just requires good understanding of callbacks (which is the point of all of these really!) The question is, if we pass the value to the first function, is the result equal to passing it to the second function?

Using the ternary operator for simple statements like this can make the code both concise and readable. Just note that you have to put the return statemtn before the first operand.

Exercise 18

Create a function objFilter that accepts an object and a callback. objFilter should make a new object, and then iterate through the passed-in object, using each key as input for the callback. If the output from the callback is equal to the corresponding value, then that key-value pair is copied into the new object. objFilter will return this new object.

const startingObj = {};
startingObj[6] = 3;
startingObj[2] = 1;
startingObj[12] = 4;
const half = (n) => n / 2;
console.log(objFilter(startingObj, half));

should log { 2: 1, 6: 3 }

Solution 18

function objFilter(obj, callback) {
  const newObj = Object.create(null);
  for (let [key, value] of Object.entries(obj)) {
    if (value === callback(parseInt(key))) newObj[key] = value;
  }
  return newObj;
}

Once again, because an object is being passed to the function, I find using a for loop easier than an Array method, though the latter is also definitely possible.

The key thing to keep in mind here is that object properties are stored as strings, even if they are just numbers. So when making the comparison, we need to make sure we cast it to the correct type (using parseInt()) to make sure that strict equality passes.

Exercise 19

Create a function rating that accepts an array (of functions) and a value. All the functions in the array will return true or false. rating should return the percentage of functions from the array that return true when the value is used as input.

const isEven = (n) => n % 2 === 0;
const greaterThanFour = (n) => n > 4;
const isSquare = (n) => Math.sqrt(n) % 1 === 0;
const hasSix = (n) => n.toString().includes("6");
const checks = [isEven, greaterThanFour, isSquare, hasSix];
console.log(rating(checks, 64));

should log 100

console.log(rating(checks, 66));

should log 75

Solution 19

function rating(arrOfFuncs, value) {
  let trueCnt = arrOfFuncs.reduce((accum, fn) => {
    if (fn(value)) accum++;
    return accum;
  }, 0);
  return (trueCnt / arrOfFuncs.length) * 100;
}

An array of functions can look a bit intimidating at first, but it's just an array! So we are taking in an array, and we want a single value to be computed from the array (the number of times its functions returned true) so we are looking at reduce again! Yay!

Here accum is initially set to 0, and everytime the function inside the array returnes true, we increment it. Finally we do a quick calculation based on the size of the array to turn this count into a percentage and return it.

Exercise 20

Create a function pipe that accepts an array (of functions) and a value. pipe should input the value into the first function in the array, and then use the output from that function as input for the second function, and then use the output from that function as input for the third function, and so forth, until we have an output from the last function in the array. pipe should return the final output.

const capitalize = (str) => str.toUpperCase();
const addLowerCase = (str) => str + str.toLowerCase();
const repeat = (str) => str + str;
const capAddlowRepeat = [capitalize, addLowerCase, repeat];
console.log(pipe(capAddlowRepeat, "cat"));

should log 'CATcatCATcat'

Solution 20

function pipe(arrOfFuncs, value) {
  return arrOfFuncs.reduce((accum, fn) => {
    return fn(accum) || fn(value);
  }, "");
}

I admit that I found this exercise difficult at first. The trick is in the callback function inside the reduce. The line return fn(accum) || fn(value); means if fn(accum) return fn(accum) else return fn(value) But I've seen this condensed format using the || operator used a lot in open source projects so I decided to use it here even if to a newbie's eyes, the more descriptive format is more readbale.

The initial accum in the reduce is an empty string. If fn(accum) returns empty, then assign it to fn(value). In consecutive calls, fn(accum) will return true, so its new value is returned.

Posted on by:

internettradie profile

Ryan Ameri

@internettradie

Re-discovering frontend development. Professional translator & interpreter. Amateur powerlifter. BLM. He/him/his

Discussion

pic
Editor guide
 

Hi,

Your solution for exercise 20 is not really correct. It works for provided functions and inputs, but won't work for situations where, for example, first function appends something to the string. It also assumes that parameters are strings.

Example where it will fail (return ssss):

const addS = (str) => str + 's';
const addLowerCase = (str) => str + str.toLowerCase();
const repeat = (str) => str + str;
const capAddlowRepeat = [addS, addLowerCase, repeat];
console.log(pipe(capAddlowRepeat, "cat"));

I believe more complete and actually also simpler solution is to correctly use initial value of reduce, like below:

function pipe(callbacks, value) {
  return callbacks.reduce((accum, cb) => cb(accum), value);
}

Cheers!