JavaScript'te birden çok dizinin kartezyen çarpımı


112

Birden çok dizinin Kartezyen çarpımını JavaScript'te nasıl uygularsınız?

Örnek olarak,

cartesian([1, 2], [10, 20], [100, 200, 300]) 

dönmeli

[
  [1, 10, 100],
  [1, 10, 200],
  [1, 10, 300],
  [2, 10, 100],
  [2, 10, 200]
  ...
]


3
Bu, js-combinatorics modülünde uygulandı
Erel Segal-Halevi


Undercore.js hakkında katılıyorum ancak işlevsel programlama etiketini kaldırmanın @le_m'ye nasıl yardımcı olacağından emin değilim
viebel

Fwiw, d3 d3.cross(a, b[, reducer])Şubat'ta eklendi . github.com/d3/d3-array#cross
Toph

Yanıtlar:


106

2017 Güncellemesi: vanilla JS ile 2 satırlı yanıt

Burada cevapların tamamı vardır aşırı karmaşık çoğu 20 kod satırları, hatta daha fazla sürebilir.

Bu örnek yalnızca iki satır vanilya JavaScript kullanır , lodash, alt çizgi veya diğer kitaplıklar içermez:

let f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesian = (a, b, ...c) => b ? cartesian(f(a, b), ...c) : a;

Güncelleme:

Bu da aynı fakat izleyin kesinlikle için geliştirilmiş olan Airbnb JavaScript Stil Kılavuzu'na - kullanılarak, onaylanmış, ESLint ile eslint-config-airbnb-baz :

const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);

Orijinal kodla ilgili linter problemlerini bana bildirdiği için ZuBB'ye özel teşekkürler .

Misal

Bu, sorunuzun tam örneğidir:

let output = cartesian([1,2],[10,20],[100,200,300]);

Çıktı

Bu, bu komutun çıktısıdır:

[ [ 1, 10, 100 ],
  [ 1, 10, 200 ],
  [ 1, 10, 300 ],
  [ 1, 20, 100 ],
  [ 1, 20, 200 ],
  [ 1, 20, 300 ],
  [ 2, 10, 100 ],
  [ 2, 10, 200 ],
  [ 2, 10, 300 ],
  [ 2, 20, 100 ],
  [ 2, 20, 200 ],
  [ 2, 20, 300 ] ]

Demo

Demoları görün:

Sözdizimi

Burada kullandığım sözdizimi yeni bir şey değil. Örneğim, Haziran 2015'te yayınlanan ve çok daha önce geliştirilmiş olan ve daha çok ES6 veya ES2015 olarak bilinen ECMA-262 standardının 6. baskısında tanımlanan JavaScript özellikleri olan yayma işleci ve dinlenme parametrelerini kullanır. Görmek:

Kodu bu kadar basitleştirir ki kullanmamak günahtır. Yerel olarak desteklemeyen eski platformlar için, onu eski sözdizimine dönüştürmek için her zaman Babel veya diğer araçları kullanabilirsiniz - ve aslında Babel tarafından aktarılan örneğim buradaki örneklerin çoğundan daha kısa ve daha basittir, ancak bu gerçekten önemli çünkü aktarımın çıktısı anlamanız veya sürdürmeniz gereken bir şey değil, sadece ilginç bulduğum bir gerçek.

Sonuç

Bakımı zor olan yüzlerce satır kod yazmaya gerek yoktur ve iki satır vanilya JavaScript işi kolayca halledebilirken, bu kadar basit bir şey için tüm kitaplıkları kullanmaya gerek yoktur. Gördüğünüz gibi, dilin modern özelliklerini kullanmak gerçekten işe yarıyor ve modern özelliklerin yerel desteği olmadan arkaik platformları desteklemeniz gereken durumlarda, yeni sözdizimini eskisine dönüştürmek için her zaman Babel veya diğer araçları kullanabilirsiniz. .

1995'miş gibi kodlama

JavaScript gelişir ve bunu bir nedenden dolayı yapar. TC39, yeni özellikler ekleyerek dil tasarımında harika bir iş çıkarıyor ve tarayıcı satıcıları bu özellikleri uygulamak için harika bir iş çıkarıyor.

Tarayıcılardaki herhangi bir özelliğin mevcut yerel destek durumunu görmek için bkz:

Düğüm sürümlerinde desteği görmek için bkz:

Modern sözdizimini yerel olarak desteklemeyen platformlarda kullanmak için Babel'i kullanın:


Burada, typcript'in dizi yayma şeklini hesaba katan küçük bir değişiklik içeren bir typcript sürümü var. gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef
Sam

