Arrays

We've touched on arrays a few times in the previous chapters. It's time to explore them in more depth.

What is an Array?

An array is an ordered list of elements; each element has a value of any type. You can define an array by placing a list of values between brackets ([]):

> let myArray = [2, 'Pete', 2.99, 'another string']

This example demonstrates that arrays are heterogeneous; myArray has both number and string values. Arrays can have anything in them, including objects and even other arrays.

Each element in an array has a unique index number that gives the position of the element in the array. Thus, we can say that arrays are indexed lists as well as ordered lists.

You can refer to any element of an array by its index number. The syntax puts the index number between brackets ([]) after the array's name or value. For instance, we can get the first element of myArray this way:

> myArray[0]
= 2

The first element of an array is always at index 0. Thus, the second element is at index 1, the third at index 2, and so on. To access the third element of an array, we can write:

> myArray[2]
= 2.99

If you know that myArray has 4 elements, it's easy to see that myArray[3] returns the last element. Suppose you don't know how many elements the array contains. How can you access the last element? It's not hard, but it is a bit tricky. The array's length property does the job:

> myArray[myArray.length - 1]
= 'another string'

Note that we must use the array's name twice, and, since we must account for the element at index 0, we must subtract 1 from the length to get the last element's index. Zero-based indexing seems strange at first, and it makes the arithmetic a little tricky, but, once you get used to it—it takes practice—it soon becomes second nature.

Modifying Arrays

Arrays wouldn't be practical if we couldn't modify them. We need the ability to add, remove, and replace elements. The [] operator and some array methods help us accomplish these tasks.

Replacing and Adding Elements With []

To replace an element of an array, use brackets ([]) with the assignment operator:

> let array = [1, 2, 3]
> array[1] = 4
= 4

> array
= [ 1, 4, 3 ]

You can also use [] to add new elements to an array:

> array[array.length] = 10
= 10

> array
= [ 1, 4, 3, 10 ]

Note that array[array.length] = value adds a new value to the end of array since the previous ending element was at array[array.length - 1].

Note that variables declared with const and initialized to an array are a little strange; while you can't change what array the variable refers to, you can modify the array's contents:

> const MyArray = [1, 2, 3]
> MyArray[1] = 5
> MyArray
= [1, 5, 3]

> MyArray = [4, 5, 6] // Uncaught TypeError: Assignment to constant variable.

This type of behavior can be confusing. It stems from the "variables as pointers" concept that we discuss a little later. Essentially, a const declaration prohibits changing what thing the const points to, but it does not prohibit changing the content of that thing. Thus, we can change an element in a const array, but we can't change which array the const points to.

If you want the elements of the array to also be const, you can use the Object.freeze method:

> const MyArray = Object.freeze([1, 2, 3])
> MyArray[1] = 5
> MyArray
= [1, 2, 3]

Notice how the assignment on line 2 had no effect on the content of the array.

It's important to realize that Object.freeze only works one level deep in the array. If your array contains nested arrays or other objects, the values inside them can still be changed unless they are also frozen:

> const MyArray = Object.freeze([1, 2, 3, [4, 5, 6]])
> MyArray[3][1] = 0
> MyArray
= [1, 2, 3, [4, 0, 6]]

If you want the sub-array to be constant as well, you have to freeze it:

> const MyArray = Object.freeze([1, 2, 3, Object.freeze([4, 5, 6])])
> MyArray[3][1] = 0
> MyArray
= [1, 2, 3, [4, 5, 6]]

Adding Elements With push

The push method adds one or more elements to the end of an array:

> array.push('a')
= 5               // the new length of the array

> array
= [ 1, 4, 3, 10, 'a' ]

> array.push(null, 'xyz')
= 7

> array
= [ 1, 4, 3, 10, 'a', null, 'xyz' ]

The push method appends its arguments to the caller (the array), which mutates the caller. It then returns the array's new length. Don't forget that methods and functions perform actions and return values. You must be careful to distinguish between these two things. push appends elements to the end of the caller array, but it returns the array's updated length. Note that it does not return the modified array! New JavaScript programmers often get confused over this difference and spend hours puzzling over why a function isn't returning the value they expect. Check the documentation if you have any doubt.

Adding Elements With concat

The concat method is similar to push, but it doesn't mutate the caller. It concatenates two arrays and returns a brand new array that contains all the elements from the original array followed by all of the arguments passed to it:

