Map

Now that you know how to add and remove elements from arrays, we can now explore some functions that help you save time! We will be covering the most common functions used in JavaScript.

Let's say you have an array of people's ages: const ages = [22, 28, 80, 48, 32]

Starting with this array, you want to get a new array of people's age 5 years from now. You could write a function yourself, but this is such a common use case that JavaScript arrays have a built in function that helps you do this quickly, called map.

const ages = [22, 28, 80, 48, 32]
const add5 = x => {
return x + 5
}
const fiveYearsLater = ages.map(add5) // fiveYearsLater is [27, 33, 85, 53, 37]

Map takes in a function, applies the function to every element in the array, and returns a new array of the same length. The input function is called for every element and the return value from your input function is put into the new array. Note that the original array isn't changed.

When map runs the input function, it will actually pass in 3 arguments: the current element in the array, the index of the current element, and the original array itself. Notice that our add5 function above only used the first parameter, and that's OK—functions don't have to have a parameter for every argument that might be given. But let's see what each call to add5 actually looked like:

const ages = [22, 28, 80, 48, 32]
const add5 = x => {
return x + 5
}
const fiveYearsLater = ages.map(add5)
/*
[ [
22, → add5( 22, 0, [22, 28, 80, 48, 32] ) → 27,
28, → add5( 28, 1, [22, 28, 80, 48, 32] ) → 33,
80, → add5( 80, 2, [22, 28, 80, 48, 32] ) → 85,
48, → add5( 48, 3, [22, 28, 80, 48, 32] ) → 53,
32 → add5( 32, 4, [22, 28, 80, 48, 32] ) → 37
] ]
*/

Let's see two examples of how we might use this extra information that map will pass in.

More Examples

// The input function is called with the element in the array
// as well as the index of the element. It is rare to use the third parameter.
const peppers = [5, 6, 6].map((element, index) => {
return element + index
}) // peppers will be [5,7,8] because [5+0,6+1,6+2]

Note that this is exactly what we asked you to implement in exercise 7 of the previous section. This makes it easier, doesn't it? See if you can figure out what's happening in the next example:

const melon = (delta, wax) => {
return delta + wax + 1
}
const peppers = [5, 6, 6].map(melon).map(melon) // what is peppers?
Answer
const melon = (delta, wax) => {
return delta + wax + 1
}
const peppers = [5, 6, 6].map(melon).map(melon)
/*
peppers is [7, 10, 12]
[5,6,6].map( melon ) returns [6, 8, 9]
5 + 0 + 1 is 6
6 + 1 + 1 is 8
6 + 2 + 1 is 9
[6,8,9].map( melon ) returns [7, 10, 12]
6 + 0 + 1 is 7
8 + 1 + 1 is 10
9 + 2 + 1 is 12
*/

Exercises

Do all of the following using the map function. You can also solve these problems without using map to compare and see the benefits that map brings!

  1. Write a function called oddToZero that copies an array while turning all odd elements to 0.

    oddToZero([1, 2, 3, 4, 5]) // returns [0,2,0,4,0]
Answer
  1. Tests

    describe('oddToZero function', () => {
    it('should zero when some elements are odd', () => {
    const result = fn.oddToZero([1, 2, 3, 4, 5])
    expect(result).toEqual([0, 2, 0, 4, 0])
    })
    it('should zero when all elements are odd', () => {
    const result = fn.oddToZero([1, 3])
    expect(result).toEqual([0, 0])
    })
    it('should return same array when no elements are odd', () => {
    const result = fn.oddToZero([8, 10, 12])
    expect(result).toEqual([8, 10, 12])
    })
    })
  2. Shape

    const oddToZero = arr => {}
  3. Explanation

    • You are given an array arr.
    • Use arr.map() function to go through each element of the array arr.
    • Inside the arr.map function:
      • Check to see if each element has a remainder.
      • If the remainder exists,
        • return 0 otherwise return the original element.
  4. Code

    const oddToZero = arr => {
    return arr.map(e => {
    if (e % 2) {
    return 0
    }
    return e
    })
    }
Debrief

Although we're doing similar things to what we did in the last section, the thinking is different here—instead of building an array piece-by-piece and returning it, we're now returning individual elements that map will use to build a new array.

Also note that unlike push, splice, and many others, arr.map doesn't modify arr. Just because a function belongs to an object doesn't mean it modifies that object.

  1. Write a function called firstLetters that returns the first letter of each string in an array of strings.

    firstLetters(['hello', 'my', 'name', 'is', 'pikachu'])
    // returns ["h", "m", "n", "i", "p"]
Answer
  1. Tests

    describe('firstLetters function', () => {
    it('should grab the first letters from 5 strings', () => {
    const result = fn.firstLetters(['hello', 'my', 'name', 'is', 'pikachu'])
    expect(result).toEqual(['h', 'm', 'n', 'i', 'p'])
    })
    it('should grab the first letters from 3 strings', () => {
    const result = fn.firstLetters(['JavaScript', 'is', 'awesome'])
    expect(result).toEqual(['J', 'i', 'a'])
    })
    it('should return an empty array when given an empty array', () => {
    const result = fn.firstLetters([])
    expect(result).toEqual([])
    })
    })
  2. Shape

    const firstLetters = arr => {}
  3. Explanation

    • You are given an array arr.
    • Use arr.map() function to go through each element of the array arr.
    • As you go through each element of arr,
      • return first letter of every element.
  4. Code

    const firstLetters = arr => {
    return arr.map(e => {
    return e[0]
    })
    }
  1. Change your firstXToZero function from the Non-Primitive section as follows:

    • Use map instead of recursion
    • Return a new array instead of mutating the original array
    firstXToZero([9, 1, 2, 2, 9], 3) // returns [0,0,0,2,9]
    firstXToZero([1, 2, 3, 4, 5], 2) // returns [0,0,3,4,5]
    firstXToZero([6, 7, 8], 3) // returns [0,0,0]