1
@rsp, gerçekten iyi cevap için çok teşekkürler. gölgelenmiş değişkenler için bir uyarı teçhizatı (2 yerel değişken ave 2 yerel değişken b) almak için bunu biraz geliştirmenizi rica
etmeme rağmen

7
"1995'miş gibi kod yazmayın" - nahoş olmaya gerek yok, herkes henüz yetişmedi.
Godwhacker

7
Bu iyidir, ancak beslendiğinde başarısız olur ve sonuç olarak ['a', 'b'], [1,2], [[9], [10]]ürün verir [ [ 'a', 1, 9 ], [ 'a', 1, 10 ], [ 'a', 2, 9 ], [ 'a', 2, 10 ], [ 'b', 1, 9 ], [ 'b', 1, 10 ], [ 'b', 2, 9 ], [ 'b', 2, 10 ] ]. Demek istediğim, öğelerin türünü korumayacak [[9], [10]].
Redu

1
...Zaten kullandığımıza göre , [].concat(...[array])basitleşmemeli [...array]mi?
Lazar Ljubenović

89

İşte soruna bir fonksiyonel çözüm (herhangi olmadan değişken değişken kullanarak!) reduceVe flattensağladığı, underscore.js:

function cartesianProductOf() {
    return _.reduce(arguments, function(a, b) {
        return _.flatten(_.map(a, function(x) {
            return _.map(b, function(y) {
                return x.concat([y]);
            });
        }), true);
    }, [ [] ]);
}

// [[1,3,"a"],[1,3,"b"],[1,4,"a"],[1,4,"b"],[2,3,"a"],[2,3,"b"],[2,4,"a"],[2,4,"b"]]
console.log(cartesianProductOf([1, 2], [3, 4], ['a']));  
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore.js"></script>

Açıklama: Bu çözüm, http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/


Bu cevapta bir yazım hatası var, "doğru" olmamalı (belki bu gönderiyi yaptığınızdan beri lodash değişti?)
Chris Jefferson

@ChrisJefferson'ın ikinci parametresi flatten, düzleştirmeyi sığ yapmaktır. Burada zorunludur!
viebel

4
Üzgünüz, bu bir lodash / alt çizgi uyumsuzluğu, bayrağın etrafını değiştirdiler.
Chris Jefferson

1
Düzleşme Yani, kullanım trueile alt çizgi ve kullanım falseile lodash sığ düzleşmesi sağlamak için.
Akseli Palén

Bu işlevi diziler dizisini kabul edecek şekilde nasıl değiştirebilirim?

44

İşte @ viebel'in kodunun herhangi bir kitaplık kullanmadan düz Javascript'te değiştirilmiş bir sürümü:

function cartesianProduct(arr) {
    return arr.reduce(function(a,b){
        return a.map(function(x){
            return b.map(function(y){
                return x.concat([y]);
            })
        }).reduce(function(a,b){ return a.concat(b) },[])
    }, [[]])
}

var a = cartesianProduct([[1, 2,3], [4, 5,6], [7, 8], [9,10]]);
console.log(JSON.stringify(a));