> array.concat(42, 'abc')
= [ 1, 4, 3, 10, 'a', null, 'xyz', 42, 'abc' ]

> array
= [ 1, 4, 3, 10, 'a', null, 'xyz' ]

How do you know which methods mutate the caller and which ones don't? Most of the time, the documentation has this information; documentation for non-mutating methods may mention that they return a new array. However, don't count on it. The way to be sure is to use the method and examine the results.

Removing Elements With pop

The inverse of push is pop. While push adds an element to the end of the array, pop removes and returns the last element of the array:

> array.pop()
= 'xyz'            // removed element value

> array
= [ 1, 4, 3, 10, 'a', null ]

pop mutates the caller.

Removing Elements With splice

The splice method lets you remove one or more elements from an array, even those that aren't at the end of the array:

let array = [1, 4, 3, 10, "a", null];

> array.splice(3, 2)
[ 10, 'a' ]

> array
= [ 1, 4, 3, null ]

In this example, we delete 2 elements starting at index position 3. splice mutates the original array and returns a new array that contains the deleted elements.

splice can also add and insert elements, but we'll leave that for our JavaScript courses.

Iteration Methods

Iterating With forEach

JavaScript has a variety of built-in methods that iterate over the contents of an array. In the Loops and Iterating chapter, we introduced the forEach method. It provides a simple way to iterate over arrays; style guides often recommend it in favor of a for loop.

To use forEach, you need a callback function that you pass to forEach as an argument. A callback function is a function that you pass to another function as an argument. The called function invokes the callback function when it runs. The forEach method invokes its callback once for each element, passing it the element's value as an argument. forEach always returns undefined.

A callback is a function that you pass to another function as an argument. The called function subsequently invokes the callback function at certain times while it runs.

let array = [1, 2, 3];
array.forEach(function(num) {
  console.log(num); // on first iteration  => 1
                    // on second iteration => 2
                    // on third iteration  => 3
}); // returns `undefined`

This code invokes the callback function once for each element in the array. forEach, during each iteration, invokes the callback with the element's value as an argument. The callback then logs it to the console. In the end, forEach returns undefined.

We can also use an arrow function instead of a function expression, which makes our code compact and, when you're familiar with the syntax, more readable.

let array = [1, 2, 3];
array.forEach(num => console.log(num));

We can also perform more complex operations:

let array = [1, 2, 3];
array.forEach(num => console.log(num + 2)); // on first iteration  => 3
                                            // on second iteration => 4
                                            // on third iteration  => 5

Transforming Arrays With map

forEach works well when you want to use the values of an array's elements. Suppose, though, that you want to create a new array whose values depend on the original contents of the array. For instance, suppose you want to create a new array that contains the squares of all the numbers in the calling array. With forEach, you might end up with something like this:

> let numbers = [1, 2, 3, 4]
> let squares = [];
> numbers.forEach(num => squares.push(num * num));
> squares
= [ 1, 4, 9, 16 ]

> numbers
= [ 1, 2, 3, 4 ]

That works well enough, but the callback now has a side effect: it modifies the squares variable, which isn't part of the callback function. Side effects sometimes lead to trouble. Consider what happens if you ran the forEach call again after running the above code:

> numbers.forEach(num => squares.push(num * num));
> squares
= [ 1, 4, 9, 16, 1, 4, 9, 16 ]

We now have two copies of the squares since we forgot to reset squares to an empty array.

The map method handles this situation more cleanly:

> let numbers = [1, 2, 3, 4]
> let squares = numbers.map(num => num * num);
> squares
= [ 1, 4, 9, 16 ]

> squares = numbers.map(num => num * num);
= [ 1, 4, 9, 16 ]

The first 4 lines of this code have the same result as the previous example using forEach. However, map returns a new array that contains one element for each element in numbers, with each element set to the return value of the callback: the squares of the numbers in this case. This code is more compact than the forEach code, and, better yet, it has no side effects; the callback doesn't update squares (the return value of map does that), and we don't have to reset the variable if we rerun the same code.

forEach and map are important methods, but they can confuse beginners. The main thing to remember is that forEach performs simple iteration and returns undefined, while map transforms an array's elements and returns a new array with the transformed values.

Filtering Arrays with filter