Answer
  1. Tests

    describe('firstXToZero function', () => {
    it('should change 3 numbers to 0', () => {
    const result = fn.firstXToZero([0, 5, 9, 6], 3)
    expect(result).toEqual([0, 0, 0, 6])
    })
    it('should not modify the array when asked to change 0 elements', () => {
    const result = fn.firstXToZero(["Don't", 'change', 'me'], 0)
    expect(result).toEqual(["Don't", 'change', 'me'])
    })
    it('should change all to zero when X beyond array length', () => {
    const result = fn.firstXToZero([1, 2, 3], 4)
    expect(result).toEqual([0, 0, 0])
    })
    })
  2. Shape

    const firstXToZero = (arr, x) => {}
  3. Explanation

    • You are given an array arr and a number x.
    • Use arr.map() function to go through each element of arr.
      • Inside map function, when index i is less than number x,
        • return 0.
      • otherwise, return the original element.
  4. Code

    const firstXToZero = (arr, x) => {
    return arr.map((e, i) => {
    if (i < x) {
    return 0
    }
    return e
    })
    }
  1. Write a function called nonPrimeToZero that copies an array while changing any number that isn't prime to 0.

    nonPrimeToZero([1, 2, 3, 4, 5]) // [0,2,3,0,5]
Answer
  1. Tests

    describe('nonPrimeToZero function', () => {
    it('should zero all numbers when non-prime', () => {
    const result = fn.nonPrimeToZero([-13, 0, 1, 4, 6])
    expect(result).toEqual([0, 0, 0, 0, 0])
    })
    it('should return an identical array if all are prime', () => {
    const result = fn.nonPrimeToZero([2, 17, 1601, 7919])
    expect(result).toEqual([2, 17, 1601, 7919])
    })
    it('should change only prime numbers to 0', () => {
    const result = fn.nonPrimeToZero([1, 2, 3, 4, 5])
    expect(result).toEqual([0, 2, 3, 0, 5])
    })
    })
  2. Shape

    const nonPrimeToZero = arr => {}
  3. Explanation

    • You are given an array arr.
    • You will need a helper function isPrime to see if each element in the arr is prime or not.
    • In your main function nonPrimeZero,
      • Use arr.map to run isPrime on each element,
        • If not prime, return 0.
    • otherwise return the original element.
  4. Code

    const isPrime = (num, i = 2) => {
    if (num < i) return false
    if (num === i) return true
    if (num % i === 0) return false
    return isPrime(num, i + 1)
    }
    const nonPrimeToZero = arr => {
    return arr.map(e => {
    if (!isPrime(e)) {
    return 0
    }
    return e
    })
    }
  1. Write a function called append that copies an array of strings while adding a string to every string in it.

    append(['hello', 'my', 'name', 'is', 'pikachu'], ' -log')
    // returns ["hello -log", "my -log", "name -log", "is -log", "pikachu -log"]
Answer
  1. Tests

    describe('append', () => {
    it('should append a string to 5 strings', () => {
    const result = fn.append(
    ['hello', 'my', 'name', 'is', 'pikachu'],
    ' -log'
    )
    expect(result).toEqual([
    'hello -log',
    'my -log',
    'name -log',
    'is -log',
    'pikachu -log'
    ])
    })
    it('should append a string to 2 strings', () => {
    const result = fn.append(['<img/>', '<p></p>'], '<hr/>')
    expect(result).toEqual(['<img/><hr/>', '<p></p><hr/>'])
    })
    it('should not modify the original array', () => {
    const arr = ['Spiderman', 'Peter Parker']
    fn.append(arr, 'Mary Jane')
    expect(arr).toEqual(['Spiderman', 'Peter Parker'])
    })
    })
  2. Shape

    const append = (arr, val) => {}
  3. Explanation

    • You are given an array arr and string val.
    • Use arr.map() function to go through each element of the array
      • In arr.map(),
        • return the sum of element plus number val.
  4. Code

    const append = (arr, val) => {
    return arr.map(e => {
    return e + val
    })
    }
  1. Change your runOnEach function from the last section to use map. You can use the same test cases.

    runOnEach(['hello', 'my', 'name', 'is', 'pikachu'], e => {
    return e.toUpperCase()
    })
    // returns ["HELLO", "MY", "NAME", "IS", "PIKACHU"]
    runOnEach([1, 32, 904, 2955], e => {
    return e % 2
    })
    // returns [1, 0, 0, 1]
Answer
  1. Shape

    const runOnEach = (arr, val) => {}
  2. Explanation

    • You are given an array arr and a callback function val.
    • Pass in the callback function val in arr.map().
    • return arr.map()
const runOnEach = (arr, val) => {
return arr.map(val)
}
  1. Write a function called clone that makes an exact duplicate of an array.
Hint

Note the following example will not work because it is returning the original array, not the new array.

const clone = arr => {
return arr
}
Answer
  1. Tests

    describe('clone function', () => {
    const farm1 = ['sheep', 'cow', 'pig']
    const farm2 = fn.clone(farm1)
    it('should clone an array of several elements', () => {
    expect(farm1).toEqual(farm2) // deep equality
    })
    it('should not return the same array', () => {
    expect(farm1 === farm2).toBeFalsy()
    })
    it('should clone an empty array', () => {
    expect(fn.clone([])).toEqual([])
    })
    })
  2. Shape

    const clone = arr => {}
  3. Explanation

    • You are given an array arr.
    • Use arr.map() to go through each element in the array.
    • Duplicate the element.
  4. Code

    const clone = arr => {
    return arr.map(e => {
    return e
    })
    }

