DEV Community

Cover image for 9 Essential Array Puzzles To Level Up Your JavaScript [JS Foundation]
Gus Pear 🍐
Gus Pear 🍐

Posted on • Updated on

9 Essential Array Puzzles To Level Up Your JavaScript [JS Foundation]

There are no shortcuts to becoming a great problem solver(which is what a dev is).

Mastery comes at the cost of practice. And since algorithms and data structures form the backbone of every software, practicing them should be on top of your priority queue. As the most used data structure for storing a sequence of values, arrays are as essential as they can be.

Today we'll hone our skills by learning how to:

*Flatten an array
*Remove duplicates from an array
*Remove duplicates from an array of objects
*Sort an array of objects
*Merge 2 arrays
*Return the difference between two arrays
*Check if an array contains duplicate elements
*Return the Intersection of two arrays
*Find the Longest String in an Array

As the old saying goes "practice makes perfect". Let's crack it.

How to flatten an array:

The easiest way is to use the built-in .flat() method.
The flat() method takes an optional maximum depth parameter.

For example, given the following array:

const arr = [1, 2, [3, 4], [[5, 6], [7, 8]]];
Enter fullscreen mode Exit fullscreen mode

You can flatten it like this:

const flattened = arr.flat(2); //max depth
console.log(flattened); // [1, 2, 3, 4, 5, 6, 7, 8]
Enter fullscreen mode Exit fullscreen mode

The flat() method returns a new array with all sub-arrays concatenated into it recursively up to the specified depth.

You can also create your own recursive function that iterates over the elements of the array and adds them to a new array, one at a time.

  • If an element is an array, the function should call itself with the element as an argument, and then concatenate the resulting array to the new array.
  • If an element is not an array, the function should simply add it to the new array.

Here is an example of how you could write a recursive function to flatten an array:

function flatten(arr) {
  let result = [];
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i]));
    } else {
      result.push(arr[i]);
    }
  }
  return result;
}

const arr = [1, [2, [3, [4]]], 5];
console.log(flatten(arr)); // [1, 2, 3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

You can make this function more concise by using the .reduce() method and the ternary operator.

function flatten(arr) {
  return arr.reduce((accumulator, currentItem) => 
    Array.isArray(currentItem)
      ? accumulator.concat(flatten(currentItem))
      : accumulator.concat(currentItem), 
  []);
}
Enter fullscreen mode Exit fullscreen mode

If you need a refresher on the .reduce() check this guide -> https://dev.to/gustavupp/a-complete-guide-to-javascripts-reduce-function-with-real-world-examples-5h6k

This function has a linear time complexity(which means that it is efficient for large arrays).
It also has a constant space complexity(which means that it does not use up more memory as the size of the array increases).

How to remove duplicates from an array:

By far the easiest and more concise way of removing duplicates of an array is by using the Set object.

The Set object stores only unique values, so you can create a new set from the array and then use the Array.from() method to convert the set back into an array.

This is one way you can do it using the Set object:

const arr = [1, 2, 3, 1, 2, 3, 4, 5];

const unique = Array.from(new Set(arr));

console.log(unique); // [1, 2, 3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

