I find it extremely beneficial to ask a job seeker to solve some of the following exercises. It helps to understand how good is a person you're dealing with in JavaScript and also shows how he or she thinks about the problems in general.
In this article, I'd like to share some of those exercises.
Just putting them here in no particular order. Feel free to use any of them in your next interview.
reduce
? Please implement the map
and filter
functions with reduce
.SOLUTION: Array.proptotype.reduce
is a function on an array, which allows to iterate through the array and collect a result to return (which can be modified on each iteration). It takes a reducer function and an initial value for the accumulator.
const arr = [1, 2, 3];
arr.reduce((acc, el) => {
return el + acc;
}, 0); // => 15
Here's how we can implement map
:
function map(arr, mapper) {
return arr.reduce((acc, el) => {
return [...acc, mapper(el)];
}, []);
}
map([1, 2, 3], (x) => x * x); // => [1, 4, 9]
And here's filter
:
function filter(arr, f) {
return arr.reduce((acc, el) => (f(el) ? [...acc, el] : acc), []);
}
filter([-1, 0, 1, 2], (x) => x > 0); // => [1, 2]
uniq
function that removes repeated elements from the array.SOLUTION:
function uniq(arr) {
return arr.reduce(
(acc, el) => (acc.indexOf(el) > -1 ? acc : [...acc, el]),
[]
);
}
uniq([1, 2, 3, 1, 2]); // => [1, 2, 3]
An even shorter solution is possible if we utilize Set which can\t have non-unique members:
const uniq = (arr) => [...new Set(arr)];
Flatten
is a function that puts elements from the inner arrays into the top array.Can you implement deepFlatten
so that
deepFlatten([1, [2], [[3], [4, [5]]]]); // => [1, 2, 3, 4, 5]
SOLUTION:
function deepFlatten(arr) {
return arr.reduce(
(acc, el) =>
Array.isArray(el) ? [...acc, ...deepFlatten(el)] : [...acc, el],
[]
);
}
Assuming we have an array of users
const users = [
{ name: "John", hobbies: ["singing", "walking", "playing guitar"] },
{ name: "Terry", hobbies: ["swimming", "playing guitar"] },
{ name: "Anna", hobbies: ["walking", "swimming", "playing guitar"] },
{ name: "Paul", hobbies: ["swimming", "singing"] },
];
For each hobby, count the number of users occupied with it.
SOLUTION:
const hobbies = uniq(flatten(users.map(u => u.hobbies)))
const hasHobby = (h) => (u) => u.hobbies.indexOf(h) > -1
const counts hobbies.map( h => ({
hobby: h,
count: users.filter(hasHobby(h)).length,
})) // => [ {hobby: 'singing', count: 2}, ... ]
SOLUTION 2 (Imperative):
const counts = {}
users.forEach(u => {
u.hobbies.forEach(h => {
counts[h] = (counts[h] || 0) + 1
})
})
SOLUTION:
function sample(arr, n) {
const length = arr.length
const indices = []
while(indices.length < n){
const r = Math.floor(Math.random() * length);
if (indices.indexOf(r) === -1) indices.push(r);
}
return indices.map(i => arr[i])
}
sample([1, 2, 3, 4, 5, 6, 7, 8, 9], 3) // => [7, 9, 6]
memoize
functionIt wraps a given function and only runs it once for a list of parameters, saves the result in inner cache and return it the next time.
SOLUTION (Basic):
function memoize(fn) {
const cache = {};
return function (...args) {
const key = args.join("-");
if (!cache[key]) {
cache[key] = fn(...args);
}
return cache[key];
};
}
This solution is very simple. But it doesn’t work for some cases. We can make it more complex by asking: what if a function returns a boolean, what if a function is type-unsafe ( 1 vs “1” ), …
...no matter how deep, and will return null
if no value is there without throwing an exception.
Example:
safeGet({ a: { b: 5 } }, "a.b"); // => 5
SOLUTION:
function safeGet(obj, path) {
const keys = path.split(".");
return keys.reduce((acc, key) => {
return (acc || {})[key] ? acc[key] : null;
}, obj);
}
safeGet({ a: { b: 5 } }, "a.b"); //=> 5
safeGet({ a: { b: 5 } }, "a.b.c.d"); //=> null
...and it returns a new function which you can use with the rest of the arguments. Write the curry
function.
Example:
sum(1, 2) // => 3
const addOne = sum(1)
addOne(2) // => 3
SOLUTION:
function curry(fn, arity) {
arity = arity || fn.length
return function(...args) {
if (args.length < arity) {
function f(...lefts) {
return fn(...args, ...lefts)
}
return curry(f, arity - args.length)
} else {
return fn(...args)
}
}
}
const sum = curry((a, b, c) => a + b + c)
sum(1, 2, 3) //=> 6
sum(1)(2, 3) //=> 6
sum(1, 2)(3) //=> 6
sum(1)(2)(3) //=> 6