Cloning an Array

In the last exercise, you had to write a function that returns a new array with the same elements as the input array, which means cloning the input array. If you examine the solution, you'll see that we cloned an array by using the map function:

const farm = ["sheep", "cow", "pig"]
const farm2 = farm.map( (e) => {
return e
})
} // farm2 is ["sheep", "cow", "pig"]

A quicker way of cloning an array is to use [...arr].

const farm = ['sheep', 'cow', 'pig']
const farm2 = [...farm]

You can also combine arrays while cloning them:

const farm = ['sheep', 'cow', 'pig']
const farm1 = ['horse', 'duck', 'llama']
const farm2 = [...farm, ...farm, ...farm1]
// farm2 is ["sheep", "cow", "pig", "sheep", "cow", "pig", "horse", "duck", "llama"]

forEach

forEach works exactly the same as map above. The only difference is that forEach runs its input function on each element and returns undefined rather than a new array. Just like with map, the input function will be given the current element, its index, and the original array, and we can use this information to write a function that modifies an array without returning anything:

const ages = [22, 28, 80, 48, 32]
const add5InPlace = (elem, idx, arr) => {
arr[idx] = arr[idx] + 5 // We could also put arr[idx] = elem + 5
// However, note that elem = elem + 5 will NOT work because elem is
// a parameter of this function and won't modify the array!
}
ages.forEach(add5InPlace)
// ages is now [27, 33, 85, 53, 37]

Note that we could have done this with map, but map by its name implies "mapping" elements of one array to a new array, and so developers use forEach in this case to make the intention of their code clear.

Exercise

Answer the questions in the comments:

const arr = []
const solution = data => {
arr.push(data)
}
// At this point, what is arr?
const arr2 = [1, 2, 3]
arr2.forEach(() => {
arr.push(arr2)
})
// At this point, what is arr?
arr2[2] = 'three'
// At this point, what is arr?
Answer
const arr = []
const solution = data => {
arr.push(data)
}
// []
arr2 = [1, 2, 3]
arr2.forEach(() => {
arr.push(arr2)
})
/*
[
[1,2,3],
[1,2,3],
[1,2,3]
]
*/
arr2[2] = 'three'
/*
[
[1,2,"three"],
[1,2,"three"],
[1,2,"three"]
]
*/

Filter

Let's say you have an array of people's ages: const ages = [22, 26, 80, 48, 32]

If you want to get a new array of all the ages under 27, you can use filter! Filter takes a function that runs on each element in the original array to decide whether to include it in the returned array. If the input function returns a truthy value, the element will be in the array. If the input function returns a falsey value, the element will be excluded from the array.

const ages = [22, 26, 80, 48, 32]
const youngerThanCardiB = ages.filter(e => {
return e < 27
}) // youngerThanCardiB is [22, 26]

Unlike map, filter returns an array that could be smaller than the original array.

Find

Let's say you have an array of people's ages: const ages = [22, 26, 80, 48, 32]

If you want to get an age that is greater than 77 from the array, use find!

const ages = [22, 26, 80, 48, 32]
const olderThanHarrisonFord = ages.find(e => {
return e > 77
}) // olderThanHarrisonFord is 80

find takes in an input function, and returns the first element which the function returns a truthy value for. If nothing is found, find returns undefined.

Exercises

  1. Use filter to write a function called noMoreEvens that copies an array, removing all even numbers.

    noMoreEvens([1, 2, 6, 4, 5]) // [1,5]
Answer
  1. Tests

    describe('noMoreEvens function', () => {
    it('should remove evens from an array with a mix of numbers', () => {
    const result = fn.noMoreEvens([1, 2, 6, 4, 5])
    expect(result).toEqual([1, 5])
    })
    it('should remove all numbers when even', () => {
    const result = fn.noMoreEvens([2, 16, 40, 52])
    expect(result).toEqual([])
    })
    it('should not touch an array of all odd numbers', () => {
    const result = fn.noMoreEvens([1, 571, 3, 9])
    expect(result).toEqual([1, 571, 3, 9])
    })
    it('should remove negative even numbers as well', () => {
    const result = fn.noMoreEvens([-2, -571, -4])
    expect(result).toEqual([-571])
    })
    })
  2. Shape

    const noMoreEvens = () => {}
  3. Explanation

    • You are given an array arr.
    • Use array helper function filter to remove the even numbers.
  4. Code

    const noMoreEvens = arr => {
    return arr.filter(e => {
    return e % 2
    })
    }
  1. Write a function that takes in an array of strings and removes the empty strings

    removeEmpty(['hello', 'world', '', 'name', '', 'is'])
    // returns ["hello", "world", "name", "is"]
Answer
  1. Tests

    describe('remove empty function', () => {
    it('should return [] when array is empty', () => {
    const result = fn.removeEmpty([])
    expect(result).toEqual([])
    })
    it('should return [] when array only has empty strings', () => {
    const result = fn.removeEmpty(['', '', ''])
    expect(result).toEqual([])
    })
    it('should return same array when array has no empty strings', () => {
    const result = fn.removeEmpty(['hello', 'world'])
    expect(result).toEqual(['hello', 'world'])
    })
    it('should return array without empty strings', () => {
    const result = fn.removeEmpty(['hello', 'world', '', 'name', '', 'is'])
    expect(result).toEqual(['hello', 'world', 'name', 'is'])
    })
    })
  2. Shape

    const removeEmpty = arr => {}
  3. Explain

    Filter out empty array by using the filter function

  4. Code

    const removeEmpty = arr => {
    return arr.filter(str => {
    return str.length
    })
    }
  1. Write a function called primesOnly that copies an array but keeps only prime numbers.

    primesOnly([1, 2, 3, 4, 5]) // [2,3,5]
