javascript: set для массивов

подскажите, можно ли использовать Set для того, чтобы оставить только уникальные массивы

т.е. чтобы из [[1, 2, 3], [1, 3, 4], [1, 2, 3], [1, 2, 3]] осталось только [[1, 2, 3], [1, 3, 4]]

или для такой задачи есть другие средства?

не хотелось бы делать через поиск в массиве по каждому элементу


Ответы (4 шт):

Автор решения: ΝNL993

Если на входе у Set-а первый аргумент массив, то он из него уже создаёт новый Set, а остальное не трогает.

Пример:

console.log([...new Set([1, 2, 3], [1, 2, 3], [4, 5, 6], 7, 8, 9).keys()])

Если же вы добавляете массивы через метод add, то тогда Set "переобразует" массив в Symbol (Кратко: уникальный объект), то есть для Set это будут два уникальных массива (Symbol), и поэтому он их трогать не будет.

Пример:

let s = new Set
s.add([1, 2, 3])
s.add([1, 2, 3])
s.add([4, 5, 6])
let keys = [...s.keys()]
console.log(keys)
console.log(keys[0] === keys[1]) // [1, 2, 3] === [1, 2, 3] // не совсем корректное выражение, но суть вы поняли

А чтобы проверить массив, и убрать не уникальные массивы можно сделать простую проверку.

Код:

let arr = [
    [1, 2, 3],
    [1, 2, 3],
    [4, 5, 6],
    [4, 5, 6]
  ]

arr.filter((e, i, a) => {
    return a.splice(i, 1).includes(e)
}) 

console.log(arr)

→ Ссылка
Автор решения: Alexander Lonberg

Нет. При вставке массива, в Set будет установлена ссылка и она же проверена на дубликат.

Придется написать что-то вроде этого:

// Неупорядоченное сравнение элементов массива
function isEquals (a1, a2) {
  const length = a1.length
  if (length !== a2.length) return false
  const indexes = [...Array(length).fill(0).keys()]
  const includes = (e) => {
    for (let i = 0; i < indexes.length; ++i) {
      if (e === a2[indexes[i]]) {
        indexes.splice(i, 1)
        return true
      }
    }
    return false
  }
  for (let i = 0; i < length; ++i) {
    if (!includes(a1[i])) return false
  }
  return true
}

function uniq (arrs) {
  const result = []
  for (let i = 0; i < arrs.length; ++i) {
    if (!result.some((e) => isEquals(arrs[i], e))) 
      // если копируем добавим .slice()
      result.push(arrs[i].slice())
  }
  return result
}

const arr = [[1, 2, 3], [1, 3, 4], [1, 2, 3], [1, 2, 3],
  // можно даже перемешать массив, он тоже будет пропущен
  [3, 2, 1]
]
console.log(uniq(arr))

→ Ссылка
Автор решения: Stanislav Volodarskiy

Массивы сохраняем в строки, Set умеет работать со строками правильно, фильтрованные строки переводим в массивы:

arrays = [[1, 2, 3], [1, 3, 4], [1, 2, 3], [1, 2, 3]];
result = [
    ...new Set(arrays.map(a => JSON.stringify(a)))
].map(s => JSON.parse(s));
console.log(result);

Set хранит переданные ему объекты без дубликатов. К сожалению, в JavaScript одинаковые массивы не равны:

console.log([1, 2, 3] == [1, 2, 3]);

А значит для Set одинаковые массивы выглядят разными. Не спешите ругать язык - в Питоне массив в set вообще поместить нельзя. Поля этой книги маловаты чтобы объяснить почему. Правда в Питоне есть кортежи (которые в set отлично вставляются), а в JavaScript их нет (тут можно немного поругаться).

Раз нельзя с массивами, переведём их в строки. Со строками Set работает правильно: равные строки действительно равны с точки зрения языка.

У предложенного способа есть недостаток: фильтруются не сами массивы а их копии. Это не всегда удобно, но обойти можно с помощью Map. Создаётся отображение <строка>: <массив>. Ключи не повторяются, в результат попадают оригинальные массивы. Ещё сэкономили на JSON.parse:

arrays = [[1, 2, 3], [1, 3, 4], [1, 2, 3], [1, 2, 3]];
result = [...new Map(arrays.map(a => [JSON.stringify(a), a])).values()];
console.log(result);

→ Ссылка
Автор решения: Qwertiy

Если есть возможность поставить массиву в соответствие уникальное значение, то можно использовать map:

var data = [[1, 2, 3], [1, 3, 4], [1, 2, 3], [1, 2, 3]]

function toKey(a) {
  return a + ""
}

var res = [...new Map(data.map(x => [toKey(x), x])).values()]

console.log(res)
.as-console-wrapper.as-console-wrapper { max-height: 100vh }

→ Ссылка