2
Kartezyen Ürünü için başarısız olur ([[[1], [2], [3]], ['a', 'b'], [['gama'], [['alfa']]], ['zii', "faa"]]) ["gama" yı "gama" ya ve [["alfa"] ila ["alfa"] arasında
düzleştirirken

çünkü .concat(y)bunun yerine.concat([ y ])
Teşekkürler

@Teşekkürler, yorum yapmak yerine doğrudan cevabı düzenleyebilirsiniz, sadece şimdi gerek yok: P
Olivier Lalonde

28

Görünüşe göre topluluk bunun önemsiz ve veya bir referans uygulaması bulmanın kolay olduğunu düşünüyor, kısa bir incelemeden sonra yapamadım veya belki de sadece tekerleği yeniden icat etmeyi veya sınıf benzeri programlama problemlerini çözmeyi seviyorum, her iki şekilde de şanslı gününüz :

function cartProd(paramArray) {

  function addTo(curr, args) {

    var i, copy, 
        rest = args.slice(1),
        last = !rest.length,
        result = [];

    for (i = 0; i < args[0].length; i++) {

      copy = curr.slice();
      copy.push(args[0][i]);

      if (last) {
        result.push(copy);

      } else {
        result = result.concat(addTo(copy, rest));
      }
    }

    return result;
  }


  return addTo([], Array.prototype.slice.call(arguments));
}


>> console.log(cartProd([1,2], [10,20], [100,200,300]));
>> [
     [1, 10, 100], [1, 10, 200], [1, 10, 300], [1, 20, 100], 
     [1, 20, 200], [1, 20, 300], [2, 10, 100], [2, 10, 200], 
     [2, 10, 300], [2, 20, 100], [2, 20, 200], [2, 20, 300]
   ]

nispeten verimli tam referans uygulaması ... :-D

Verimlilik konusunda: eğer döngüden çıkarak ve teknik olarak sabit olduğundan 2 ayrı döngüye sahip olarak bir miktar kazanabilirsiniz ve dal tahminine ve tüm bu karmaşaya yardımcı olursunuz, ancak bu nokta javascript'te biraz tartışmalı

neyse, keyfini çıkar -ck


1
Ayrıntılı cevabınız için teşekkürler @ckoz. Neden reducedizi işlevini kullanmıyorsunuz ?
viebel

1
@viebel neden azaltmak istiyorsun? Birincisi, düşürme, eski tarayıcılar için çok zayıf bir desteğe sahiptir (bakınız: developer.mozilla.org/en-US/docs/JavaScript/Reference/… ) ve her halükarda, diğer cevabın o çılgın kodu aslında size okunabilir görünüyor mu? ? bana göre değil. elbette daha kısadır, ancak bir kez küçültüldüğünde bu kod aynı uzunlukta olur, hata ayıklaması / optimize etmesi daha kolaydır, ikinci olarak tüm bu "azalt" çözümleri aynı şeye ayrılır, ancak bir kapanış aramasına sahip olmaları (teorik olarak daha yavaş), ayrıca daha zordur sonsuz setleri
işleyecek

5
2+ kat daha hızlı ve (imo) daha temiz bir sürüm oluşturdum: pastebin.com/YbhqZuf7 Kullanmayarak result = result.concat(...)ve kullanmayarak hız artışı sağlıyor args.slice(1). Ne yazık ki, kurtulmanın bir yolunu curr.slice()ve özyinelemeyi bulamadım .
Pauan

2
@ Pauan iyi iş, gördüklerime bağlı olarak% 10-% 50'lik bir performans artışı liginde genel olarak sıcak noktalarda hoş bir azalma. Yine de "temizlik" konusunda konuşamıyorum, kapanış kapsamı değişkenlerinin kullanılması nedeniyle sürümünüzü takip etmenin daha zor olduğunu düşünüyorum. Ancak genel olarak konuşursak, daha performanslı kodun izlenmesi daha zordur. Okunabilirlik için orijinal sürümü yazdım, keşke daha fazla zamanım olsaydı, böylece seni bir performans atışı
yapabilseydim

bu gerçekten de o sorunlardan biri
James

26

Aşağıdaki verimli oluşturucu işlevi , verilen tüm yinelemelerin kartezyen ürününü döndürür :

// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
  const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
  for (let r of remainder) for (let h of head) yield [h, ...r];
}

// Example:
console.log(...cartesian([1, 2], [10, 20], [100, 200, 300]));

Yinelenebilir protokolü uygulayan dizileri, dizeleri, kümeleri ve diğer tüm nesneleri kabul eder .

N-ary kartezyen ürünün spesifikasyonunu takiben verir

  • []verilen yinelemelerden biri veya daha fazlası boşsa, örneğin []veya''
  • [[a]]tek bir değer içeren tek bir yinelenebilir averilirse.

Diğer tüm durumlar, aşağıdaki test durumlarında gösterildiği gibi beklendiği gibi ele alınır:


Bunda ne olduğunu açıklamanın sakıncası var mı? Çok teşekkürler!
LeandroP

Bize jeneratör işlevi + kuyruk özyineleme + çift katmanlı döngüleri kullanmanın harika bir örneğini öğrettiğiniz için teşekkür ederiz! Ancak, çıktı alt dizilerinin sırasını doğru yapmak için koddaki ilk döngü pozisyonunun değiştirilmesi gerekir. Sabit kod:function* cartesian(head, ...tail) { for (let h of head) { const remainder = tail.length > 0 ? cartesian(...tail) : [[]]; for (let r of remainder) yield [h, ...r] } }
ooo

@ooo OP'nin yorumu ile verilen kartezyen ürün gruplarının sırasını yeniden oluşturmak istiyorsanız, yaptığınız değişiklik doğrudur. Bununla birlikte, ürün içindeki demetlerin sırası genellikle alakalı değildir, örneğin matematiksel olarak sonuç sırasız bir kümedir. Bu siparişi seçtim çünkü çok daha az özyinelemeli çağrı gerektiriyor ve bu nedenle biraz daha performanslı - yine de bir kıyaslama yapmadım.
le_m

Erratum: Yukarıdaki yorumumda, "tail recursion" "recursion" olmalıdır (bu durumda bir tail call değil).
ooo

21

İşte süslü olmayan, basit, özyinelemeli bir çözüm:

function cartesianProduct(a) { // a = array of array
    var i, j, l, m, a1, o = [];
    if (!a || a.length == 0) return a;

    a1 = a.splice(0, 1)[0]; // the first array of a
    a = cartesianProduct(a);
    for (i = 0, l = a1.length; i < l; i++) {
        if (a && a.length) for (j = 0, m = a.length; j < m; j++)
            o.push([a1[i]].concat(a[j]));
        else
            o.push([a1[i]]);
    }
    return o;
}

console.log(cartesianProduct([[1,2], [10,20], [100,200,300]]));
// [[1,10,100],[1,10,200],[1,10,300],[1,20,100],[1,20,200],[1,20,300],[2,10,100],[2,10,200],[2,10,300],[2,20,100],[2,20,200],[2,20,300]]


2
Bu, bu konu altındaki en verimli saf JS kodu olarak ortaya çıkıyor. 1M uzunluğunda bir dizi oluşturmak için 3 x 100 öğe dizisinde bitirmek yaklaşık 600 milisaniye sürer.
Redu

1
Kartezyen için çalışırÜrün ([[[1], [2], [3]], ['a', 'b'], [['gama'], [['alfa']]], ['zii', 'faa']]); orijinal değerleri düzleştirmeden
Mzn

10

İşte bir ECMAScript 2015 oluşturucu işlevini kullanan özyinelemeli bir yol, böylece tüm tupl'ları aynı anda oluşturmanız gerekmez:

function* cartesian() {
    let arrays = arguments;
    function* doCartesian(i, prod) {
        if (i == arrays.length) {
            yield prod;
        } else {
            for (let j = 0; j < arrays[i].length; j++) {
                yield* doCartesian(i + 1, prod.concat([arrays[i][j]]));
            }
        }
    }
    yield* doCartesian(0, []);
}

console.log(JSON.stringify(Array.from(cartesian([1,2],[10,20],[100,200,300]))));
console.log(JSON.stringify(Array.from(cartesian([[1],[2]],[10,20],[100,200,300]))));


cartesian([[1],[2]],[10,20],[100,200,300])
Redu

@Redu Cevap, dizi argümanlarını desteklemek için güncellendi.
heenenee

Evet, .concat()yerleşik yayılma operatörü bazen aldatıcı olabilir.
Redu

10

İşte yerel ES2019'u kullanan tek astar flatMap. Kitaplığa gerek yok, sadece modern bir tarayıcı (veya aktarıcı):

data.reduce((a, b) => a.flatMap(x => b.map(y => [...x, y])), [[]]);

Esasen viebel'in cevabının, lodash içermeyen modern bir versiyonu.


Elbette kitaplığa gerek yoktu. Ama aynı zamanda şimdiye kadarki en okunabilir kod değil. Bu bir değiş tokuş.
Arturo Hernandez

Okunabilirliğin bu durumda benim yayılma operatörünü kullanma seçimimle daha çok ilgisi olduğunu düşünüyorum ve kitaplık kullanmama seçimiyle pek ilgisi yok. Lodash'ın daha okunabilir koda yol açtığını sanmıyorum.
Fred Kleuver

9

ES6 jeneratörleri ile tipik bir geri izleme kullanarak,

function cartesianProduct(...arrays) {
  let current = new Array(arrays.length);
  return (function* backtracking(index) {
    if(index == arrays.length) yield current.slice();
    else for(let num of arrays[index]) {
      current[index] = num;
      yield* backtracking(index+1);
    }
  })(0);
}
for (let item of cartesianProduct([1,2],[10,20],[100,200,300])) {
  console.log('[' + item.join(', ') + ']');
}
div.as-console-wrapper { max-height: 100%; }

Aşağıda daha eski tarayıcılarla uyumlu benzer bir sürüm bulunmaktadır.


9

Bu, ok işlevlerini kullanan saf bir ES6 çözümüdür

function cartesianProduct(arr) {
  return arr.reduce((a, b) =>
    a.map(x => b.map(y => x.concat(y)))
    .reduce((a, b) => a.concat(b), []), [[]]);
}

var arr = [[1, 2], [10, 20], [100, 200, 300]];
console.log(JSON.stringify(cartesianProduct(arr)));


7

Lodash içeren bir kahve yazı tipi:

_ = require("lodash")
cartesianProduct = ->
    return _.reduceRight(arguments, (a,b) ->
        _.flatten(_.map(a,(x) -> _.map b, (y) -> x.concat(y)), true)
    , [ [] ])

7

Girintilerle daha iyi okumak için tek satırlık bir yaklaşım.

result = data.reduce(
    (a, b) => a.reduce(
        (r, v) => r.concat(b.map(w => [].concat(v, w))),
        []
    )
);

Aranan kartezyen öğelerin dizilerini içeren tek bir dizi alır.

var data = [[1, 2], [10, 20], [100, 200, 300]],
    result = data.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));