Answer
  1. Tests

    describe('primesOnly function', () => {
    it('should return empty array if no primes', () => {
    const result = fn.primesOnly([-13, 0, 1, 4, 6])
    expect(result).toEqual([])
    })
    it('should return an identical array if all are prime', () => {
    const result = fn.primesOnly([2, 17, 1601, 7919])
    expect(result).toEqual([2, 17, 1601, 7919])
    })
    })
  2. Shape

    const primesOnly = num => {}
  3. Explanation

    • You are given an array arr.
    • You need helper function isPrime to see if each value in the array is prime or not.
    • User array helper function filter to filter prime numbers only.
  4. Code

    const isPrime = (num, i = 2) => {
    if (num < i) return false
    if (num === i) return true
    if (num % i === 0) return false
    return isPrime(num, i + 1)
    }
    const primesOnly = arr => {
    return arr.filter(e => {
    return isPrime(e)
    })
    }
Debrief

You might have tried the following code and found that it didn't work:

const primesOnly = arr => {
return arr.filter(isPrime)
}

Why not? Although isPrime looks like it fits perfectly here, filter actually passes not only the element but also its index and the array itself to the function it calls (just like map and forEach). These extra arguments get filled in as parameters in isPrime and break the recursion! So we have to use a "wrapper" function to make sure isPrime gets the correct number of arguments.

  1. Write a function called firstPrime that finds the first prime number in an array.

    firstPrime([1, 2, 3, 4, 5]) // 2
Answer
  1. Tests

    describe('firstPrime function', () => {
    it('should find a prime at the beginning of the array', () => {
    const result = fn.firstPrime([2, 17, 1601, 7919])
    expect(result).toEqual(2)
    })
    it('should find a prime at the end of the array', () => {
    const result = fn.firstPrime([1, 4, 16, 7919])
    expect(result).toEqual(7919)
    })
    it('should find no primes in an empty array', () => {
    expect(fn.firstPrime([])).toEqual(undefined)
    })
    })
  2. Shape

    const firstPrime = arr => {}
  3. Explanation

    • You are given an array arr.
    • You need helper function isPrime to see if the value of each index is prime or not.
    • Use array helper function arr.find() to located your first prime in the array arr and return the first prime number you find in the array.
  4. Code

    const isPrime = (num, i = 2) => {
    if (num < i) return false
    if (num === i) return true
    if (num % i === 0) return false
    return isPrime(num, i + 1)
    }
    const firstPrime = arr => {
    return arr.find(e => {
    return isPrime(e)
    })
    }

Reduce

Let's say you have an array of test scores for a team: const scores = [22, 28, 80, 48, 32]

From the array, you want to get the sum of all the scores of that team (plus 19 points extra credit). You could write a function yourself, but this is such a common use case that JavaScript arrays have a built-in function that helps you do this quickly, called reduce.

const scores = [22, 28, 80, 48, 32]
const addNumbers = scores.reduce((acc, e) => {
console.log(acc, e)
return acc + e
}, 19) // addNumbers is 229
/*
Logs for acc, e every time the input function is run:
19, 22
41, 28
69, 80
149, 48
197, 32
*/

The reduce function has 2 parameters, a function and a starting value.

If you look at the documentation for reduce, you'll see that it says the second parameter is optional. This is considered bad practice in many companies, so you should always provide a starting value when calling reduce.

When the input function is called, it will receive 4 parameters:

the return value from the previous input function call or starting value (for the very first call),

the current element in the array,

the current index of the element,

and the array itself.

We don't always use all 4 parameters. Here we're using 3 of them:

let winter = [5, 6, 6].reduce((acc, element, index) => {
return acc + element + index
}, 100)
// winter will be (100 + 5 + 0), then (105 + 6 + 1),
// then (112 + 6 + 2) = 120

Although the accumulated result can be a number, it can also be another array:

const cherries = [1, 2, 3]
const tomatoes = [5, 6, 7].reduce((farm, weight, tax) => {
if (weight > 5) farm.push(weight + tax)
return farm
}, cherries) // what is tomatoes?
Answer
const cherries = [1, 2, 3]
const tomatos = [5, 6, 7].reduce((farm, weight, tax) => {
if (weight > 5) farm.push(weight + tax)
return farm
}, cherries) // tomatoes is [1,2,3,7,9]

reduce is a very important function that is used frequently, so make sure you understand how it works before moving on.

Exercises

  1. Write a function called sum that adds up all the elements of an array.

    sum([9, 8, 6, 2, 3]) // returns 28
Answer
  1. Tests

    describe('sum function', () => {
    it('should return 0 for an empty array', () => {
    const result = fn.sum([])
    expect(result).toEqual(0)
    })
    it('should return negative for array of negative numbers', () => {
    const result = fn.sum([-2, -3])
    expect(result).toEqual(-5)
    })
    it('should add up array with negative and postive numbers', () => {
    const result = fn.sum([-20, -3, 20])
    expect(result).toEqual(-3)
    })
    })
  2. Shape

    const a = (sum = arr => {})
  3. Explanation

    • You are given an array arr.
    • Use array helper function arr.reduce() to add up all the elements in the array.
      • Inside the reduce function, pass accumulator acc and element e as arguments.
      • In arr.reduce() function, return the accumulator acc plus element e .
      • start with 0.
  4. Code

    const sum = arr => {
    return arr.reduce((acc, e) => {
    return acc + e
    }, 0)
    }
  1. Rewrite your largest function from the Non-Primitive section to use reduce. You can use the same tests.

    largestReduce([9, 8, 16, 2, 3]) // returns 16