You can also use our loved .reduce() (what can't you do with the reduce?? lol)

const arr = [1, 2, 3, 1, 2, 3, 4, 5]

arr.reduce((accumulator, currentItem) => {
    if (!accumulator.includes(currentItem))
        accumulator.push(currentItem)
    return accumulator
},[])
Enter fullscreen mode Exit fullscreen mode

How to sort an array of objects

To sort an array of objects you have to choose a property from the object to sort the array by.

You can then call .sort() and pass it a comparator function.

This is how the comparator function works:

  • It takes two arguments
  • If the first should come before the second it returns a negative value.
  • If the first should come after the second it returns a positive value
  • It returns 0 if the values are equal.

Here's how you could sort an array of objects by the value of the name property:

const arr = [  
  {name: 'Bob', age: 30},  
  {name: 'Alice', age: 25},  
  {name: 'Charlie', age: 35}
];

arr.sort((a, b) => {
  if (a.age < b.age) { //you can sort by any property
    return -1;
  } else if (a.age > b.age) {
    return 1;
  } else {
    return 0;
  }
});

/*
[
  {name: 'Alice', age: 25}, 
  {name: 'Bob', age: 30}, 
  {name: 'Charlie', age: 35}
] */
Enter fullscreen mode Exit fullscreen mode

You can also use the ternary operator to make a one-liner version:

arr.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
Enter fullscreen mode Exit fullscreen mode

Note: Sometimes nested ternaries can be hard to read.

  • If sorting by a string it's better to use the .localeCompare() method:
arr.sort((a, b) => a.name.localeCompare(b.name));
Enter fullscreen mode Exit fullscreen mode

Keep in mind that the sort() method modifies the original array, so if you want to preserve the original array, you should make a copy of it first.

How to merge 2 arrays

To merge two arrays you can use the concat() method.
The concat() method returns a new array that includes the elements from both arrays.

Here is an example of how you could use concat() to merge two arrays:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

const merged = arr1.concat(arr2);
console.log(merged); // [1, 2, 3, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

A fancier or rather an ES6 version uses the spread operator (...).
The spread operator expands the elements of an array into separate arguments.

This is how it would look like

const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

Both of these approaches:

  • Create a new array that includes the elements from both arrays
  • Do not modify the original array.

If modifying the array is not an issue you can simply use .push() to add the elements of the other array to the end of the first array.

arr1.push(...arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

Return the difference between 2 arrays

There are kinds of differences:

  • Non-symmetric
  • Symmetric

Non-symmetric

  • Elements that are in the first array but not in the second

Symmetric

  • Elements that are in the first array but not in the second, and elements that are in the second but not in the first

Here is how you can use the filter() method to find the non-symmetric difference between two arrays:

const arr1 = [1, 2, 3, 4, 5];
const arr2 = [3, 4, 5, 6, 7];

const difference = arr1.filter(x => !arr2.includes(x));
console.log(difference); // [1, 2]
Enter fullscreen mode Exit fullscreen mode

And this is to get the symmetric difference:

const arr1 = [1, 2, 3, 4, 5];
const arr2 = [3, 4, 5, 6, 7];

const difference = arr1
    .filter(x => !arr2.includes(x))
    .concat(arr2.filter(x => !arr1.includes(x)));

console.log(difference); // [1, 2, 6, 7]
Enter fullscreen mode Exit fullscreen mode

This approach returns a new array and does not modify the original arrays.

Alternatively, you can use the Set object and its .has() instead of .includes()

Non-symmetric difference:

const set = new Set(arr2);
const difference = arr1.filter(x => !set.has(x));
console.log(difference); // [1, 2]
Enter fullscreen mode Exit fullscreen mode

Symmetric difference:

const set = new Set(arr2);
const set1 = new Set(arr1)
const difference = arr1
    .filter(x => !set.has(x))
    .concat(arr2.filter(x => !set1.has(x)));

console.log(difference); // [1, 2, 6, 7]
Enter fullscreen mode Exit fullscreen mode

how to Check if an array contains duplicate elements

The simplest way of doing this is by using the Set object and the size property.

The Set object stores only unique values, so you can create a set from the array and compare the size of the set to the size of the original array.

If the size of the set is smaller than the size of the array, it means that the array contains duplicate elements.

Here's how you'd do it:

const arr = [1, 2, 3, 4, 5, 5];

const hasDuplicates = new Set(arr).size !== arr.length;
console.log(hasDuplicates); // true
Enter fullscreen mode Exit fullscreen mode

You can achieve the same with an object(also called a hash or dictionary)

As you loop through the array you set the current value as the key and true as the keys' value.

If you try to access a key and there is already a value there, it means you found a duplicate.

This is a way of doing it:

const hasDuplicates = (arr) => {
    let hash = {}
   for(let i = 0; i < arr.length; i++){
       if(!hash[arr[i]]){
           hash[arr[i]] = true
       } else {
           return true;
       }
   }
    return false
}

console.log(hasDuplicates(arr)); // true
Enter fullscreen mode Exit fullscreen mode

Both of these approaches create a new object (a set or a hash object) and do not modify the original array.
They also have a linear time complexity(they are efficient for large arrays).

How to return the intersection between 2 arrays

The most common way would be by using the filter() and includes().

It would look something like this:

const arr1 = [1, 2, 3, 4, 5];
const arr2 = [3, 4, 5, 6, 7];

const intersection = arr1.filter(x => arr2.includes(x));
console.log(intersection); // [3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

This approach returns a new array and does not modify the original arrays.

As an alternative, you can use the Set object(again) and the .has() method

const set = new Set(arr2);
const intersection = arr1.filter(x => set.has(x));
console.log(intersection); // [3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

Do you see a pattern? Whenever work something out with a plain object you can create a version using the Set Object and vice versa.

How to find the longest string in an array

To do so, you can use our popular .reduce() method and a function to compare the values.

The comparator function will compare the length of the current string to the length of the previous longest string, and return the current string if it is longer, or the previous longest string if it is not.

This is how you can do it:

const arr = ['short', 'medium', 'long', 'longest'];

const longest = arr.reduce((acc, x) => x.length > acc.length ? x : acc, '');
console.log(longest); // 'longest'
Enter fullscreen mode Exit fullscreen mode

This approach creates a new variable (the acc or accumulator) and does not modify the original array.

The time complexity is linear (meaning that it is efficient for large arrays).

The same can be achieved by using the sort() method and the spread operator (...) to find the longest string in an array more concisely:

const longest = [...arr].sort((a, b) => b.length - a.length)[0];
console.log(longest); // 'longest'
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article we have learned how to:

*Flatten an array
*Remove duplicates from an array
*Remove duplicates from an array of objects
*Sort an array of objects
*Merge 2 arrays
*Return the difference between two arrays
*Check if an array contains duplicate elements
*Return the Intersection of two arrays
*Find the Longest String in an Array

Thanks for reading!

If you like this article:

*Leave a comment below(You can just say hi!)
*Follow me on Twitter @theguspear.

Catch you later,

Gus.

Top comments (6)

Collapse
 
lionelrowe profile image
lionel-rowe • Edited

For smart alphabetical comparison, it's best to use String#localeCompare.

const names = [
    'Alice',
    'bob',
    'Charlie',
    'Ángel'.normalize('NFC'),
    'Ángel'.normalize('NFD'),
    'aaron',
]
Enter fullscreen mode Exit fullscreen mode

Sorted with > and <:

['Alice', 'Ángel', 'Charlie', 'aaron', 'bob', 'Ángel']
Enter fullscreen mode Exit fullscreen mode

Sorted with String#localeCompare:

['aaron', 'Alice', 'Ángel', 'Ángel', 'bob', 'Charlie']
Enter fullscreen mode Exit fullscreen mode
Collapse
 
gustavupp profile image
Gus Pear 🍐

Thanks for taking the time to leave a comment @lionelrowe !

I had completely forgotten the .localeCompare() (in spite of having used it a few times).

I updated the example to use a number property and gave an additional example if sorting by string.

Thanks for contributing!

Have a great week

Collapse
 
lionelrowe profile image
lionel-rowe

Your post inspired me to look into localeCompare a bit more — turns out it's even cooler than I thought and can be customized with options as well as a specific locale.

const list = () => ['1', '2', '10', 'i', 'I', 'ı', 'İ', ...'座入号对']

const Locales = {
    English: 'en',
    Turkish: 'tr',
    Chinese: 'zh',
}

list().sort((a, b) => a.localeCompare(b, Locales.English))
// ⇒ ['1', '10', '2', 'i', 'I', 'İ', 'ı', '入', '号', '对', '座']

list().sort((a, b) => a.localeCompare(b, Locales.Turkish))
// ⇒ ['1', '10', '2', 'ı', 'I', 'i', 'İ', '入', '号', '对', '座']
// orders dotted and dotless i separately

list().sort((a, b) => a.localeCompare(b, Locales.Chinese))
// ⇒ ['1', '10', '2', '对', '号', '入', '座', 'i', 'I', 'İ', 'ı']
// orders Chinese characters by their pronunciation

list().sort((a, b) => a.localeCompare(b, Locales.English, { numeric: true }))
// ⇒ ['1', '2', '10', 'i', 'I', 'İ', 'ı', '入', '号', '对', '座']
// orders numbers intelligently
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
gustavupp profile image
Gus Pear 🍐

That's awesome lionel!

I had briefly seen all the options on MDN but it was a bit too dense for this post.

And your one about Proxies did the same to me haha

Collapse
 
tracygjg profile image
Tracy Gilmore

Hi Gus,
Really like the post. Here is another way to perform the sort comparison.

arr.sort((a, b) => -(a.name < b.name) || +(a.name > b.name));
Enter fullscreen mode Exit fullscreen mode

It even returns zero is a and b have the same name.
Best regards

Collapse
 
gustavupp profile image
Gus Pear 🍐

Hey Gilmore, I am so glad you liked it.

You not only took the time to leave a comment but also contributed to the post.

It's an honor to see a dev with your experience show up in the comment section of one of my humble posts.

I hope to see you around again.

All the best.

Gus