The filter method is another array iteration method. It returns a new array that includes all elements from the calling array for which the callback returns a truthy value. That's a mouthful. Some code should help clarify what filter does:

> let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2]
> numbers.filter(num => num > 4)
= [ 5, 6, 7, 8, 9, 10 ]

> numbers
= [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2 ]

filter iterates over the elements of the array. During each iteration, it invokes the callback function, using the value of the current element as an argument. If the callback returns a truthy value, filter appends the element's value to a new array. Otherwise, it ignores the element's value and does nothing. When filter finishes iterating, it returns the array of selected elements: the elements for which the callback returned a truthy value. In our example, filter selects all of the elements with a value greater than 4.

filter doesn't mutate the caller.

Building New Values from Arrays with reduce

The reduce method effectively reduces the contents of an array to a single value. It is, perhaps, one of the hardest array iteration methods to understand, but it is also one of the most fundamental. You can build forEach, map, and filter with reduce, as well as a number of other iterative methods defined for Arrays.

reduce takes two arguments: a callback function and a value that initializes something called the accumulator. In its simplest form, the callback function takes two arguments: the current value of the accumulator and an element from the array. It returns a value that will be used as the accumulator in the next invocation of the callback. That sounds more complicated than it is, so let's take a look at two simple uses of reduce:

> let arr = [2, 3, 5, 7]
> arr.reduce((accumulator, element) => accumulator + element, 0)
= 17

> arr.reduce((accumulator, element) => accumulator * element, 1)
= 210

The first invocation computes the sum of all the values in the array, e.g., 2 + 3 + 5 + 7. To get us started, we initialize the accumulator to 0. Thus, on the first invocation of the callback function, accumulator is 0 and element is 2. The callback returns 2, which becomes the new accumulator value when we invoke the callback again, this time with the element 3. That invocation, in turn, returns 5. This process continues until the final return value is 17.

The second invocation of reduce computes the product of the numbers in the array (2 * 3 * 5 * 7), starting out with 1 as the accumulator. (If we started with 0 as the accumulator, the final return value would be 0 since 0 times anything is 0.)

The reduce function isn't limited to computing numbers: you can also use it to compute strings, objects, arrays: anything. Here's an example with strings:

> let strings = ['a', 'b', 'c', 'd']
> strings.reduce((accumulator, element) => {
...   return accumulator + element.toUpperCase()
... }, '');
= 'ABCD'

reduce does not mutate the caller. (It is possible that the callback might mutate the caller, but that's inadvisable, and not reduce's fault.)

Arrays Can Be Odd

