javascript: set для массивов
подскажите, можно ли использовать Set для того, чтобы оставить только уникальные массивы
т.е. чтобы из [[1, 2, 3], [1, 3, 4], [1, 2, 3], [1, 2, 3]]
осталось только [[1, 2, 3], [1, 3, 4]]
или для такой задачи есть другие средства?
не хотелось бы делать через поиск в массиве по каждому элементу
Ответы (4 шт):
Если на входе у 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)
Нет. При вставке массива, в 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))
Массивы сохраняем в строки, 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);
Если есть возможность поставить массиву в соответствие уникальное значение, то можно использовать 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 }