Answer
  1. Tests

    describe('largest function', () => {
    it('should find the largest number in the array', () => {
    const result = fn.largest([9, 8, 16, 2, 3])
    expect(result).toEqual(16)
    })
    it('should return undefined since given array is empty', () => {
    const result = fn.largest([])
    expect(result).toEqual(undefined)
    })
    it('should return first index of array if all numbers are same', () => {
    const result = fn.largest([10, 10, 10, 10])
    expect(result).toEqual(10)
    })
    })
  2. Shapes

    const largestReduce = arr => {}
  3. Examples

    • You are given an array arr.
    • Use array helper function arr.reduce() to find the largest value.
      • Inside the reduce function, pass accumulator acc and element e as arguments.
      • In arr.reduce(), if element e is greater than the accumulator acc, return e.
      • return acc.
      • start with the first element of the array arr[0] .
  4. Code

    const largestReduce = arr => {
    return arr.reduce((acc, e) => {
    if (e > acc) {
    return e
    }
    return acc
    }, arr[0]) // start with the first element
    }
  1. Write a function called longest that returns the longest string out of an array of strings.

    longest(['Thor', 'Loki', 'Ant-Man', 'Rocket']) // returns "Ant-Man"
Answer
  1. Tests

    describe('longest function', () => {
    it('should find the longest string at the end of the array', () => {
    const result = fn.longest(['Thor', 'Loki', 'Rocket', 'Ant-Man'])
    expect(result).toEqual('Ant-Man')
    })
    it('should find the longest string in the middle of the array', () => {
    const result = fn.longest(['Thor', 'Spiderman', 'Ant-Man'])
    expect(result).toEqual('Spiderman')
    })
    it('should return string from array of length 1', () => {
    const result = fn.longest(['Wasp'])
    expect(result).toEqual('Wasp')
    })
    })
  2. Shape

    const longest = a => {}
  3. Explanation

    • You are given an array a.
    • Use array helper function a.reduce() to find the longest string in the array a.
      • Inside the reduce function, pass accumulator acc and element e as arguments.
      • In a.reduce() function,
        • when the length of e is greater than the length of accumulator,
          • return e
      • return acc
      • start with first index of the given array a[0].
  4. Code

    const longest = a => {
    return a.reduce((acc, e) => {
    if (e.length > acc.length) {
    return e
    }
    return acc
    }, a[0]) // start with the first length
    }
  1. Write a function called matches that counts how many times a given element occurs in an array.

    matches(['Thor', 'Loki', 'Ant-Man', 'Loki'], 'Loki') // returns 2
Answer
  1. Tests

    describe('matches function', () => {
    it('should match elements in various positions', () => {
    const result = fn.matches(['Thor', 'Loki', 'Ant-Man', 'Loki'], 'Loki')
    expect(result).toEqual(2)
    })
    it('should match concurrent elements', () => {
    const result = fn.matches(
    ['Spiderman', 'Spiderman', 'Mary Jane'],
    'Spiderman'
    )
    expect(result).toEqual(2)
    })
    it('should return 0 if no matches', () => {
    const result = fn.matches(['Thor', 'Loki', 'Ant-Man'], 'Wonder Woman')
    expect(result).toEqual(0)
    })
    it('should return 0 if for an empty array', () => {
    const result = fn.matches([], 'Thor')
    expect(result).toEqual(0)
    })
    })
  2. Shape

    const matches = (a, val) => {}
  3. Explanation

    • You are given an array a and a value val
    • Use array helper function a.reduce() to count how many times a given element occurs in the array a
      • Inside the reduce function, pass accumulator acc and element e as arguments.
      • When element e equals value val , return accumulator acc + 1
      • return accumulator acc
      • start with 0
  4. Code

    const matches = (a, val) => {
    return a.reduce((acc, e) => {
    if (e === val) {
    return acc + 1
    }
    return acc
    }, 0) // start with zero matches
    }
  1. Write a function called combineLess5 that takes in an array of strings, and returns a combined string of all strings with length < 5.

    combineLess5(['Thor', 'Loki', 'Ant-Man', 'Rocket', 'Wasp'])
    // returns "ThorLokiWasp"
Answer
  1. Tests

    describe('combineLess5 function', () => {
    it('should combine strings in various positions', () => {
    const arr = ['Thor', 'Loki', 'Ant-Man', 'Rocket', 'Wasp']
    const result = fn.combineLess5(arr)
    expect(result).toEqual('ThorLokiWasp')
    })
    it('should return one element with length < 5', () => {
    const arr = ['Spiderman', 'Loki', 'Ant-Man', 'Rocket']
    const result = fn.combineLess5(arr)
    expect(result).toEqual('Loki')
    })
    it('should return empty string if no matching elements', () => {
    const arr = ['Black Panther', 'Doctor Strange', 'Captain Marvel']
    const result = fn.combineLess5(arr)
    expect(result).toEqual('')
    })
    })
  2. Shape

    const combineLess5 = a => {}
  3. Explanation

    • You are given an array a.
    • Use array helper function a.reduce()
      • Inside the reduce function,
        • pass accumulator acc and element e as arguments.
        • When the length of e is greater than or equal to 5,
          • return acc
      • return acc + e
      • start with empty string ""
  4. Code

    const combineLess5 = a => {
    return a.reduce((acc, e) => {
    if (e.length >= 5) {
    return acc
    }
    return acc + e
    }, '') // start with empty string
    }
  1. Write a function called largerThan5 that takes in an array, and returns an array of numbers larger than 5.

    • Note: This is here to show the versatility of the reduce function. The filter function is better suited for this task, so try to do this with filter as well.
    largerThan5([5, 9, 2, 6, 5]) // returns [9,6]
