Дедупликация и слияние массивов

в JavaScript от ES5 до ES7+

Всем привет! Бывают случаи в работе фронтендера, когда возникает необходимость слияния 2х массивов. А иногда даже не просто слияние, а еще и дедупликация (удаление повторяющихся значений). Такие задачи могут возникать не только в работе, но и на собеседовании (тадаа ☺).

Пойдем по порядку. Сначала разберемся как сливать массивы или создавать новый на базе существующих. А затем уже посмотрим как их “чистить” от дубликатов.

Этот пост — сборник способов, которые я знаю и которые могут быть применены. Пост написан ради академического интереса и чтобы не забыть. ☺

Слияние массивов

На собеседовании я иногда спрашиваю, как слить два массива. Часто люди предлагают цикл, в котором проходятся по элементам и добавляют их новый. Более продвинутые предлагают добавить метод в prototype, более опытные предлагают добавить метод через defineProperty, чтобы сделать свойство не перечисляемым в экземплярах класса. Я не говорю про различные jQuery и Lodash/Underscoer unique меторды, которые мне не интересны… Все эти методы рабочие, безусловно, но с приходом ES6+ это можно сделать легко и быстро, с минимум кода. Но даже в ES5 эта задача так же решается просто.

Дано

var a = [1,2,3,4],
b = [3,4,5,6],
c; // результирующий
//Результат проверяем следующим образом:
console.log(Array.isArray(c) === true);
console.log(c); // результат слияния 2х массивов
/*
1. В первой части статьи должно выполниться c == [1,2,3,4,3,4,5,6]
2. Во второй части c == [1,2,3,4,5,6]
*/

Будем считать что по условию задачи мы на выходе всегда должны иметь результат в переменной c, даже если в этом нет необходимости. Важно — результат должен проходить проверку на Array.isArray().

Слияние через перебор

Такой ответ часто дают на собеседованиях. Я покажу усредненный вариант таких случаев. Варианты эти имеют преимущество в случае, если вам нужно будет сделать какие-то дополнительные действия с данными перед слиянием.

> for (var c=a, i=0; i<b.length; i++) a.push(b[i]);
> console.log(c);
> [1, 2, 3, 4, 3, 4, 5, 6]

Как видим это обычный цикл, в котором мы добавляем в массив a элементы из массива b. Вариация такого цикла:

> for (var c=b,i=b.length; i --> 0;) b.unshift(a[i]);
> console.log(c);
> [1, 2, 3, 4, 3, 4, 5, 6]

Результат на выходе такой же, но мы используем массив b в качестве результирующего, ссылку на который мы сохраняем в переменной c.

Слияние через push

Слить массивы можно и без использования циклов:

c = a,Array.prototype.push.apply(a,b)

или даже так

c = a,a.push.apply(a,b)

или так

c = b,b.unshift.apply(b,a)

Использование reduce

Еще один интересный способ слить массивы — метод reduce.

c = b.reduce((a,b)=>(a.push(b),a),a)

или даже так

c = a.reduceRight((a,b)=>(a.unshift(b),a),b)

Метод concat

О да, в JavaScript есть уже метод для слияния массивов.

Метод concat() возвращает новый массив, состоящий из массива, на котором он был вызван, соединённого с другими массивами и/или значениями, переданными в качестве аргументов.

Выходит что все что было написано выше вообще не нужно, ведь у нас есть этот метод. Тогда все что описано было выше, мы можем переписать таким образом:

c = [].concat(a,b)

или

c = a.concat(b)

Spread syntax

Spread оператор позволяет расширять выражения в тех местах, где предусмотрено использование нескольких аргументов (при вызовах функции) или ожидается несколько элементов (для массивов).

Выходит что мы можем написать так

c = a,a.push(...b)

и получить опять же слитые массивы воедино. Но тогда выходит что мы можем вообще отказаться от push и все за нас сделает интерпретатор языка:

c = [...a, ...b]

Победитель нашего челенджа по мёрджу 2х массивов.


Дедупликация элементов

Как слить массивы мы уже поняли. Есть куча разных способов. А как их слить так, чтобы не было повторяющихся элементов?

Метод filter

> c = a.concat(b.filter(i=>a.indexOf(i)===-1));
> console.log(c);
> [1, 2, 3, 4, 5, 6]

Set

С приходом ES2015 у нас появились новые структуры данных. К примеру Set.

Объект Set позволяет сохранять уникальные значения любого типа.

Супер! Это же упрощает нашу жизнь. Мы можем слить массивы используя Set:

c = new Set([...a, ...b])

Но есть одно но! Мы получили экземпляр объекта Set, но никак не Array, в итоге наша проверка Array.isArray не проходит. Нам надо переписать наш вариант следующим образом:

c = [ ...new Set([...a,...b])]

Вот теперь мы получили самый лаконичный и читабельный способ получить слияние 2х массивов с исключением повторяющихся элементов. Да здравствует ECMAScript.Next ! ☺

Array.of

Метод Array.of() создаёт новый экземпляр массива Array из произвольного числа агрументов, вне зависимости от числа или типа аргумента.
c = Array.of(...new Set(Array.of(...a, ...b)))

Array.from

Метод Array.from() создаёт новый экземпляр Array из массивоподобного или итерируемого объекта.
c = Array.from(new Set(a.concat(b)))