JavaScript arrays have some odd properties that might catch you by surprise. If you've worked with arrays in other languages, the oddities may be especially surprising.

  • You've already met one oddity: indexes start at 0. Most programming languages use zero-based indexes, so it's not too odd. However, learners often stumble on this aspect of using arrays.

  • The length property always returns a number that is one greater than the greatest used index position of the array. For instance, if an element exists at index position 124 and there are no other elements with greater index values, then the array's length is 125.

  • Arrays are objects, which we'll meet in the next chapter. One side effect of this is that the typeof operator doesn't return 'array' when applied to an array:

    > let arr = [1, 2, 3]
    > typeof arr
    = 'object'
    

    If you really need to detect whether a variable references an array, you need to use Array.isArray instead:

    > let arr = [1, 2, 3]
    > Array.isArray(arr)
    = true
    
  • If you change an array's length property to a new, smaller value, the array gets truncated; JavaScript removes all elements beyond the new final element.

  • If you change an array's length property to a new, larger value, the array expands to the new size. The new elements do not get initialized, which leads to some strange behavior:

    > let arr = []
    > arr.length = 3
    > arr
    = [ <3 empty items> ]
    
    > arr[0]
    = undefined
    
    > arr.filter(element => element === undefined)
    = []
    
    > arr.forEach(element => console.log(element)) // no output
    = undefined
    
    > arr[1] = 3
    > arr
    = [ <1 empty item>, 3, <1 empty item> ]
    
    > arr.length
    = 3
    
    > arr.forEach(element => console.log(element))
    = 3
    = undefined
    
    > Object.keys(arr)
    = ['1']
    

    In general, JavaScript treats unset array elements as missing, but the length property includes the unset elements.

  • You can create array "elements" with indexes that use negative or non-integer values, or even non-numeric values:

    > arr = [1, 2, 3]
    = [ 1, 2, 3 ]
    
    > arr[-3] = 4
    = 4
    
    > arr
    = [ 1, 2, 3, '-3': 4 ]
    
    > arr[3.1415] = 'pi'
    = 'pi'
    
    > arr["cat"] = 'Fluffy'
    = 'Fluffy'
    
    > arr
    = [ 1, 2, 3, '-3': 4, '3.1415': 'pi', cat: 'Fluffy' ]
    

    These "elements" aren't true elements; they are properties on the array object, which we'll discuss later. Only index values (0, 1, 2, 3, and so on) count toward the length of the array.

    > arr.length
    = 3
    
  • Since arrays are objects, you can use the Object.keys method to return an array's keys -- its index values -- as an array of strings. Even negative, non-integer, and non-numeric indexes are included.

    > arr = [1, 2, 3]
    > arr[-3] = 4
    > arr[3.1415] = 'pi'
    > arr["cat"] = 'Fluffy'
    > arr
    = [ 1, 2, 3, '-3': 4, '3.1415': 'pi', cat: 'Fluffy' ]
    
    > Object.keys(arr)
    = [ '0', '1', '2', '-3', '3.1415', 'cat' ]
    

    One quirk of this method is that it treats unset values in arrays differently from those that merely have a value of undefined. Unset values are created when there are "gaps" in the array; they show up as empty items until you try to use their value:

    > let a = new Array(3);
    > a
    = [ <3 empty items> ]
    
    > a[0] === undefined;
    = true
    
    > let b = [];
    > b.length = 3;
    > b
    = [ <3 empty items> ]
    
    > b[0] === undefined;
    = true
    
    > let c = [undefined, undefined, undefined]
    > c
    = [ undefined, undefined, undefined ]
    
    > c[0] === undefined;
    = true
    

    While the length property of Array includes unset values in the count, Object.keys only counts those values that have been set to some value:

    > let aKeys = Object.keys(a)
    > a.length
    = 3
    > aKeys.length;
    = 0
    
    > let bKeys = Object.keys(b)
    > b.length
    = 3
    > bKeys.length;
    = 0
    
    > let cKeys = Object.keys(c)
    > c.length
    = 3
    > cKeys.length;
    = 3
    

It's worth spending some time and effort to memorize these behaviors.

Nested Arrays

Array elements can contain anything, including other arrays. You can create arrays with arrays inside them and even arrays inside those inner arrays. Suppose you want to track all of the teams playing in a mixed doubles tennis tournament. You might create an array like this.

> let teams = [['Joe', 'Jennifer'], ['Frank', 'Molly'], ['Dan', 'Sarah']]

You can now find the teams by index.

> teams[2]
= [ 'Dan', 'Sarah' ]

If you want to retrieve the second element of teams[2], you can append [1] to the expression:

> teams[2][1]
= 'Sarah'

Array Equality

What do you think the following expression returns?

> [1, 2, 3] === [1, 2, 3]

A reasonable answer is that it returns true. After all, the arrays look identical. JavaScript, however, isn't reasonable in this case: the expression returns false.

> [1, 2, 3] === [1, 2, 3]
= false

How about the following comparison?

> let a = [1, 2, 3]
> let b = a
> a === b

Curiously, this comparison evaluates as true. What's happening here?

JavaScript treats two arrays as equal only when they are the same array: they must occupy the same spot in memory. This rule holds for JavaScript objects in general; objects must be the same object. For this reason, the second example returns true while the first one returns false. Assigning a to b makes b refer to the same array as a; it doesn't create a new array.

If that last paragraph confused you, come back to this section after you've read Variables as Pointers in the More Stuff chapter. If you want, you can even read it now.

It's important to realize that the previous discussion concerns arrays (and other JavaScript objects). The situation with primitive values is different and less surprising.

At first glance, you might say that the arrays in the first example are also "the same array," but they're not. They're two different arrays that happen to have the same values. However, they occupy distinct positions in memory, so they aren't the same array, and thus aren't equal.

Given this behavior, how do you check whether two arrays have the same elements? One option is to create a function that compares the elements of one array with the corresponding elements in the other:

function arraysEqual(arr1, arr2) {
  if (arr1.length !== arr2.length) return false;

  for (let i = 0; i < arr1.length; i += 1) {
    if (arr1[i] !== arr2[i]) {
      return false;
    }
  }

  return true;
}

