Jak zaimplementowałbyś iloczyn kartezjański wielu tablic w JavaScript?
Jako przykład,
cartesian([1, 2], [10, 20], [100, 200, 300])
powinien wrócić
[
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[2, 10, 100],
[2, 10, 200]
...
]
Jak zaimplementowałbyś iloczyn kartezjański wielu tablic w JavaScript?
Jako przykład,
cartesian([1, 2], [10, 20], [100, 200, 300])
powinien wrócić
[
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[2, 10, 100],
[2, 10, 200]
...
]
d3.cross(a, b[, reducer])
w lutym. github.com/d3/d3-array#crossOdpowiedzi:
Aktualizacja 2017: 2-wierszowa odpowiedź z waniliowym JS
Wszystkie odpowiedzi tutaj są zbyt skomplikowane , większość z nich zajmuje 20 linii kodu lub nawet więcej.
W tym przykładzie użyto tylko dwóch wierszy zwykłego JavaScript , żadnych bibliotek lodash, podkreślenia ani innych:
Aktualizacja:
Jest to to samo, co powyżej, ale poprawione, aby ściśle przestrzegać Przewodnika po stylach JavaScript Airbnb - zweryfikowanego za pomocą ESLint z eslint-config-airbnb-base :
Specjalne podziękowania dla ZuBB za poinformowanie mnie o problemach lintera z oryginalnym kodem.
Przykład
Oto dokładny przykład z Twojego pytania:
Wynik
Oto wynik tego polecenia:
Próbny
Zobacz dema na:
Składnia
Składnia, której tutaj użyłem, nie jest niczym nowym. Mój przykład wykorzystuje operator spreadu i pozostałe parametry - cechy JavaScript zdefiniowane w 6. edycji standardu ECMA-262 opublikowanego w czerwcu 2015 roku i opracowanego znacznie wcześniej, lepiej znanego jako ES6 lub ES2015. Widzieć:
To sprawia, że taki kod jest tak prosty, że grzechem jest go nie używać. W przypadku starych platform, które nie obsługują go natywnie, zawsze możesz użyć Babel lub innych narzędzi, aby przenieść go do starszej składni - i w rzeczywistości mój przykład transponowany przez Babel jest nadal krótszy i prostszy niż większość przykładów tutaj, ale tak nie jest naprawdę ważne, ponieważ efekt transpilacji nie jest czymś, co musisz zrozumieć lub utrzymać, to po prostu fakt, który wydałem mi się interesujący.
Wniosek
Nie ma potrzeby pisania setek linii kodu, który jest trudny w utrzymaniu i nie ma potrzeby używania całych bibliotek do tak prostej rzeczy, kiedy dwie linie zwykłego JavaScript mogą z łatwością wykonać zadanie. Jak widać, naprawdę opłaca się korzystać z nowoczesnych funkcji języka, aw przypadkach, gdy potrzebujesz obsługi archaicznych platform bez natywnego wsparcia dla nowoczesnych funkcji, zawsze możesz użyć Babel lub innych narzędzi do przeniesienia nowej składni do starej .
Nie koduj jak w 1995 roku
JavaScript ewoluuje i nie bez powodu. TC39 wykonuje niesamowitą robotę przy projektowaniu języka, dodając nowe funkcje, a dostawcy przeglądarek wykonują niesamowitą robotę, implementując te funkcje.
Aby zobaczyć aktualny stan natywnej obsługi dowolnej funkcji w przeglądarkach, zobacz:
Aby zobaczyć obsługę w wersjach Node, zobacz:
Aby używać nowoczesnej składni na platformach, które nie obsługują jej natywnie, użyj Babel:
źródło
a
i 2 zmienne lokalneb
)['a', 'b'], [1,2], [[9], [10]]
co[ [ 'a', 1, 9 ], [ 'a', 1, 10 ], [ 'a', 2, 9 ], [ 'a', 2, 10 ], [ 'b', 1, 9 ], [ 'b', 1, 10 ], [ 'b', 2, 9 ], [ 'b', 2, 10 ] ]
w rezultacie daje plon . Mam na myśli, że nie zatrzymam tego typu przedmiotów[[9], [10]]
....
już używamy , czy nie powinno[].concat(...[array])
stać się po prostu[...array]
?Oto funkcjonalne rozwiązanie problemu (bez żadnej zmiennej zmiennej !) Przy użyciu
reduce
iflatten
, zapewniane przezunderscore.js
:Uwaga: to rozwiązanie zostało zainspirowane http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/
źródło
flatten
jest spłaszczenie płyt . Tutaj jest to obowiązkowe!true
z podkreśleniem i używajfalse
z lodash, aby zapewnić płytkie spłaszczenie.Oto zmodyfikowana wersja kodu @ viebel w zwykłym JavaScript, bez użycia żadnej biblioteki:
źródło
.concat(y)
zamiast.concat([ y ])
wydaje się, że społeczność uważa, że jest to trywialne i / lub łatwe do znalezienia implementacji referencyjnej, po krótkiej inspekcji nie mogłem, a może po prostu lubię wymyślać na nowo koło lub rozwiązywać problemy programistyczne podobne do klasowych, tak czy inaczej to twój szczęśliwy dzień :
pełna implementacja referencyjna, która jest stosunkowo wydajna ... :-D
na wydajności: możesz trochę zyskać, wyjmując if z pętli i mając 2 oddzielne pętle, ponieważ jest on technicznie stały i pomagałbyś w przewidywaniu gałęzi i całym tym bałaganie, ale ten punkt jest trochę dyskusyjny w javascript
ktokolwiek, ciesz się -ck
źródło
reduce
funkcji tablicy?result = result.concat(...)
i nie używającargs.slice(1)
. Niestety nie mogłem znaleźć sposobu na pozbycie sięcurr.slice()
i rekursji.Następująca wydajna funkcja generatora zwraca iloczyn kartezjański wszystkich podanych iterable :
Akceptuje tablice, ciągi znaków, zestawy i wszystkie inne obiekty implementujące iterowalny protokół .
Zgodnie ze specyfikacją z kartezjańskim produktu N-ary zwracającej
[]
jeśli jedna lub więcej podanych iterable jest pustych, np.[]
lub''
[[a]]
jeślia
podana jest pojedyncza iterowalna zawierająca jedną wartość .Wszystkie inne przypadki są obsługiwane zgodnie z oczekiwaniami, co pokazują następujące przypadki testowe:
Pokaż fragment kodu
źródło
function* cartesian(head, ...tail) { for (let h of head) { const remainder = tail.length > 0 ? cartesian(...tail) : [[]]; for (let r of remainder) yield [h, ...r] } }
Oto proste, proste rozwiązanie rekurencyjne:
źródło
Oto rekurencyjny sposób korzystania z funkcji generatora ECMAScript 2015 , dzięki czemu nie musisz tworzyć wszystkich krotek naraz:
źródło
cartesian([[1],[2]],[10,20],[100,200,300])
.concat()
wbudowany operator rozprzestrzeniania się czasami może stać się oszustwem.Oto jedna linijka korzystająca z natywnego ES2019
flatMap
. Żadne biblioteki nie są potrzebne, tylko nowoczesna przeglądarka (lub transpiler):Zasadniczo jest to nowoczesna wersja odpowiedzi Viebel, bez lodash.
źródło
Używając typowego backtrackingu z generatorami ES6,
Poniżej podobna wersja kompatybilna ze starszymi przeglądarkami.
Pokaż fragment kodu
źródło
To jest czyste rozwiązanie ES6 wykorzystujące funkcje strzałek
źródło
Wersja Coffeescript z lodash:
źródło
Podejście jednoliniowe dla lepszego czytania z wcięciami.
Zajmuje pojedynczą tablicę z tablicami poszukiwanych elementów kartezjańskich.
źródło
if (arr.length === 1) return arr[0].map(el => [el]);
To jest oznaczone jako programowanie funkcjonalne, więc spójrzmy na monadę List :
Cóż, to brzmi jak idealne dopasowanie
cartesian
. JavaScript daje namArray
i monadyczną funkcją wiążącą jestArray.prototype.flatMap
, więc użyjmy ich -Zamiast
loop
powyższegot
można dodać jako parametr curried -źródło
Niektóre odpowiedzi w tym temacie kończą się niepowodzeniem, gdy którakolwiek z tablic wejściowych zawiera element tablicy. Lepiej to sprawdź.
W każdym razie nie ma potrzeby podkreślania, chleba w ogóle. Uważam, że ten powinien zrobić to z czystym JS ES6, tak funkcjonalnym, jak to tylko możliwe.
Ten fragment kodu używa mapy redukującej i zagnieżdżonej, aby po prostu uzyskać iloczyn kartezjański dwóch tablic, jednak druga tablica pochodzi z rekurencyjnego wywołania tej samej funkcji z jedną tablicą mniej; W związku z tym..
a[0].cartesian(...a.slice(1))
źródło
W moim konkretnym miejscu podejście „staromodne” wydawało się skuteczniejsze niż metody oparte na bardziej nowoczesnych funkcjach. Poniżej znajduje się kod (w tym małe porównanie z innymi rozwiązaniami zamieszczonymi w tym wątku przez @rsp i @sebnukem), jeśli okaże się przydatny także komuś innemu.
Pomysł jest następujący. Powiedzmy, że tworzymy iloczyn zewnętrzny
N
tablic,a_1,...,a_N
z których każda mam_i
komponenty. Iloczyn zewnętrzny tych tablic maM=m_1*m_2*...*m_N
elementy i każdy z nich możemy zidentyfikować za pomocąN-
wektora wymiarowego, którego składowymi są dodatnie liczby całkowite, ai
-ty składnik jest ściśle ograniczony od góry przezm_i
. Na przykład wektor(0, 0, ..., 0)
odpowiadałby określonej kombinacji, w której bierze się pierwszy element z każdej tablicy, podczas gdy(m_1-1, m_2-1, ..., m_N-1)
jest identyfikowany za pomocą kombinacji, w której pobiera się ostatni element z każdej tablicy. Tak więc, aby zbudować wszystkoM
kombinacje, poniższa funkcja konstruuje kolejno wszystkie takie wektory i dla każdego z nich identyfikuje odpowiednią kombinację elementów tablic wejściowych.z
node v6.12.2
, otrzymuję następujące czasy:źródło
Dla tych, którzy potrzebują TypeScript (reimplemented @ Danny's answer)
źródło
Dla wyboru prawdziwa prosta implementacja przy użyciu tablic
reduce
:źródło
Nowoczesny JavaScript w zaledwie kilku wierszach. Żadnych bibliotek zewnętrznych ani zależności, takich jak Lodash.
źródło
Możesz mieć
reduce
tablicę 2D. UżyjflatMap
na tablicy akumulatorów, aby uzyskaćacc.length x curr.length
liczbę kombinacji w każdej pętli.[].concat(c, n)
jest używany, ponieważc
jest liczbą w pierwszej iteracji, a następnie tablicą.(To jest oparte na odpowiedzi Niny Scholz )
źródło
Podejście nierekurencyjne, które dodaje możliwość filtrowania i modyfikowania produktów przed faktycznym dodaniem ich do zestawu wyników. Zwróć uwagę na użycie .map zamiast .forEach. W niektórych przeglądarkach .map działa szybciej.
źródło
Proste rozwiązanie „przyjazne dla umysłu i wizualne”.
źródło
Prosta, zmodyfikowana wersja kodu @ viebel w prostym języku JavaScript:
źródło
Bardziej czytelna implementacja
źródło
Dotyczy to 3 tablic.
Niektóre odpowiedzi ustąpiły miejsca dowolnej liczbie tablic.
Może to łatwo skurczyć się lub rozszerzyć do mniejszej lub większej liczby tablic.
Potrzebowałem kombinacji jednego zestawu z powtórzeniami, więc mogłem użyć:
ale używany:
źródło
Zauważyłem, że nikt nie opublikował rozwiązania, które umożliwia przekazanie funkcji do przetwarzania każdej kombinacji, więc oto moje rozwiązanie:
Wynik:
źródło
Zwykłe podejście brutalnej siły w JS, które przyjmuje tablicę tablic jako dane wejściowe.
źródło
Właśnie przekonwertowałem odpowiedź @ dummersl z CoffeScript na JavaScript. Po prostu działa.
źródło
Jeszcze inna realizacja. Nie najkrótszy ani wyszukany, ale szybki:
źródło
Żadne biblioteki nie są potrzebne! :)
Potrzebuje jednak funkcji strzałek i prawdopodobnie nie jest tak wydajna. : /
źródło
Tak dla porządku
Oto moja wersja. Zrobiłem to przy użyciu najprostszego iteratora javascript „for ()”, więc jest kompatybilny w każdym przypadku i ma najlepszą wydajność.
Z poważaniem.
źródło