Answer
  1. Tests

    describe('largerThan5 function', () => {
    it('should find numbers larger than 5 from various positions', () => {
    const result = fn.largerThan5([5, 9, 2, 6, 5])
    expect(result).toEqual([9, 6])
    })
    it('should find concurrent numbers larger than 5', () => {
    const result = fn.largerThan5([8, 8, 2, 3, 10])
    expect(result).toEqual([8, 8, 10])
    })
    it('should return empty array if no numbers larger than 5', () => {
    const result = fn.largerThan5([1, 2, 3, 4, 5])
    expect(result).toEqual([])
    })
    })
  2. Shape

    const largerThan5 = a => {}
  3. Explanation

    • You are given an array a.
    • Use array helper function a.reduce().
      • Inside the reduce function,
        • pass accumulator acc and element e as arguments.
        • When element e is greater than 5,
          • push element e into accumulator acc.
      • return acc.
      • start with an empty array.
  4. Code

    const largerThan5 = a => {
    return a.reduce((acc, e) => {
    if (e > 5) {
    acc.push(e)
    }
    return acc
    }, []) // start with an empty array
    }

Map / Reduce in Big Data

In big data, the concept of map / reduce is used a lot.

Let's say you are Facebook and you want to count what is the most frequent first name.

Facebook has 2.5 billion users. If a computer could look up 100 users per second, then it would take about 10 months to go through all the users. Can you imagine waiting 10 months for your program to run? This is unacceptable.

The simple way to solve this problem is to split 2.5 billion into 10,000 groups of 250,000 users each.

Map: Facebook has 10,000 computers at its disposal, and each computer will run through 250,000 users to figure out the most common first name and how many times it shows up. You will get 10,000 first names and one result (name and count) from each computer. An example result from a computer could look like this: ["joe", 20000]. Assuming a computer can process 100 users per second, it will only take 42 minutes to get these 10,000 names!

Reduce: Now that you have 10,000 first names, you can go through these 10,000 first names and count to find the most common first name. Assuming a computer process 100 users per second, this will only take 2 minutes!

Using map/reduce we have reduced our calculation time from 10 months to 44 minutes!

Prototype Inheritance

Now that you know a few array functions:

  • Add / remove: push, pop, shift, unshift
  • Helpers: map, filter, find, reduce

What if you wanted to add new functions to arrays? For example:

  • [9,8,6,1].last() to return the last element of the array
  • [1,2,3,4,5].evens() to return an array of even numbers

Here are the steps to add your own customized function for arrays:

  1. Define your function using function( ... parameters ...) { ... code ...}.
    • The difference between function and () => {} is described thoroughly in the next lesson.
  2. Assign your function to Array.prototype.
  3. Access array properties using the this keyword.
    • Note that this is a system keyword. Do not name your variables this!

When the function is running, this refers to the object that comes before ..

Array.prototype.last = function () {
return this[this.length - 1]
}
;[1, 2, 3].last() // When the last function is run, 'this' refers to [1,2,3]
const jackfruit = [1, 2, 3].last() // What is jackfruit?
Answer
Array.prototype.last = function () {
return this[this.length - 1]
}
;[1, 2, 3].last() // When the last function is run, this refers to [1,2,3]
jackfruit = [1, 2, 3].last() // 3
Array.prototype.papaya = function (i = 0, z = 0) {
if (i === this.length) return z
return this.papaya(i + 1, this[i] + z)
}
const juicy = [7, 8, 2].papaya // what is juicy?
const juicy2 = [7, 8, 2].papaya() // what is juicy2?
Answer
Array.prototype.papaya = function (i = 0, z = 0) {
if (i === this.length) return z
return this.papaya(i + 1, this[i] + z)
}
const juicy = [7, 8, 2].papaya // function
const juicy2 = [7, 8, 2].papaya() // 17

The examples above shows how you can add new functions into arrays. This approach is called prototype inheritance. But why?

  • Prototype - Because you are assigning your function to Array.prototype. More details covered in the next lesson.

  • Inheritance - Because in the following example, all existing arrays inherit the new function.

    const a = [9, 8, 7]
    Array.prototype.greet = function () {
    return this[0]
    }
    a.greet() // returns 9
    /*
    Even though Array.prototype.greet is executed after a is defined,
    a still has the function greet.
    a inherited the function!
    */

Exercises

  1. Create a prototype function called getEvens that returns a new array of all the even numbers in an array of numbers.

    const arr = [9, 80, 12, 2]
    arr.getEvens() // returns [80, 12, 2]
Answer
  1. Tests

    describe('getEvens function', () => {
    it('should pull even numbers from various positions', () => {
    const result = [9, 80, 11, 2].getEvens()
    expect(result).toEqual([80, 2])
    })
    it('should pull even numbers from concurrent positions', () => {
    const result = [2, 4, 6, 7, 8].getEvens()
    expect(result).toEqual([2, 4, 6, 8])
    })
    it('should have no result if no evens', () => {
    const result = [1, 3, 9, 21].getEvens()
    expect(result).toEqual([])
    })
    })
  2. Shape

    Array.prototype.getEvens = function () {}
  3. Explanation

    • Use array helper function filter to filter out the even numbers in the given array this.
    • Return the helper filer function by using this.filter().
  4. Code

    Array.prototype.getEvens = function () {
    return this.filter(e => {
    return e % 2 == 0
    })
    }
  1. Create a prototype function called sum that adds all the elements of an array together.

    const arr = [9, 80, 12, 2]
    arr.sum() // returns 103