console.log(arraysEqual([1, 2, 3], [1, 2, 3]));    // => true
console.log(arraysEqual([1, 2, 3], [4, 5, 6]));    // => false
console.log(arraysEqual([1, 2, 3], [1, 2, 3, 4])); // => false

arraysEqual takes two arrays and returns false when an element in one array doesn't equal the corresponding element in the other. Otherwise, it returns true. It returns false right away if the arrays have different lengths; taking care of this case first simplifies the rest of the function.

arraysEqual works best when all elements in both arrays are primitive values. If any pair of elements has a non-primitive value (an array or object), arraysEqual might not return the result you expect:

> arraysEqual([1, 2, [3, 4], 5], [1, 2, [3, 4], 5])
= false

Other Array Methods

This section introduces some additional built-in methods that JavaScript Arrays provide. You should bookmark that page: you will use it often.

includes

The includes method determines whether an array includes a given element:

> let a = [1, 2, 3, 4, 5]
> a.includes(2)
= true

> a.includes(10)
= false

Internally, includes uses === to compare elements of the array with the argument. That means we can't use includes to check for the existence of a nested array or an object unless we have a reference to the same object or array we're looking for:

> let inner = [3, 4];
> let a = [1, 2, inner, 5]

> a.includes([3, 4])
= false

> a.includes(inner)
= true

indexOf

The indexOf method searches an array for an element with a given value and returns the index of the found element. If the element is not found, indexOf returns -1.

> let a = ['a', 'b', 'c', 'd', 'e']
> a.indexOf('c')
= 2

> a.indexOf('x')
= -1

As with includes, indexOf internally uses === to compare elements of the array with the argument. That means we can't use indexOf to check for the existence of a nested array or an object unless we have a reference to the same object or array we're looking for:

> let inner = [3, 4];
> let a = [1, 2, inner, 5]

> a.indexOf([3, 4])
= -1

> a.indexOf(inner)
2

By default, indexOf only looks for the first occurrence of a value in the array. If you want to look beyond the first occurrence, you need to give indexOf a starting index:

> let a = ['a', 'b', 'c', 'b', 'e']
> a.indexOf('b')
= 1

> a.indexOf('b', 2)
= 3

This last example begins the search at index position 2.

sort

The sort method is a handy way to rearrange the elements of an array in sequence. It returns a sorted array.

> let a = ["e", "c", "h", "b", "d", "a"]
> a.sort()
= [ 'a', 'b', 'c', 'd', 'e', 'h' ]

Use node or the console to test whether sort is destructive, i.e., does it mutate the caller? (It does, but check for yourself.)

We'll learn more about sort and how it works in a later course.

slice

The slice method - not the splice method you met earlier - extracts and returns a copied portion of the array. It takes two optional arguments. The first is the index at which extraction begins, while the second is where extraction ends:

> let fruits = ['mango', 'orange', 'banana', 'pear', 'apple']
> fruits.slice(1, 3)
= [ 'orange', 'banana' ]

> fruits.slice(2) // second argument defaults to rest of array
= [ 'banana', 'pear', 'apple' ]

> fruits.slice() // no arguments duplicates the array
= [ 'mango', 'orange', 'banana', 'pear', 'apple' ]

If you omit the second argument, slice returns the rest of the array starting with the index given by the first argument. With the second argument, it returns the elements up to but excluding that index. (Contrast this detail with how splice treats its second argument.) If you don't provide any arguments at all, slice returns a copy of the entire array: that is, it returns a new array with the same elements as the original. Full copies of an array are useful when you want to use a destructive method on an array but you don't want to modify the original. You can mutate the copy and keep the original.

reverse

The reverse method reverses the order of an array.

> let numbers = [1, 2, 3, 4]
> numbers.reverse()
= [ 4, 3, 2, 1 ]

> numbers
= [ 4, 3, 2, 1 ]

reverse is destructive: it mutates the array. As mentioned in the previous section, you can use slice with no arguments if you don't want to mutate the original array:

> let numbers = [1, 2, 3, 4]
> let reversedNumbers = numbers.slice().reverse();
> reversedNumbers
= [ 4, 3, 2, 1 ]

> numbers
= [ 1, 2, 3, 4 ]

Summary

Arrays are a valuable data structure. You'll see them all the time in real-world programs; nearly every useful program uses arrays at some point. JavaScript's array type has plenty of built-in methods that can perform the basic operations that programmers need every day.