console.log(result.map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }


1
Dizinin tek bir öğeye sahip olduğu durumu doğru şekilde ele almak için bir koruma ifadesi if (arr.length === 1) return arr[0].map(el => [el]);
eklemem gerekiyordu

5

Bu, işlevsel programlama olarak etiketlenmiştir, bu yüzden List monad'a bir göz atalım :

Bu monadik liste için bir uygulama, kesin olmayan hesaplamayı temsil ediyor. bir algoritmadaki List tüm yürütme yolları için sonuçları tutabilir ...

Kulağa mükemmel bir uyum gibi geliyor cartesian. JavaScript bize verir Arrayve monadik bağlama işlevi öyle Array.prototype.flatMap, bu yüzden onları kullanmaya başlayalım -

const cartesian = (...all) =>
{ const loop = (t, a, ...more) =>
    a === undefined
      ? [ t ]
      : a .flatMap (x => loop ([ ...t, x ], ...more))
  return loop ([], ...all)
}

console .log (cartesian ([1,2], [10,20], [100,200,300]))

loopYukarıdakinin yerine tcurried parametre olarak eklenebilir -

const makeCartesian = (t = []) => (a, ...more) =>
  a === undefined
    ? [ t ]
    : a .flatMap (x => makeCartesian ([ ...t, x ]) (...more))

