Как бы вы реализовали декартово произведение нескольких массивов в JavaScript?
Например,
cartesian([1, 2], [10, 20], [100, 200, 300])
должен вернуться
[
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[2, 10, 100],
[2, 10, 200]
...
]
Как бы вы реализовали декартово произведение нескольких массивов в JavaScript?
Например,
cartesian([1, 2], [10, 20], [100, 200, 300])
должен вернуться
[
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[2, 10, 100],
[2, 10, 200]
...
]
d3.cross(a, b[, reducer])
в феврале. github.com/d3/d3-array#crossОтветы:
Обновление 2017: 2-строчный ответ с vanilla JS
Все ответы здесь слишком сложны , большинство из них занимает 20 строк кода или даже больше.
В этом примере используются всего две строки ванильного JavaScript , без lodash, подчеркивания или других библиотек:
Обновить:
Это то же самое, что и выше, но улучшено, чтобы строго следовать Руководству по стилю JavaScript Airbnb - проверено с помощью ESLint с eslint-config-airbnb-base :
Особая благодарность ZuBB за сообщение о проблемах линтера с исходным кодом.
пример
Это точный пример из вашего вопроса:
Вывод
Это результат этой команды:
Демо
Смотрите демонстрации на:
Синтаксис
В синтаксисе, который я использовал здесь, нет ничего нового. В моем примере используется оператор распространения и остальные параметры - функции JavaScript, определенные в 6-м издании стандарта ECMA-262, опубликованного в июне 2015 года и разработанного намного раньше, более известного как ES6 или ES2015. Видеть:
Он делает такой код настолько простым, что грех не использовать его. Для старых платформ, которые не поддерживают его изначально, вы всегда можете использовать Babel или другие инструменты для преобразования его в старый синтаксис - и на самом деле мой пример, перенесенный Babel, все еще короче и проще, чем большинство примеров здесь, но это не так. действительно имеет значение, потому что результат транспиляции - это не то, что вам нужно понимать или поддерживать, это просто факт, который я нашел интересным.
Вывод
Нет необходимости писать сотни строк кода, который трудно поддерживать, и нет необходимости использовать целые библиотеки для такой простой вещи, когда две строки ванильного JavaScript могут легко выполнить работу. Как видите, использование современных функций языка действительно окупается, и в случаях, когда вам нужно поддерживать архаичные платформы без встроенной поддержки современных функций, вы всегда можете использовать Babel или другие инструменты для переноса нового синтаксиса в старый. .
Не пиши код, как будто это 1995 год
JavaScript развивается, и на это есть причина. TC39 отлично справляется с дизайном языка, добавляя новые функции, а поставщики браузеров проделывают потрясающую работу по реализации этих функций.
Чтобы увидеть текущее состояние встроенной поддержки любой данной функции в браузерах, см.
Чтобы увидеть поддержку в версиях Node, см .:
Чтобы использовать современный синтаксис на платформах, которые не поддерживают его изначально, используйте Babel:
источник
a
и 2 локальные варыb
)['a', 'b'], [1,2], [[9], [10]]
что[ [ 'a', 1, 9 ], [ 'a', 1, 10 ], [ 'a', 2, 9 ], [ 'a', 2, 10 ], [ 'b', 1, 9 ], [ 'b', 1, 10 ], [ 'b', 2, 9 ], [ 'b', 2, 10 ] ]
в результате дает урожай . Я имею в виду, что не буду хранить предметы типа[[9], [10]]
....
уже пользуемся , не должно ли[].concat(...[array])
становиться просто[...array]
?Вот функциональное решение проблемы (без какой-либо изменяемой переменной !) С использованием
reduce
иflatten
, предоставляемоеunderscore.js
:Примечание: это решение было вдохновлено http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/
источник
flatten
- сделать сглаживание неглубоким. Здесь это обязательно!true
с подчеркиванием и используйтеfalse
с lodash, чтобы обеспечить неглубокое сглаживание.Вот модифицированная версия кода @ viebel на простом Javascript, без использования какой-либо библиотеки:
источник
.concat(y)
вместо.concat([ y ])
кажется, сообщество считает это тривиальным и / или легко найти эталонную реализацию, после краткого осмотра я не смог или, может быть, просто мне нравится заново изобретать колесо или решать задачи программирования, подобные классным, в любом случае это ваш счастливый день :
полная эталонная реализация, которая относительно эффективна ... :-D
по эффективности: вы могли бы получить некоторые, вынув if из цикла и имея 2 отдельных цикла, поскольку он технически постоянен, и вы бы помогли с предсказанием ветвления и всем этим беспорядком, но эта точка является своего рода спором в javascript
Anywho, наслаждайтесь -ck
источник
reduce
функцию массива?result = result.concat(...)
и не используетargs.slice(1)
. К сожалению, мне не удалось найти способ избавиться отcurr.slice()
рекурсии.Следующая эффективная функция генератора возвращает декартово произведение всех заданных итераций :
Он принимает массивы, строки, наборы и все другие объекты, реализующие итеративный протокол .
Следуя спецификации n-арного декартова произведения, получается
[]
если один или несколько заданных итераций пусты, например,[]
или''
[[a]]
если дана единственная итерация, содержащая единственное значениеa
.Все остальные случаи обрабатываются должным образом, как показано в следующих тестовых примерах:
Показать фрагмент кода
источник
function* cartesian(head, ...tail) { for (let h of head) { const remainder = tail.length > 0 ? cartesian(...tail) : [[]]; for (let r of remainder) yield [h, ...r] } }
Вот простое рекурсивное решение:
источник
Вот рекурсивный способ, в котором используется функция генератора ECMAScript 2015, поэтому вам не нужно создавать все кортежи сразу:
источник
cartesian([[1],[2]],[10,20],[100,200,300])
.concat()
встроенный оператор распространения иногда может оказаться обманчивым.Вот однострочник с использованием собственного ES2019
flatMap
. Никаких библиотек не требуется, только современный браузер (или транспилятор):По сути, это современная версия ответа viebel без lodash.
источник
Используя типичный возврат с помощью генераторов ES6,
Ниже представлена аналогичная версия, совместимая со старыми браузерами.
Показать фрагмент кода
источник
Это чистое решение ES6 с использованием стрелочных функций
источник
Версия coffeescript с lodash:
источник
Однострочный подход с отступами для лучшего чтения.
Требуется один массив с массивами требуемых декартовых элементов.
источник
if (arr.length === 1) return arr[0].map(el => [el]);
Это теговое функциональное программирование, поэтому давайте взглянем на монаду List :
Что ж, похоже, идеально подходит для
cartesian
. JavaScript дает нам,Array
а функция монадического связывания естьArray.prototype.flatMap
, поэтому давайте воспользуемся ими -Вместо
loop
вышеуказанногоt
можно добавить как параметр каррирования -источник
Некоторые ответы по этой теме не работают, если какой-либо из входных массивов содержит элемент массива. Тебе лучше это проверить.
В любом случае нет необходимости в подчеркивании, вообще в lodash. Я считаю, что этот должен делать это на чистом JS ES6, настолько функциональном, насколько это возможно.
Этот фрагмент кода использует сокращение и вложенную карту, просто чтобы получить декартово произведение двух массивов, однако второй массив получается в результате рекурсивного вызова той же функции с одним массивом меньше; следовательно ..
a[0].cartesian(...a.slice(1))
источник
В моем конкретном случае «старомодный» подход казался более эффективным, чем методы, основанные на более современных функциях. Ниже приведен код (включая небольшое сравнение с другими решениями, опубликованными в этой ветке @rsp и @sebnukem), если он окажется полезным и для кого-то другого.
Идея следующая. Допустим, мы создаем внешний продукт
N
массивов,a_1,...,a_N
каждый из которых имеетm_i
компоненты. Внешний продукт этих массивов имеетM=m_1*m_2*...*m_N
элементы, и мы можем идентифицировать каждый из них с помощьюN-
размерного вектора, компоненты которого являются положительными целыми числами, аi
-я компонента строго ограничена сверху значениемm_i
. Например, вектор(0, 0, ..., 0)
будет соответствовать конкретной комбинации, в которой берется первый элемент из каждого массива, а(m_1-1, m_2-1, ..., m_N-1)
идентифицируется комбинацией, в которой берется последний элемент из каждого массива. Таким образом, чтобы построить всеM
комбинации, функция ниже последовательно создает все такие векторы и для каждого из них идентифицирует соответствующую комбинацию элементов входных массивов.с
node v6.12.2
, я получаю следующие тайминги:источник
Для тех, кому нужен TypeScript (переработанный ответ @Danny)
источник
Просто на выбор настоящая простая реализация с использованием массива
reduce
:источник
Современный JavaScript всего в нескольких строках. Никаких внешних библиотек или зависимостей, таких как Lodash.
источник
Вы могли
reduce
бы 2D-массив. ИспользуйтеflatMap
в массиве аккумуляторов, чтобы получитьacc.length x curr.length
количество комбинаций в каждом цикле.[].concat(c, n)
используется, потому чтоc
это число в первой итерации, а затем - массив.(Это основано на ответе Нины Шольц )
источник
Нерекурсивный подход, который добавляет возможность фильтровать и изменять продукты перед фактическим добавлением их в набор результатов. Обратите внимание на использование .map, а не .forEach. В некоторых браузерах .map работает быстрее.
источник
Простое «разумное и визуально понятное» решение.
источник
Простая модифицированная версия кода @ viebel на простом Javascript:
источник
Более читаемая реализация
источник
Это для 3-х массивов.
Некоторые ответы давали возможность использовать любое количество массивов.
Это может легко сжиматься или расширяться до меньшего или большего количества массивов.
Мне нужны были комбинации одного подхода с повторениями, поэтому я мог использовать:
но использовали:
источник
Я заметил, что никто не опубликовал решение, позволяющее передавать функцию для обработки каждой комбинации, поэтому вот мое решение:
Вывод:
источник
Простой метод грубой силы JS, который принимает в качестве входных данных массив массивов.
источник
Просто конвертированы @ dummersl в ответ от CoffeScript на JavaScript. Просто работает.
источник
Еще одна реализация. Не самый короткий или навороченный, но быстрый:
источник
Библиотеки не нужны! :)
Однако нужны стрелочные функции и, вероятно, не так эффективны. : /
источник
Для записи
Вот моя версия. Я использовал простейший итератор javascript "for ()", поэтому он совместим во всех случаях и имеет лучшую производительность.
С уважением.
источник