An Intro To JS Higher Order Array Functions
Tags: javascript, codenewbie, webdev, beginners
Published: 2021-12-01 06:56:37

One of the most crucial skills in JS is understanding how higher order and callback functions work. Simply put, a higher order function is a function that: 1) takes a different function as an argument **and/or** 2) returns a new function. That's it. A callback function is just the function that gets passed in. These are comp-sci words that hides simple concepts. For example, this is basically all `forEach` does:

```js
const fakeForEach = (arr, callbackFunction) => {
  for (let i = 0; i < arr.length; i++) {
    const value = arr[i]
    const index = i;
    const givenArr = arr;
    callbackFunction(value, index, givenArr)
  }
}

const myArr = ['a', 'b', 'c']
const myCallback = (val, idx, arr) => {
  console.log('Value at index:', val);
  console.log('Current index:', idx);
  console.log('Original array:', arr);
};

// these will log the same things!
fakeForEach(myArr, myCallback);
myArr.forEach(myCallback);
```

By passing in a function *but not calling it* we allow a higher order function, in this case `fakeForEach` or `.forEach` to invoke it with each iteration of the loop. Now lets break down some the major higher order array functions that come built in with JS.

Also, you can of course define the callback functions inline, but for the following examples, I'm explicitly creating a variable just so it's *perfectly* clear what the callback refers to.

```js
// inline
arr.forEach((val) => {
  console.log(val)
});

// variable
const callback = (val) => {
  console.log(val)
});
arr.forEach(callback);

// both are fine!
```

## .forEach
### Function Description
`.forEach` iterates through an array without caring about return values. If you essentially want a basic loop or mutate an existing object, this is your method.

### Callback Description
The callback for `forEach` takes in the value, the index, and the original array during each iteration. The return value of the provided callback is ignored.

### Example
```js
const letters = ['a', 'b', 'c'];
const callback = (val, idx, arr) => {
  console.log('Value at index:', val);
  console.log('Current index:', idx);
  console.log('Original array:', arr);
};
letters.forEach(callback);
// Value at index: a
// Current index: 0
// Original array: [ 'a', 'b', 'c' ]
// Value at index: b
// Current index: 1
// Original array: [ 'a', 'b', 'c' ]
// Value at index: c
// Current index: 2
// Original array: [ 'a', 'b', 'c' ]
```
----------------------------------------------------------------

## .map
### Function Description
`.map` is a lot like `forEach`, except that it builds up and returns a new array.

### Callback Description
Like `forEach`, the provided callback gives you access to the value, the index, and the original array. Each individual return value from the callback is what gets saved to the new array.

### Example
```js
const numbers = [10, 20, 30];

const callback = (val, idx, arr) => {
  console.log('Value at index:', val);
  console.log('Current index:', idx);
  console.log('Original array:', arr);
  return val * 100;
};
const bigNumbers = numbers.map(callback);

console.log('bigNumbers: ', bigNumbers);
// bigNumbers:  [ 1000, 2000, 3000 ]
```
-------------------------------------------------------
## .filter
### Function Description
`filter` is used to return a new array based off of values that pass a condition.

### Callback Description
The callback has the value, index, and array, however it is the return value that's interesting. If an iteration has a truthy return value, the value in the array at that iteration gets saved to the new array.

```js
const names = ['tom', 'ezekiel', 'robert'];

const callback = (val, idx, arr) => {
  return val.length > 3;
};
const longNames = names.filter(callback);
console.log('longNames: ', longNames);
// longNames:  [ 'ezekiel', 'robert' ]
```
----------------------------------------------------------------
## .some
### Function Description
Some returns a boolean if at least *one* of the elements in the array meets the given condition.

### Callback Description
It's a standard value, index, arr situation. However, unlike the other methods so far, once the callback returns `true`, `some` will stop iterating through the array. That's becuase theres no need to keep going. Remember, `some` only cares if there's at least one value, if you want the *exact* number of truthy values, you should use either `forEach` and keep a `count` variable, or `filter` and then just use the length of the new array. The only way `some` will iterate through the entire array is if it never finds a value that returns a truthy value. At which point `some` will return `false`.


### Example
```js
const numbers = [1, 4, 9001, 7, 12];
const callback = num => {
  console.log('num: ', num);
  return num > 9000;
};
const isOver9000 = numbers.some(callback);
// num:  1
// num:  4
// num:  9001

console.log('isOver9000: ', isOver9000);
// isOver9000:  true
```

----------------------------------------------------------------

## .every
### Function Description
`every` returns a boolean, `true` if *every* value in the array passes the callback's condition, and `false` otherwise.


### Callback description
The callback has the value, index, and array, we've come to know and love. It works exactly like `some`, where it evaluates the return values as truthy/falsy. However, it abandons iteration if a single value returns falsy, which is the opposite of `some`. It's kind of like `||` vs `&&` short circuiting.