const cartesian =
  makeCartesian ()

console .log (cartesian ([1,2], [10,20], [100,200,300]))


3

Giriş dizilerinden herhangi biri bir dizi öğesi içerdiğinde, bu başlık altındaki yanıtlardan birkaçı başarısız olur. Şunu kontrol etsen iyi olur.

Neyse alt çizgiye gerek yok, her ne olursa olsun lodash. Bunun, olabildiğince işlevsel, saf JS ES6 ile yapması gerektiğine inanıyorum.

Bu kod parçası, basitçe iki dizinin kartezyen çarpımını elde etmek için bir azaltma ve iç içe geçmiş bir eşleme kullanır, ancak ikinci dizi, bir daha az dizi ile aynı işleve yönelik özyinelemeli bir çağrıdan gelir; dolayısıyla .. a[0].cartesian(...a.slice(1))

Array.prototype.cartesian = function(...a){
  return a.length ? this.reduce((p,c) => (p.push(...a[0].cartesian(...a.slice(1)).map(e => a.length > 1 ? [c,...e] : [c,e])),p),[])
                  : this;
};

var arr = ['a', 'b', 'c'],
    brr = [1,2,3],
    crr = [[9],[8],[7]];
console.log(JSON.stringify(arr.cartesian(brr,crr))); 


3

Benim özel ortamımda, "eski moda" yaklaşım, daha modern özelliklere dayanan yöntemlerden daha verimli görünüyordu. Aşağıda kod (@rsp ve @sebnukem tarafından bu konuya gönderilen diğer çözümlerle küçük bir karşılaştırma dahil) başkası için de yararlı olması durumunda verilmiştir.

Fikir takip ediyor. Diyelim ki N, a_1,...,a_Nher biri m_ibileşenlere sahip olan dizilerin dış çarpımını oluşturuyoruz. Bu dizilerin dış çarpımının M=m_1*m_2*...*m_Nelemanları vardır ve her birini N-, bileşenleri pozitif tamsayı olan ve i-'inci bileşeni yukarıdan kesinlikle sınırlanmış boyutlu bir vektörle tanımlayabiliriz m_i. Örneğin, vektör (0, 0, ..., 0), her diziden ilk öğeyi alan belirli bir bileşime karşılık gelirken (m_1-1, m_2-1, ..., m_N-1), her diziden son öğeyi aldığı kombinasyonla tanımlanır. Böylece hepsini inşa etmek içinM kombinasyonlar, aşağıdaki fonksiyon bu tür tüm vektörleri art arda inşa eder ve bunların her biri için giriş dizilerinin elemanlarının karşılık gelen kombinasyonunu tanımlar.

