Mastering Hard Parts of JavaScript: Callbacks III
Tags: javascript, codenewbie, callbacks
Published: 2020-08-09 21:16:13

---
series: Mastering Hard Parts of JavaScript
description: An introduction to JavaScript callbacks by completing 24 exercises
---
## Exercise 12

> Create a function majority that accepts an array and a callback. The callback will return either true or false. majority will iterate through the array and perform the callback on each element until it can be determined if the majority of the return values from the callback are true. If the number of true returns is equal to the number of false returns, majority should return false.

```js
const isOdd = function (num) {
  return num % 2 === 1;
};
console.log(majority([1, 2, 3, 4, 5, 7, 9, 11], isOdd));
```

should log `true`

```js
console.log(majority([2, 3, 4, 5], isOdd));
```

should log `false`.

### Solution 12

```js
function majority(array, callback) {
  let trueCount = 0;
  let falseCount = 0;
  array.forEach((item) => {
    callback(item) ? trueCount++ : falseCount++;
  });
  return trueCount > falseCount ? true : false;
}
```

I found this exercice to be on the easy side, as long as you use two variables and initialise them to zero. Also demonstrating the use of the terneray operator, which I find helps in readablity of simple `if...else...` blocks.

## Exercise 13

> Create a function prioritize that accepts an array and a callback. The callback will return either true or false. prioritize will iterate through the array and perform the callback on each element, and return a new array, where all the elements that yielded a return value of true come first in the array, and the rest of the elements come second.

```js
const startsWithS = function (str) {
  return str[0] === "s" || str[0] === "S";
};
console.log(
  prioritize(
    ["curb", "rickandmorty", "seinfeld", "sunny", "friends"],
    startsWithS
  )
);
```

Should log `['sunny', 'seinfeld', 'curb', 'rickandmorty', 'friends']`

### Solution 13

```js
function prioritize(array, callback) {
  return array.reduce((accum, item) => {
    callback(item) ? accum.unshift(item) : accum.push(item);
    return accum;
  }, []);
}
```

This is actually very similar to the previous exercise, except now instead of having two variables, we have two arrays, a truthy array and a falsy array. Since the example expects a single array to be returned, we need to concat these two arrays. I decided to save a bit of code and memory and just use a single array, and use two different array methods, unshift() and push() to put truthy and falsy values at the two ends of the array. Also notice that we are again using reduce, but providing an empty array as the accumulator of our reduce.

Edit: For an alternative solution that preserves the order of items, have a look at Khushbu's comment in the discussion below.

## Exercice 14

> Create a function countBy that accepts an array and a callback, and returns an object. countBy will iterate through the array and perform the callback on each element. Each return value from the callback will be saved as a key on the object. The value associated with each key will be the number of times that particular return value was returned.

```js
console.log(
  countBy([1, 2, 3, 4, 5], function (num) {
    if (num % 2 === 0) return "even";
    else return "odd";
  })
);
```

should log `{ odd: 3, even: 2 }`

### Solution 14

```js
function countBy(array, callback) {
  return array.reduce((obj, item) => {
    let result = callback(item);
    obj[result] ? (obj[result] = obj[result] + 1) : (obj[result] = 1);
    return obj;
  }, Object.create(null));
}
```

By now we should be familiar with this pattern. We are taking in an array and returning a single object, so we're looking for reduce! We provide the new object as the accumulator to reduce, run the callback on the items in the array, and based on its return value, set the value in the object accordingly.

The power of reduce should be apparent now.

## Exercise 15

> Create a function groupBy that accepts an array and a callback, and returns an object. groupBy will iterate through the array and perform the callback on each element. Each return value from the callback will be saved as a key on the object. The value associated with each key will be an array consisting of all the elements that resulted in that return value when passed into the callback.

```js
const decimals = [1.3, 2.1, 2.4];
const floored = function (num) {
  return Math.floor(num);
};
console.log(groupBy(decimals, floored));
```

should log `{ 1: [1.3], 2: [2.1, 2.4] }`

### Solution 15

```js
function groupBy(array, callback) {
  return array.reduce((obj, item, index, arr) => {
    let res = callback(item);
    obj[res] = arr.filter((element) => parseInt(element) === parseInt(res));
    return obj;
  }, Object.create(null));
}
```

The solution here requires knowing that reduce can take more than a callback and the item, it can also (optionally) take the index of the array and the whole array as parameters as well.

If this was being done by traditional for loops, you'd need two nested loops here. Using these Array methods, the first loop is replaced with array.reduce() and the second loop is replaced by arr.filter(). filter() takes a callback function and returns all the elements for which the callback returns true. Here filter returns an array, and we just assign that to be the value inside our newly created (accumulator) object.

It took me a while to get comfortable with this style of declarative programming and using Array methods. Once you do get comfortable with them though, you don't want to go back to for loops, with all the potential off-by-one errors they introduce. But sometimes a loop is just easier to read and implement, as we'll see in the next exercise.

Edit: Also have a look at Khushbu's solution below in the discussion, which is a faster way of solving the problem (albeit without using filter). 

## Exercise 16

> Create a function goodKeys that accepts an object and a callback. The callback will return either true or false. goodKeys will iterate through the object and perform the callback on each value. goodKeys will then return an array consisting only the keys whose associated values yielded a true return value from the callback.

```js
const sunny = {
  mac: "priest",
  dennis: "calculating",
  charlie: "birdlaw",
  dee: "bird",
  frank: "warthog",
};
const startsWithBird = function (str) {
  return str.slice(0, 4).toLowerCase() === "bird";
};
console.log(goodKeys(sunny, startsWithBird));
```

should log `['charlie', 'dee']`

### Solution 16

```js
function goodKeys(obj, callback) {
  const arr = [];
  for (let [key, value] of Object.entries(obj)) {
    if (callback(value)) arr.push(key);
  }
  return arr;
}
```

In this exercise, I really struggled to use Array methods instead. The callback function ended up looking rather ugly, and it really felt like it wasn't the right tool for the job. There is no point being dogmatic, if a loop is easier to read and does the job well enough, we don't need to avoid it! Especially now that we can easily turn objects into iterables with Object.entries() and then use destructuring and the `for...of` loop to easily iterate through it.

If you have a neat way of using an Array method instead of a loop to solve this, please let me know!