### Example
```js
const numbers = [9001, 9002, 7, 12];

const callback = (num) => {
  console.log('num: ', num);
  return num > 9000;
}
const areAllOver9000 = numbers.every(callback)
// num:  9001
// num:  9002

console.log('areAllOver9000: ', areAllOver9000);
// areAllOver9000:  false
```
-------------------------------------------------------------------------------------
## The more complicated iterators
The next methods depart somewhat from the `val, idx, arr` pattern of callbacks and are a little more complicated. As such, let's explain them a little more in depth.

-----------------------------------------------------------------

## .reduce (basic use case)
This method reduces an array of values into a single one. The provided callback's first argument is the `accumulator`. The second argument is the `current value`. The main trick with `reduce` is that whatever the iterator returns from one iteration becomes the `accumulator` for the next. The final return value of `reduce` is whatever the `accumulator` has been built up to by the final iteration.

### What about the first iteration?
`reduce` has an optional, but highly recommended, second argument that sets the `initial value` for the `accumulator`. If no initial value is provided, `reduce` will essentially take the first value of the array, treat that as the `initial value` and the second value in the array as the `current value`. In general, you should just always provide an `initial value`, as it leads to fewer bugs.

```js
const numbers = [12,8,23,5];
const startingVal = 0;
const callbackFn = (accumulator, currentVal) => {
  console.log('Accumulator', accumulator);
  console.log('Value at index:', currentVal);
  // console.log('Current index:', idx);
  // console.log('Original array:', arr);
  return accumulator + currentVal;
}

const total = numbers.reduce(callbackFn, startingVal);
// Accumulator 0
// Value at index: 12
// Accumulator 12
// Value at index: 8
// Accumulator 20
// Value at index: 23
// Accumulator 43
// Value at index: 5
console.log('total', total);
// total: 48
```
--------------------------------------------------------
## .reduce (advanced use case)
At the end of the day, `reduce` just adds things up into an accumulator. But no one said the accumulator couldn't be...an object?? Look how you can use `reduce` to build up an object. For comparison, we do the exact same thing but using `.forEach`. The crucial thing to remember is now the initial value *must* be explicitly set an object. Also, we don't need them in this case, but the `idx` and `arr` parameters are still available.

```js
const arr = ['x', 'y', 'z', 'z', 'x', 'z'];
const countForEach = (arr) => {
  const result = {};
  arr.forEach((letter) => {
    result[letter] = (result[letter]) ? result[letter] + 1 : 1;
  });
  return result;
};

const countReduce = (arr) => arr.reduce((acc, letter) => {
  acc[letter] = acc[letter] ? acc[letter] + 1 : 1;
  return acc;
}, {});

console.log(countForEach(arr));
// { x: 2, y: 1, z: 3 }
console.log(countReduce(arr));
// { x: 2, y: 1, z: 3 }
```
-------------------------------------------------
## .sort
The default `sort()` method sorts things alphabetically. Which means `[1, 3, 2, 11]` would get sorted into `[1, 11, 2, 3]`.` This is not ideal. To sort numbers correctly, you need to pass in a compare callback function. The compare function needs to return a positive number, a negative number, or 0. JS will then use these numbers to determine if the values are in the right order. So kind of like this:

```js
const compare = (a, b) => {
  if (a is less than b by some ordering criterion) {
    return a negative number;
  }
  if (a is greater than b by the ordering criterion) {
    return a positive number;
  }
  // a must be equal to b
  return 0;
}
```
That's a very manual setup, which may be useful for sorting non-numeric values. However, if you're comparing numeric values, you can get away with a *drastically* simpler callback that still does the same thing:

```js
// sorts smallest to biggest (ascending)
let compare = (a, b) => a - b;
// sorts biggest to smallest (descending)
compare = (a, b) => b - a;
```

Used in context, `sort` looks like so.

```js
const numbers = [4, 2, 5, 1, 3];
numbers.sort((a, b) => a - b);
console.log('numbers:', numbers);
// [ 1, 2, 3, 4, 5 ]
```

The compare function can easily deal with objects as well, simply access whatever property to need.

```js
const houses = [
  {color: 'blue', price: 350000},
  {color: 'red', price: 470000},
  {color: 'pink', price: 280000},
];
houses.sort((a,b) => a.price - b.price)
console.log('houses:', houses);
// houses [
//   { color: 'pink', price: 280000 },
//   { color: 'blue', price: 350000 },
//   { color: 'red', price: 470000 }
// ]
```

Something important to note here is that unlike the other iterator functions on this list, sort is *not* pure; it will mutate the original array instead of making a new one.


## More higher order functions await!
This is just the base of the higher order mountain, there is so much more to explore about this concept. But, you should now have a pretty good grasp on the basics, and I encourage you to open up a console and play around with the values until it just feels second nature.

happy coding everyone, 

mike