function cartesianProduct(){
    const N = arguments.length;

    var arr_lengths = Array(N);
    var digits = Array(N);
    var num_tot = 1;
    for(var i = 0; i < N; ++i){
        const len = arguments[i].length;
        if(!len){
            num_tot = 0;
            break;
        }
        digits[i] = 0;
        num_tot *= (arr_lengths[i] = len);
    }

    var ret = Array(num_tot);
    for(var num = 0; num < num_tot; ++num){

        var item = Array(N);
        for(var j = 0; j < N; ++j){ item[j] = arguments[j][digits[j]]; }
        ret[num] = item;

        for(var idx = 0; idx < N; ++idx){
            if(digits[idx] == arr_lengths[idx]-1){
                digits[idx] = 0;
            }else{
                digits[idx] += 1;
                break;
            }
        }
    }
    return ret;
}
//------------------------------------------------------------------------------
let _f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesianProduct_rsp = (a, b, ...c) => b ? cartesianProduct_rsp(_f(a, b), ...c) : a;
//------------------------------------------------------------------------------
function cartesianProduct_sebnukem(a) {
    var i, j, l, m, a1, o = [];
    if (!a || a.length == 0) return a;

    a1 = a.splice(0, 1)[0];
    a = cartesianProduct_sebnukem(a);
    for (i = 0, l = a1.length; i < l; i++) {
        if (a && a.length) for (j = 0, m = a.length; j < m; j++)
            o.push([a1[i]].concat(a[j]));
        else
            o.push([a1[i]]);
    }
    return o;
}
//------------------------------------------------------------------------------
const L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const args = [L, L, L, L, L, L];

let fns = {
    'cartesianProduct': function(args){ return cartesianProduct(...args); },
    'cartesianProduct_rsp': function(args){ return cartesianProduct_rsp(...args); },
    'cartesianProduct_sebnukem': function(args){ return cartesianProduct_sebnukem(args); }
};

Object.keys(fns).forEach(fname => {
    console.time(fname);
    const ret = fns[fname](args);
    console.timeEnd(fname);
});

ile node v6.12.2aşağıdaki zamanlamaları alıyorum:

cartesianProduct: 427.378ms
cartesianProduct_rsp: 1710.829ms
cartesianProduct_sebnukem: 593.351ms

3

TypeScript'e ihtiyaç duyanlar için (yeniden uygulandı @ Danny'nin cevabı)

/**
 * Calculates "Cartesian Product" sets.
 * @example
 *   cartesianProduct([[1,2], [4,8], [16,32]])
 *   Returns:
 *   [
 *     [1, 4, 16],
 *     [1, 4, 32],
 *     [1, 8, 16],
 *     [1, 8, 32],
 *     [2, 4, 16],
 *     [2, 4, 32],
 *     [2, 8, 16],
 *     [2, 8, 32]
 *   ]
 * @see https://stackoverflow.com/a/36234242/1955709
 * @see https://en.wikipedia.org/wiki/Cartesian_product
 * @param arr {T[][]}
 * @returns {T[][]}
 */
function cartesianProduct<T> (arr: T[][]): T[][] {
  return arr.reduce((a, b) => {
    return a.map(x => {
      return b.map(y => {
        return x.concat(y)
      })
    }).reduce((c, d) => c.concat(d), [])
  }, [[]] as T[][])
}

2

Sadece bir seçim için, dizileri kullanarak gerçekten basit bir uygulama reduce:

const array1 = ["day", "month", "year", "time"];
const array2 = ["from", "to"];
const process = (one, two) => [one, two].join(" ");

const product = array1.reduce((result, one) => result.concat(array2.map(two => process(one, two))), []);

2

Sadece birkaç satırda modern JavaScript. Lodash gibi harici kitaplıklar veya bağımlılıklar yok.

function cartesian(...arrays) {
  return arrays.reduce((a, b) => a.flatMap(x => b.map(y => x.concat([y]))), [ [] ]);
}

console.log(
  cartesian([1, 2], [10, 20], [100, 200, 300])
    .map(arr => JSON.stringify(arr))
    .join('\n')
);


2

Yapabilirsin reduce2B dizi. Kullanım flatMapakümülatör dizi elde etmek için acc.length x curr.lengthher bir döngüde kombinasyonlarının sayısı. [].concat(c, n)kullanılır çünkü cilk yinelemede bir sayı ve daha sonra bir dizidir.

const data = [ [1, 2], [10, 20], [100, 200, 300] ];

const output = data.reduce((acc, curr) =>
  acc.flatMap(c => curr.map(n => [].concat(c, n)))
)

console.log(JSON.stringify(output))