Hint
If you don't give reduce a starting value, it will start with the first element of the array—but the array can't be empty!
Answer
  1. Tests

    describe('sum function', () => {
    it('should find sum of an array of numbers', () => {
    const result = [2, 17, 3, -3].sum()
    expect(result).toEqual(19)
    })
    it('should add strings together', () => {
    const data = ['<p>', "<img src='https://placebear.com/800/710'>", '</p>']
    const result = data.sum()
    expect(result).toEqual(
    "<p><img src='https://placebear.com/800/710'></p>"
    )
    })
    it('should return undefined for an empty array', () => {
    const result = [].sum()
    expect(result).toEqual(undefined)
    })
    })
  2. Shape

    Array, (prototype.sum = function () {})
  3. Explanation

    • When the length of array this equals zero,
      • return undefined
    • Use array helper function reduce to calculate all the sum in the given array this.
    • Depending on whether the first element is a string or a number, we either start with '' or 0. To check what type of variable it is, we use typeof function.
    • Return this.reduce().
  4. Code

    Array.prototype.sum = function () {
    if (this.length === 0) {
    return undefined
    }
    let startingValue = ''
    if (typeof this[0] === 'number') {
    startingValue = 0
    }
    return this.reduce((acc, e) => {
    return acc + e
    }, startingValue)
    }
  1. Create a prototype function called pad that adds a given string to an array a certain number of times.

    const arr = ["<button name='submit'></button>", '<div></div>']
    arr.pad(2, '<br/>')
    // arr is ["<button name='submit'></button>", "<div></div>", "<br/>", "<br/>"]
Answer
  1. Tests

    describe('pad function', () => {
    it('should modify the original array', () => {
    const arr = ['Doctor']
    arr.pad(1, 'Strange')
    expect(arr).toEqual(['Doctor', 'Strange'])
    })
    it('should pad multiple times', () => {
    const arr = ["<button name='submit'></button>", '<div></div>']
    arr.pad(2, '<br/>')
    expect(arr).toEqual([
    "<button name='submit'></button>",
    '<div></div>',
    '<br/>',
    '<br/>'
    ])
    })
    it('should return same array when given negative pad number', () => {
    const result = ['Quill', 'Gamora'].pad(-2, 'Drax')
    expect(result).toEqual(['Quill', 'Gamora'])
    })
    it('should return same array when given zero pad number', () => {
    const result = ['Quill', 'Gamora'].pad(0, 'Drax')
    expect(result).toEqual(['Quill', 'Gamora'])
    })
    })
  2. Shape

    Array.prototype.pad = function (num, str) {}
  3. Explanation

    • You are given number num and a string str.
    • When number num is less than or equal to 0,
      • return the array this.
    • Push string str into array this.
    • Continue.
  4. Code

    Array.prototype.pad = function (num, str) {
    if (num <= 0) {
    return this
    }
    this.push(str)
    return this.pad(num - 1, str)
    }
  1. Create a prototype function called fizzbuzz that changes the original array. All numbers divisible by 3 will be converted to "fizz", all numbers divisible by 5 will be converted to "buzz", and all numbers divisible by both 3 and 5 will be converted to "fizzbuzz".

    const arr = [9, 80, 12, 2, 30]
    arr.fizzbuzz()
    // arr is ["fizz", "buzz", "fizz", 2, "fizzbuzz"]