Exercises

  1. In the following code, what are the final length values for array1, array2, array3, array4, and array5?

    let array1 = [1, 2, undefined, 4];
    
    let array2 = [1];
    array2.length = 5;
    
    let array3 = [];
    array3[-1] = [1];
    
    let array4 = [1, 2, 3, 4, 5];
    array4.length = 3;
    
    let array5 = [];
    array5[100] = 3;
    

    Solution

    • The length of array1 is 4. The length is the highest index position that has a value, plus 1. In this case, the highest index position that has a value is 3; add 1 to that, and we get the length value of 4.

    • The length of array2 is 5. You can set the length of an array. Even if the highest index position that has a value assigned is 0, assigning a new length of 5 overrides that length. For now, you can think of the resulting array as having 5 elements of which the last 4 have a value of undefined. In actuality, the array still has only one element, but has 4 gaps at the end -- the gaps aren't real elements and take up very little memory. We'll learn more about this in the Core Curriculum.

    • The length of array3 is 0. Index positions must be non-negative integers starting from 0. Negative and non-integer indexes don't get taken into account when determining an array's length.

    • The length of array4 is 3. Contrast this with array2. When you set an array to a length that is shorter than its current length, the array gets truncated to the new length. In this example, JavaScript truncates the array by removing the last two elements, leaving a total of 3 elements.

    • The length of array5 is 101. As already noted, the length is the highest index position that has a value, plus 1. In this case, the highest index position that has a value is 100, so the length is 101.

    Video Walkthrough

    Please register to play this video

  2. Log all of the even values from myArray to the console.

    let myArray = [1, 3, 6, 11, 4, 2,
                   4, 9, 17, 16, 0];
    

    Expected Output

    6
    4
    2
    4
    16
    0
    

    Solution

    for (let i = 0; i < myArray.length; i += 1) {
      let value = myArray[i];
      if (value % 2 === 0) {
        console.log(value);
      }
    }
    

    Our approach is straightforward: we iterate over all the elements in the array and check whether each element is even. You can also use forEach and let JavaScript take care of the indexing:

    myArray.forEach(function(value) {
      if (value % 2 === 0) {
        console.log(value);
      }
    });
    

    Video Walkthrough

    Please register to play this video

  3. Let's make the problem a little harder. In this problem, we're again interested in even numbers, but this time the numbers are in nested arrays in a single outer array.

    let myArray = [
      [1, 3, 6, 11],
      [4, 2, 4],
      [9, 17, 16, 0],
    ];
    

    Solution

    for (let i = 0; i < myArray.length; i += 1) {
      for (let j = 0; j < myArray[i].length; j += 1) {
        let value = myArray[i][j];
        if (value % 2 === 0) {
          console.log(value);
        }
      }
    }
    

    Our approach is again straightforward, but it's a bit verbose. However, the chained brackets in myArray[i][j] make it visually explicit that we're dealing with a two-dimensional nested array.

    As before, we can also use forEach to abstract away the messy details of indexes and stopping conditions:

    myArray.forEach(function(nestedArray) {
      nestedArray.forEach(function(value) {
        if (value % 2 === 0) {
          console.log(value);
        }
      });
    });
    

    That's a bit complicated at this stage in your development, but have a go at trying to figure out how it works.

    Video Walkthrough

    Please register to play this video

  4. Let's try another variation on the even-numbers theme.

    We'll return to the simpler one-dimensional array. In this problem, we want to use the map function to create a new array that contains one element for each element in the original array. If the element is an even value, then the corresponding element in the new array should contain the string 'even'; otherwise, the element in the new array should contain 'odd'.

    Example

    let myArray = [
      1, 3, 6, 11,
      4, 2, 4, 9,
      17, 16, 0,
    ];
    

    Expected Output

    [
      'odd', 'odd', 'even', 'odd',
      'even', 'even', 'even', 'odd',
      'odd', 'even', 'even',
    ]
    

    If you have trouble using map to accomplish this, try it using a regular for loop instead.

    Solution

    let newArray = myArray.map(function(value) {
      if (value % 2 === 0) {
        return 'even';
      } else {
        return 'odd';
      }
    });
    

    With a For Loop

    let newArray = [];
    for (let i = 0; i < myArray.length; i += 1) {
      let value = myArray[i];
      if (value % 2 === 0) {
        newArray.push('even');
      } else {
        newArray.push('odd');
      }
    }
    

    Our approach is again straightforward: we iterate over all the elements in the array and check whether each element is even. If it's even, we either return 'even' from the function we passed to map, or push 'even' onto the newArray. Otherwise, we return or push a value of 'odd'.

    Video Walkthrough

    Please register to play this video

  5. Write a findIntegers function that takes an array argument and returns an array that contains only the integers from the input array. Use the filter method in your function.

    Example

    let things = [1, 'a', '1', 3, NaN, 3.1415, -4, null, false];
    let integers = findIntegers(things);
    console.log(integers); // => [1, 3, -4]
     

    You can use Number.isInteger(value) to determine whether a numeric value is an integer. It returns true if the value is an integer, false otherwise.

    Solution

    function findIntegers(array) {
      return array.filter(function(element) {
        return Number.isInteger(element);
      });
    }
    

    Video Walkthrough

    Please register to play this video

  6. Use map and filter to first determine the lengths of all the elements in an array of string values, then discard the even values (keep the odd values).

    let arr = ['a', 'abcd', 'abcde', 'abc', 'ab'];
    console.log(oddLengths(arr)); // => [1, 5, 3]
    

    Note that it is possible to solve this problem without using map. However, our intent is to show how you can combine multiple functions to achieve a desired result.

    Your first step should be to create an array of the lengths, e.g., [1, 4, 5, 3, 2].

    Solution

    function oddLengths(strings) {
      let lengths = strings.map((letters) => letters.length);
      let oddLengths = lengths.filter((number) => number % 2 === 1);
      return oddLengths;
    }
    
    let arr = ['a', 'abcd', 'abcde', 'abc', 'ab'];
    console.log(oddLengths(arr));
    

    Video Walkthrough

    Please register to play this video

  7. Use reduce to compute the sum of the squares of all of the numbers in an array:

    let array = [3, 5, 7];
    console.log(sumOfSquares(array)); // => 83
    

    Note that 83 is 3*3 + 5*5 + 7*7.

    Solution

    function sumOfSquares(numbers) {
      return numbers.reduce((accumulator, number) => {
        return accumulator + number * number;
      }, 0);
    }
    
    let array = [3, 5, 7];
    console.log(sumOfSquares(array)); // => 83
    

    What happens if you forget to provide an initial value of 0 for the accumulator? Take a look at the MDN Documentation for reduce and see if you can determine what sumOfSquares would return if you omitted the initial accumulator value.

    Video Walkthrough

    Please register to play this video

  8. This problem is more challenging than most in this book. Don't worry if you can't solve it on your own.

    Write a function similar to the oddLengths function from Exercise 6, but don't use map or filter. Instead, try to use the reduce method.

    let arr = ['a', 'abcd', 'abcde', 'abc', 'ab'];
    console.log(oddLengths(arr)); // => [1, 5, 3]
    

    Use an array as the accumulator. Ideally, you should be able to use the return value of reduce as the return value of the function.

    Solution

    function oddLengths(strings) {
      return strings.reduce((filteredNumbersArray, letters) => {
        let length = letters.length;
        if (length % 2 === 1) {
          filteredNumbersArray.push(length);
        }
    
        return filteredNumbersArray;
      }, []);
    }
    
    let arr = ['a', 'abcd', 'abcde', 'abc', 'ab'];
    console.log(oddLengths(arr));
    

    Video Walkthrough

    Please register to play this video

  9. Without using a for, while, or do/while loop, write some code that checks whether the number 3 appears inside these arrays:

    let numbers1 = [1, 3, 5, 7, 9, 11];
    let numbers2 = [];
    let numbers3 = [2, 4, 6, 8];
    

    Return true or false depending on each result.

    Solution

    > numbers1.includes(3);
    = true
    
    > numbers2.includes(3);
    = false
    
    > numbers3.includes(3);
    = false
    

    Video Walkthrough

    Please register to play this video

  10. Write some code to replace the value 6 in the following array with 606:

    let arr = [
      ["hello", "world"],
      ["example", "mem", null, 6, 88],
      [4, 8, 12]
    ];
    

    You don't have to search the array. Just write an assignment that replaces the 6.

    Solution

    > arr[1][3] = 606;
    

    Video Walkthrough

    Please register to play this video