(Bu, Nina Scholz'un cevabına dayanmaktadır )


1

Ürünleri sonuç kümesine eklemeden önce filtreleme ve değiştirme yeteneği ekleyen yinelemeli olmayan bir yaklaşım. .ForEach yerine .map kullanımına dikkat edin. Bazı tarayıcılarda .map daha hızlı çalışır.

function crossproduct(arrays,rowtest,rowaction) {
      // Calculate the number of elements needed in the result
      var result_elems = 1, row_size = arrays.length;
      arrays.map(function(array) {
            result_elems *= array.length;
      });
      var temp = new Array(result_elems), result = [];

      // Go through each array and add the appropriate element to each element of the temp
      var scale_factor = result_elems;
      arrays.map(function(array)
      {
        var set_elems = array.length;
        scale_factor /= set_elems;
        for(var i=result_elems-1;i>=0;i--) {
            temp[i] = (temp[i] ? temp[i] : []);
            var pos = i / scale_factor % set_elems;
            // deal with floating point results for indexes, this took a little experimenting
            if(pos < 1 || pos % 1 <= .5) {
                pos = Math.floor(pos);
            } else {
                pos = Math.min(array.length-1,Math.ceil(pos));
            }
            temp[i].push(array[pos]);
            if(temp[i].length===row_size) {
                var pass = (rowtest ? rowtest(temp[i]) : true);
                if(pass) {
                    if(rowaction) {
                        result.push(rowaction(temp[i]));
                    } else {
                        result.push(temp[i]);
                    }
                }
            }
        }
      });
      return result;
    }

1

Basit bir "akıl ve görsel olarak dost" çözüm.

görüntü açıklamasını buraya girin


// t = [i, length]

const moveThreadForwardAt = (t, tCursor) => {
  if (tCursor < 0)
    return true; // reached end of first array

  const newIndex = (t[tCursor][0] + 1) % t[tCursor][1];
  t[tCursor][0] = newIndex;

  if (newIndex == 0)
    return moveThreadForwardAt(t, tCursor - 1);

  return false;
}

const cartesianMult = (...args) => {
  let result = [];
  const t = Array.from(Array(args.length)).map((x, i) => [0, args[i].length]);
  let reachedEndOfFirstArray = false;

  while (false == reachedEndOfFirstArray) {
    result.push(t.map((v, i) => args[i][v[0]]));

    reachedEndOfFirstArray = moveThreadForwardAt(t, args.length - 1);
  }

  return result;
}

// cartesianMult(
//   ['a1', 'b1', 'c1'],
//   ['a2', 'b2'],
//   ['a3', 'b3', 'c3'],
//   ['a4', 'b4']
// );

console.log(cartesianMult(
  ['a1'],
  ['a2', 'b2'],
  ['a3', 'b3']
));

1

Düz Javascript'te @ viebel kodunun basit, değiştirilmiş bir sürümü:

function cartesianProduct(...arrays) {
  return arrays.reduce((a, b) => {
    return [].concat(...a.map(x => {
      const next = Array.isArray(x) ? x : [x];
      return [].concat(b.map(y => next.concat(...[y])));
    }));
  });
}

const product = cartesianProduct([1, 2], [10, 20], [100, 200, 300]);

console.log(product);
/*
[ [ 1, 10, 100 ],
  [ 1, 10, 200 ],
  [ 1, 10, 300 ],
  [ 1, 20, 100 ],
  [ 1, 20, 200 ],
  [ 1, 20, 300 ],
  [ 2, 10, 100 ],
  [ 2, 10, 200 ],
  [ 2, 10, 300 ],
  [ 2, 20, 100 ],
  [ 2, 20, 200 ],
  [ 2, 20, 300 ] ];
*/

1

Daha okunaklı bir uygulama

function productOfTwo(one, two) {
  return one.flatMap(x => two.map(y => [].concat(x, y)));
}

function product(head = [], ...tail) {
  if (tail.length === 0) return head;
  return productOfTwo(head, product(...tail));
}

const test = product(
  [1, 2, 3],
  ['a', 'b']
);

console.log(JSON.stringify(test));


1
f=(a,b,c)=>a.flatMap(ai=>b.flatMap(bi=>c.map(ci=>[ai,bi,ci])))

Bu 3 dizi içindir.
Bazı cevaplar herhangi bir sayıda dizi için bir yol verdi.
Bu, daha az veya daha fazla diziye kolayca daralabilir veya genişleyebilir.
Tek bir setin tekrarlı kombinasyonlarına ihtiyacım vardı, bu yüzden kullanabilirdim:

f(a,a,a)

ama kullanıldı:

f=(a,b,c)=>a.flatMap(a1=>a.flatMap(a2=>a.map(a3=>[a1,a2,a3])))

0

Hiç kimsenin her kombinasyonu işlemek için bir işlevin geçirilmesine izin veren bir çözüm göndermediğini fark ettim, işte benim çözümüm:

const _ = require('lodash')

function combinations(arr, f, xArr = []) {
    return arr.length>1 
    ? _.flatMap(arr[0], x => combinations(arr.slice(1), f, xArr.concat(x)))
    : arr[0].map(x => f(...xArr.concat(x)))
}

// use case
const greetings = ["Hello", "Goodbye"]
const places = ["World", "Planet"]
const punctuationMarks = ["!", "?"]
combinations([greetings,places,punctuationMarks], (greeting, place, punctuationMark) => `${greeting} ${place}${punctuationMark}`)
  .forEach(row => console.log(row))

Çıktı:

Hello World!
Hello World?
Hello Planet!
Hello Planet?
Goodbye World!
Goodbye World?
Goodbye Planet!
Goodbye Planet?

0

Düz JS kaba kuvvet yaklaşımı bir dizi diziyi girdi olarak alır.

var cartesian = function(arrays) {
    var product = [];
    var precals = [];
    var length = arrays.reduce(function(acc, curr) {
        return acc * curr.length
    }, 1);
    for (var i = 0; i < arrays.length; i++) {
        var array = arrays[i];
        var mod = array.length;
        var div = i > 0 ? precals[i - 1].div * precals[i - 1].mod : 1;
        precals.push({
            div: div,
            mod: mod
        });
    }
    for (var j = 0; j < length; j++) {
        var item = [];
        for (var i = 0; i < arrays.length; i++) {
            var array = arrays[i];
            var precal = precals[i];
            var k = (~~(j / precal.div)) % precal.mod;
            item.push(array[k]);
        }
        product.push(item);
    }
    return product;
};

cartesian([
    [1],
    [2, 3]
]);

cartesian([
    [1],
    [2, 3],
    [4, 5, 6]
]);

0

var chars = ['A', 'B', 'C']
var nums = [1, 2, 3]

var cartesianProduct = function() {
  return _.reduce(arguments, function(a, b) {
    return _.flatten(_.map(a, function(x) {
      return _.map(b, function(y) {
        return x.concat(y);
      });
    }), true);
  }, [
    []
  ]);
};

console.log(cartesianProduct(chars, nums))
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

Sadece dönüştürülen dummersl en @ coffeescript dan JavaScript cevap. Sadece çalışıyor.

var chars = ['A', 'B', 'C']
var nums = [1, 2, 3]

var cartesianProduct = function() {
  return _.reduce(arguments, function(a, b) {
    return _.flatten(_.map(a, function(x) {
      return _.map(b, function(y) {
        return x.concat(y);
      });
    }), true);
  }, [[]]);
};

console.log( cartesianProduct(chars, nums) )

0

Yine başka bir uygulama. En kısa veya süslü değil, hızlı:

function cartesianProduct() {
    var arr = [].slice.call(arguments),
        intLength = arr.length,
        arrHelper = [1],
        arrToReturn = [];

    for (var i = arr.length - 1; i >= 0; i--) {
        arrHelper.unshift(arrHelper[0] * arr[i].length);
    }

    for (var i = 0, l = arrHelper[0]; i < l; i++) {
        arrToReturn.push([]);
        for (var j = 0; j < intLength; j++) {
            arrToReturn[i].push(arr[j][(i / arrHelper[j + 1] | 0) % arr[j].length]);
        }
    }

    return arrToReturn;
}

0

Kütüphaneye gerek yok! :)

