DEV Community

Ryan Ameri
Ryan Ameri

Posted on

Mastering Hard Parts of JavaScript: Callbacks II

Exercise 8

Construct a function union that compares input arrays and returns a new array that contains all elements. If there are duplicate elements, only add it once to the new array. Preserve the order of the elements starting from the first element of the first input array. BONUS: Use reduce!

console.log(union([5, 10, 15], [15, 88, 1, 5, 7], [100, 15, 10, 1, 5])); should output [5, 10, 15, 88, 1, 7, 100].

Solution 8

function union(...arrays) {
  return arrays.reduce((acc, array) => {
    const newItem = array.filter((item) => !acc.includes(item));
    return acc.concat(newItem);
  });
}

Here again we are using reduce and filter, but the logic is flipped inside the filter method. The acc array is again set to the first item, but then we are checking every item in the subsequent arrays, and if that item is not included in our acc array, we are adding it and finally returning the accumulator.

Exercise 9

Construct a function objOfMatches that accepts two arrays and a callback. objOfMatches will build an object and return it. To build the object, objOfMatches will test each element of the first array using the callback to see if the output matches the corresponding element (by index) of the second array. If there is a match, the element from the first array becomes a key in an object, and the element from the second array becomes the corresponding value.

console.log(
  objOfMatches(
    ["hi", "howdy", "bye", "later", "hello"],
    ["HI", "Howdy", "BYE", "LATER", "hello"],
    function (str) {
      return str.toUpperCase();
    }
  )
);

Should log { hi: 'HI', bye: 'BYE', later: 'LATER' }

Solution 9

function objOfMatches(array1, array2, callback) {
  return array2.reduce((res, value, index) => {
    if (value === callback(array1[index])) {
      res[array1[index]] = value;
    }
    return res;
  }, Object.create(null));
}

The trick here is to note that the accumulator that goes into reduce doesn't need to be just a primitive type, it can also be an array or an object. So here we set the accumulator res to an empty object, and then we check to see if calling the callback on array1 results in the same value as the item in array 2. If they are equal, we add it to our accumulator and finally return our accumulator. The power of reduce should be apparent now, but yet it might take you a bit of time and practice to wrap your head around this. That's ok! We're going to be using reduce a lot in the following exercises 😛.

Exercise 10

Construct a function multiMap that will accept two arrays: an array of values and an array of callbacks. multiMap will return an object whose keys match the elements in the array of values. The corresponding values that are assigned to the keys will be arrays consisting of outputs from the array of callbacks, where the input to each callback is the key.

console.log(
  multiMap(
    ["catfood", "glue", "beer"],
    [
      function (str) {
        return str.toUpperCase();
      },
      function (str) {
        return str[0].toUpperCase() + str.slice(1).toLowerCase();
      },
      function (str) {
        return str + str;
      },
    ]
  )
);

should output { catfood: ['CATFOOD', 'Catfood', 'catfoodcatfood'], glue: ['GLUE', 'Glue', 'glueglue'], beer: ['BEER', 'Beer', 'beerbeer'] }

Solution 10

function multiMap(arrVals, arrCallbacks) {
  return arrVals.reduce((accum, item) => {
    accum[item] = arrCallbacks.map((fn) => fn(item));
    return accum;
  }, Object.create(null));
}

Reading the exercise, it can look a little bit challenging but looking at the expected output should make things a bit more clear. Our function accepts two parameters, an array of values and an array of functions. Then we need to construct an object in some fashion. So constructing an object from an array, immediately should spring to mind reduce.

The next difficulty is figuring out what the value of each prop inside the object is. Based on the example output, we can see that the value should be an array, an array whereby the callback function have been called on the item one by one. So we are providing an array as input and want a different array as output, this should spring to mind map.

This really is the gist of callbacks in functional programming, and this example using reduce and map shows us how much can be achieved using a little declarative code.

Exercise 11

Construct a function objectFilter that accepts an object as the first parameter and a callback function as the second parameter. objectFilter will return a new object. The new object will contain only the properties from the input object such that the property's value is equal to the property's key passed into the callback.

const cities = {
  London: "LONDON",
  LA: "Los Angeles",
  Paris: "PARIS",
};
console.log(objectFilter(cities, (city) => city.toUpperCase()));

Should output { London: 'LONDON', Paris: 'PARIS'}

Solution 11

function objectFilter(obj, callback) {
  const newObj = Object.create(null);
  for (let [key, value] of Object.entries(obj)) {
    if (
      Object.prototype.hasOwnProperty.call(obj, key) &&
      callback(key) === value
    )
      newObj[key] = value;
  }
  return newObj;
}

The only trick here is to iterate an object properly. In the old days this used to be difficult with a for...in loop which could cause some unintended sideffects. Thankfully nowadays we have Object.entries() which gives us a nice array of keys and values of the object, which we can safely iterate through.

In the conditional if statement, I would have normally used if (obj.hasOwnProperty(key)) but ESLint yelled at me and said that that's not safe, and I should call the prototype method insead in this fashion to make the code safer. Technically this check is unnessary for the given example but I just wanted to demonstrate how to safely check if an object has a property in modern JS.

Top comments (2)

Collapse
 
philosphi profile image
Phi Nguyen • Edited

This is also a valid solution for Exercise 11.

Reducing the keys of the object, to build an object of entries that satisfy the callback result.

function objectFilter(obj, callback) {
    return Object.keys(obj).reduce((acc, key) => {
    if (obj[key] == callback(key)) {
      acc[key] = obj[key]
    }
    return acc
  }, {})
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
engineeringweb profile image
Aravind • Edited

Hey @internettradie ,

Nice work on sharing your knowledge with the community.

Just want to clear few things on Exercise 11 solution you have mentioned.

if (Object.prototype.hasOwnProperty.call(obj, key) && callback(key) === value)
Enter fullscreen mode Exit fullscreen mode

In the conditional if statement, I would have normally used
if (obj.hasOwnProperty(key)) but ESLint yelled at me and said that that's
not safe, and I should call the prototype method insead in this fashion to make
the code safer. Technically this check is unnessary for the given example but
I just wanted to demonstrate how to safely check if an object has a property
in modern JS.

But that's not exactly true, I would like to explain it clearer. Actually you are creating a empty object using Object.create(null) so we will not inherit any properties from Object.protoype which is a good thing. See the snippet below

{} == Object.create(Object.prototype); // We inherit all the Object properties like hasOwnProperty, isPrototypeOf etc..

Object.create(null) // We don't inherit anything. It's just a blank object.
Enter fullscreen mode Exit fullscreen mode

If you are using const newObj = {}; you will be able to use obj.hasOwnProperty(key) as under the hood we are initializing the object {} with it's properties inherited as Object.create(Object.prototype)

But as you are using const newObj = Object.create(null); we need to first go inside its property prototype to access hasOwnProperty method and as we're not inheriting any properties we need to explicitly use call(obj, key) to check if key exists inside the object.

Hope it explains it. Cheers!