Mastering Hard Parts of JavaScript: Callbacks II
Ryan Ameri
Posted on August 8, 2020
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.
Posted on August 8, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.