Ok işlevlerine ihtiyaç duyuyor ve muhtemelen o kadar verimli değil. : /

const flatten = (xs) => 
    xs.flat(Infinity)

const binaryCartesianProduct = (xs, ys) =>
    xs.map((xi) => ys.map((yi) => [xi, yi])).flat()

const cartesianProduct = (...xss) =>
    xss.reduce(binaryCartesianProduct, [[]]).map(flatten)
      
console.log(cartesianProduct([1,2,3], [1,2,3], [1,2,3]))


0

Kayıt için

İşte benim versiyonum. Bunu en basit javascript yineleyicisini "for ()" kullanarak yaptım, bu nedenle her durumda uyumlu ve en iyi performansa sahip.

function cartesian(arrays){
    var quant = 1, counters = [], retArr = [];

    // Counts total possibilities and build the counters Array;
    for(var i=0;i<arrays.length;i++){
        counters[i] = 0;
        quant *= arrays[i].length;
    }

    // iterate all possibilities
    for(var i=0,nRow;i<quant;i++){
        nRow = [];
        for(var j=0;j<counters.length;j++){
            if(counters[j] < arrays[j].length){
                nRow.push(arrays[j][counters[j]]);
            } else { // in case there is no such an element it restarts the current counter
                counters[j] = 0;
                nRow.push(arrays[j][counters[j]]);
            }
            counters[j]++;
        }
        retArr.push(nRow);
    }
    return retArr;
}

Saygılarımla.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.