Answer
  1. Tests

    describe('fizzbuzz function', () => {
    it('should change numbers divisible by 3 to fizz', () => {
    const magicNumbers = [1, 2, 3, 6, 19, 18]
    magicNumbers.fizzbuzz()
    expect(magicNumbers).toEqual([1, 2, 'fizz', 'fizz', 19, 'fizz'])
    })
    it('should change numbers divisible by 5 to buzz', () => {
    const magicNumbers = [1, 2, 5, 10, 11]
    magicNumbers.fizzbuzz()
    expect(magicNumbers).toEqual([1, 2, 'buzz', 'buzz', 11])
    })
    it('should change numbers divisible by 15 to fizzbuzz', () => {
    const magicNumbers = [1, 2, 4, 15, 16, 30]
    magicNumbers.fizzbuzz()
    expect(magicNumbers).toEqual([1, 2, 4, 'fizzbuzz', 16, 'fizzbuzz'])
    })
    it('should correctly change 3 to fizz, 5 to buzz, and 15 to fizzbuzz', () => {
    const magicNumbers = [9, 80, 12, 2, 30]
    magicNumbers.fizzbuzz()
    expect(magicNumbers).toEqual(['fizz', 'buzz', 'fizz', 2, 'fizzbuzz'])
    })
    })
  2. Shape

    Array.prototype.fizzbuzz = function () {}
  3. Explanation

    • You are given an array this.
    • We will need another variable to track index of array this (let's say i) which starts at 0.
    • When index i equals the length of array this,
      • return array this.
    • When array this at index i is divisible by both numbers 5 and 3,
      • return array this at index i equals "fizzbuzz".
    • When array this at index i is divisible by 3,
      • return this at index i equals "fizz".
    • When array this at index i is divisible by 5,
      • return array this at index i equals "buzz".
    • Continue.
  4. Code

    Array.prototype.fizzbuzz = function (i = 0) {
    if (i === this.length) {
    return this
    }
    if (this[i] % 5 === 0 && this[i] % 3 === 0) {
    this[i] = 'fizzbuzz'
    }
    if (this[i] % 3 === 0) {
    this[i] = 'fizz'
    }
    if (this[i] % 5 === 0) {
    this[i] = 'buzz'
    }
    return this.fizzbuzz(i + 1)
    }
    /* (OPTIONAL) Below is a wrong implementation.
    * Let's say a coworker walks up to you and
    * tells you that there's a bug with his code...
    * It doesn't work with 15
    * Could you spot it?
    Array.prototype.fizzbuzz = function(i=0) {
    if (i===this.length) {
    return this
    }
    if (this[i] % 3 === 0) {
    this[i] = "fizz"
    }
    if (this[i] % 5 === 0) {
    this[i] = "buzz"
    }
    if (this[i] % 5 === 0 && this[i] % 3 === 0) {
    this[i] = "fizzbuzz"
    }
    return this.fizzbuzz(i + 1)
    }
    */
    /*
    Answer
    Because 15 is divisible by 3
    so this[i] is now "fizz"
    "fizz" is not divisible by anything, so none of the following ifs match
    */
  1. Create a prototype function called removeEvens, which removes all the even numbers from the array.

    const arr = [9, 80, 12, 2]
    arr.removeEvens()
    // arr becomes [9]
Answer
  1. Tests

    describe('removeEvens function', () => {
    it('should remove even numbers from various positions', () => {
    const arr = [9, 80, 11, 2]
    arr.removeEvens()
    expect(arr).toEqual([9, 11])
    })
    it('should remove even numbers from concurrent positions', () => {
    const arr = [2, 4, 6, 7, 8]
    arr.removeEvens()
    expect(arr).toEqual([7])
    })
    it('should leave array the same if no evens', () => {
    const arr = [1, 3, 9, 21]
    arr.removeEvens()
    expect(arr).toEqual([1, 3, 9, 21])
    })
    it('should leave empty array the same', () => {
    const arr = []
    arr.removeEvens()
    expect(arr).toEqual([])
    })
    })
  2. Shape

    Array.prototype.removeEvens = function () {}
  3. Explanation

    • You are given an array this.
    • We also need a variable to track index in array this(let's say i) and it starts at 0.
    • When index i equals the length of array this ,
      • return array this.
    • When array this at index i is even,
      • Use array helper function splice to remove that index value at array this at index i.
      • return this.removeEvens() to update the given array with index i as an argument.
    • Continue.
  4. Code

    Array.prototype.removeEvens = function (i = 0) {
    if (i === this.length) {
    return this
    }
    if (this[i] % 2 === 0) {
    this.splice(i, 1)
    return this.removeEvens(i)
    }
    return this.removeEvens(i + 1)
    }
Debrief
Did your function skip an element right after removing one? When we modify an array with a function like splice, the index that pointed at the next element will now be one position too far ahead. That's why the solution calls `removeEvens(i)` after removing an element, but `removeEvens(i+1)` if nothing was removed.
  1. Create a prototype function called getIterator that returns a function. When the returned function is called, it returns the next element of the array.
Hint
Think back to the last lesson and you'll remember that whatever variable you're using to iterate (an index like i, for example) has to be defined outside of the iterate function. You've learned two ways to do this: as a default parameter, or using closure.
Answer
  1. Tests

    describe('getIterator function', () => {
    it('should iterate through 3 elements', () => {
    const iterate = ['PayPal', 'Google', 'Netflix'].getIterator()
    expect(iterate()).toEqual('PayPal')
    expect(iterate()).toEqual('Google')
    expect(iterate()).toEqual('Netflix')
    })
    it('should return to beginning once done', () => {
    const iterate = [9, 80, 12, 2].getIterator()
    expect(iterate()).toEqual(9)
    expect(iterate()).toEqual(80)
    expect(iterate()).toEqual(12)
    expect(iterate()).toEqual(2)
    expect(iterate()).toEqual(9)
    expect(iterate()).toEqual(80)
    })
    it('should return undefined for empty array iterator', () => {
    const iterate = [].getIterator()
    expect(iterate()).toEqual(undefined)
    })
    it('should iterate through one element', () => {
    const iterate = ['Ironman'].getIterator()
    expect(iterate()).toEqual('Ironman')
    expect(iterate()).toEqual('Ironman')
    })
    })
  2. Shape

    Array.prototype.getIterator = function () {}
  3. Explanation

    • We need a local variable (let's say i) that starts at -1.
    • When the function is originally called, it returns another function
    • When the returned function is called,
      • i is incremented by 1.
      • Continue going through the array this
  4. Code

    Array.prototype.getIterator = function () {
    let i = -1
    return () => {
    i = i + 1
    return this[i % this.length]
    }
    }

Async

In the previous chapter you learned about the asynchronous nature of JavaScript. Here are two more examples to work through to tie this asynchronous functionality into what we've just learned about arrays and their helper functions.

const peppers = [5, 6, 6].map((element, index) => {
setTimeout(() => {
console.log(element)
return 100
}, 1000)
}) // what is peppers?
// Describe what is logged to the console.
// Which values are logged and when are they logged?
Answer
const peppers = [5, 6, 6].map((element, index) => {
setTimeout(() => {
console.log(element)
return 100
}, 1000)
}) // [undefined, undefined, undefined]
// After 1 second, all 3 logs will be printed out at once
// 5
// 6
// 6

Why is peppers [undefined, undefined, undefined] ?

Why do all 3 logs get printed out at once?

Because map runs the functions really fast and returns immediately. In a race, if all runners started immediately one after another, they would arrive at the destination at around the same time.

const friends = ['Tony Stark', 'Vision', 'Ultron'].reduce((a, b) => {
setTimeout(() => {
console.log('Greetings,', b)
}, 2000)
return a + b
}, '') // What is friends?
// What gets printed out into the console?
Answer
const friends = ['Tony Stark', 'Vision', 'Ultron'].reduce((a, b) => {
setTimeout(() => {
console.log('Greetings,', b)
}, 2000)
return a + b
}, '') // "Tony StarkVisionUltron"
// After 2 seconds, all 3 logs will be printed out at once:
// Greetings, Tony Stark
// Greetings, Vision
// Greetings, Ultron

All your solutions must be solved recursively. You're not allowed to use higher-order functions like forEach, reduce, filter, find, or map. This is because you will be implementing these functions. Once you know how to implement these functions, then you can freely use them in future lessons.

Master your skill by solving challenges

Complete the rest of JS2 challenges

Edit this page on Github