Эту задачу можно понять несколькими смыслами:
- Мы в принципе хотим понять существует ли массив с квадратными скобками в основном массиве
- Мы хотим понять, есть ли похожий массив в основном массиве (с теми же элементами и их значениями)
В этой публикации мы рассмотрим эти два варианта трактования вопроса. Начнём с простого.
Есть ли какой-то массив в основном массиве? Он существует в виде значения элемента основного массива?
Есть основной массив с одним объектом, одним массивом, числами и строками:
var massiv = [1, 2, 3, {a:"a"}, [777], 4, 5, "efim360.ru", "javascript"]
Мы будем проверять существование какого-либо массива в основном массиве при помощи метода filter(). Внутрь метода filter() мы будем передавать анонимную функцию, которая будет возвращать нам true или false по заданному условию. Если будет true, то элемент попадёт в новый массив (отфильтрованный), если false, то не попадёт.
Т. к. мы имеем дело с прототипами классов, то условие сравнения будет определяться при помощи «имени конструктора«, в котором этот элемент был создан. В качестве цели будет выбран класс Array, а его имя конструктора будет строкой «Array«. Не перепутайте, а то не получится. Мы сравниваем строки!
massiv.filter(i => i.constructor.name == "Array")
Мы вызвали filter() и в ответ получили новый массив (отфильтрованный) с одним элементом. Это как раз то, что мы искали. Мы отловили в основном массиве какой-то массив.
Мы имеем новый массив (отфильтрованный) длиной 1. Если бы в нашем основном массиве небыло бы массивов, тогда фильтр вернул бы нам массив нулевой длины. Вот это и будет нашим условием существования какого либо объекта в массиве:
(massiv.filter(i => i.constructor.name == "Array").length > 0)
Получили ИСТИНУ (TRUE) значит в основном массиве есть массив.
Справка
Мы всегда можем распознать объект по его принадлежности к классу. В нашем случае:
- фигурные скобки принадлежат классу Object,
- квадратные скобки принадлежат классу Array,
- числа принадлежат классу Number
- строки принадлежат классу String
Давайте убедимся, что это так. Мы будем получать класс каждого элемента. То есть мы будем получать название конструктора, в котором был создан данный элемент массива.
massiv.map(i=>[i, i.constructor.name])
Есть ли похожий массив в массиве?
На эту тему можно спорить долго. «Одинаковость» массивов я буду определять по Наборам элементов. Если наборы совпадают — значит элементы равны (очень похожи друг на друга). Вы можете придумать свой вариант схожести массивов.
Есть массив:
var massiv = [[1,2,3], [3,2,1], [1,2,3,4], 4, 5, 6, "efim360.ru", "javascript", true]
Именно в таком виде, где порядок значений внутренних массивов отличается.
Предположим, что мы хотим узнать существование массива [3,2,1] в основном массиве. Есть ли похожий? Как это сделать?
Давайте с «подводных камней». Как вам такое условие?
Для кого-то это будет шок! Это значит, что мы не можем сравнивать массивы «в тупую» как они есть. Что делать?
Нужно привести массивы к строке и сравнивать строки. Поможет нам в этом конструктор JSON и его метод stringify(). В чём его слабость? В том, что он НЕ сортирует ключи перед упаковкой в строку. 🙂
Смотрим как это работает:
JSON.stringify([3,2,1]) "[3,2,1]" JSON.stringify([1,2,3]) "[1,2,3]" JSON.stringify([3,2,1]) == JSON.stringify([1,2,3]) false
Итоговые строки не равны — false
Сейчас нам нужна функция, которая умеет сортировать значения в массивах, до их передачи в stringify()
Мы можем сделать так:
- Создаём новый набор Set
- Конвертируем набор в массив
- Сортируем массив
- Переводим массив в строку
- Сравниваем строки
Поехали
JSON.stringify(Array.from(new Set([3,2,1])).sort()) "[1,2,3]" JSON.stringify(Array.from(new Set([1,2,3])).sort()) "[1,2,3]" JSON.stringify(Array.from(new Set([3,2,1])).sort()) == JSON.stringify(Array.from(new Set([1,2,3])).sort()) true
Мы научились сравнивать массивы и теперь можем вернуться к нашему основному массиву и проверить есть ли в нём такой массив или нет.
Будем использовать тот же самый filter()
massiv.filter(i=>JSON.stringify(Array.from(new Set(i)).sort()) == JSON.stringify(Array.from(new Set([3,2,1])).sort()) )
В таком виде команда может не сработать, т. к. попытка приведения неитерируемого объекта к Набору вызовет ошибку:
new Set(4) — вызовет ошибку. По этой причине нам нужна двойная фильтрация!
Итог
Первая фильтрация:
massiv.filter(i => i.constructor.name == "Array")
Отловили все массивы в основном массиве
Вторая фильтрация:
massiv.filter( i=>i.constructor.name == "Array" ).filter( i => JSON.stringify(Array.from(new Set(i)).sort()) == JSON.stringify(Array.from(new Set([3,2,1])).sort()) )
Отыскали похожий массив на наш. Даже два похожих. Хочу обратить ваше внимание, что массив [1,2,3,4] был исключён из фильтрации по причине наличия в нём значения «4».
Длина отфильтрованного массива больше «0» (нуля), значит искомый массив встречается в основном массиве. Это и есть условие:
massiv.filter( i=>i.constructor.name == "Array" ).filter( i => JSON.stringify(Array.from(new Set(i)).sort()) == JSON.stringify(Array.from(new Set([3, 2, 1])).sort()) ).length > 0
true
Задача выполнена!
Информационные ссылки
JavaScript | Равенство объектов
JavaScript | Как узнать экземпляром какого класса является объект?
Стандарт ECMAScript — Раздел «25.5 The JSON Object» — https://tc39.es/ecma262/#sec-json-object