JavaScript에서 여러 배열의 데카르트 곱


112

JavaScript에서 여러 배열의 데카르트 곱을 어떻게 구현 하시겠습니까?

예로서,

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

돌아와야한다

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


3
JS-조합론에서 구현이 모듈 : github.com/dankogai/js-combinatorics
Erel 시걸-Halevi


나는 underscore.js에 대해 동의하지만 난 기능 프로그래밍 태그를 제거하는 @le_m 도움이 될 것입니다 방법을 볼 모르겠어요
viebel

Fwiw, d3 d3.cross(a, b[, reducer])는 2 월에 추가 되었습니다. github.com/d3/d3-array#cross
Toph

답변:


105

2017 업데이트 : 바닐라 JS를 사용한 2 줄 답변

여기에있는 모든 답변은 지나치게 복잡 하며 대부분 20 줄 이상의 코드가 필요합니다.

이 예제는 lodash, underscore 또는 기타 라이브러리없이 두 줄의 바닐라 JavaScript 만 사용합니다 .

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;

최신 정보:

위와 동일하지만 ESLinteslint-config-airbnb- base 와 함께 사용하여 검증 된 Airbnb JavaScript 스타일 가이드 를 엄격하게 따르도록 개선되었습니다 .

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);

원본 코드의 linter 문제에 대해 알려 주신 ZuBB 에게 특별히 감사드립니다 .

이것은 귀하의 질문에 대한 정확한 예입니다.

let output = cartesian([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 ] ]

데모

데모보기 :

통사론

여기에서 사용한 구문은 새로운 것이 아닙니다. 내 예에서는 스프레드 연산자와 나머지 매개 변수를 사용합니다.이 기능은 2015 년 6 월에 발행 된 ECMA-262 표준 6 판에 정의 된 JavaScript의 기능이며 훨씬 더 일찍 개발되었으며 ES6 또는 ES2015로 더 잘 알려져 있습니다. 보다:

이렇게 코드를 간단하게 만들어서 사용하지 않는 것은 죄악입니다. 기본적으로 지원하지 않는 이전 플랫폼의 경우 항상 Babel 또는 기타 도구를 사용하여 이전 구문으로 변환 할 수 있습니다. 사실 Babel로 변환 한 제 예제는 여기에있는 대부분의 예제보다 여전히 짧고 간단하지만 그렇지 않습니다. 번역의 결과물은 이해하거나 유지해야하는 것이 아니기 때문에 정말 중요합니다. 단지 제가 흥미로워 한 사실입니다.

결론

유지 관리가 어려운 수백 줄의 코드를 작성할 필요가 없으며 두 줄의 바닐라 자바 ​​스크립트가 작업을 쉽게 수행 할 수있는 간단한 작업을 위해 전체 라이브러리를 사용할 필요가 없습니다. 보시다시피 언어의 최신 기능을 사용하는 것이 실제로 효과가 있으며 최신 기능을 기본 지원하지 않는 구식 플랫폼을 지원해야하는 경우 항상 Babel 또는 기타 도구를 사용하여 새 구문을 이전 구문으로 변환 할 수 있습니다. .

1995 년처럼 코딩하지 마세요

자바 스크립트는 진화하고 있으며 그 이유가 있습니다. TC39는 새로운 기능을 추가하여 언어 디자인의 놀라운 작업을 수행하고 브라우저 공급 업체는 이러한 기능을 구현하는 놀라운 작업을 수행합니다.

브라우저의 특정 기능에 대한 현재 기본 지원 상태를 보려면 다음을 참조하십시오.

Node 버전의 지원을 보려면 다음을 참조하십시오.

기본적으로 지원하지 않는 플랫폼에서 최신 구문을 사용하려면 Babel을 사용하세요.


다음은 typescript가 배열 확산을 수행하는 방식을 설명하기 위해 약간 변경된 typescript 버전입니다. gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef
Sam Sippe

1
@rsp 정말 좋은 답변을 주셔서 감사합니다. 섀도우 변수 (로컬 변수 a2 b
개와

7
"1995 년처럼 코딩하지 마세요"-불쾌 할 필요는 없습니다. 아직 모든 사람이 따라 잡은 것은 아닙니다.
Godwhacker

7
이것은 괜찮지 만 결과적으로 ['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]].
Redu

1
우리는 ...이미 사용 하고 있기 때문에 [].concat(...[array])단순히 되어야하지 [...array]않습니까?
Lazar Ljubenović

88

여기서 문제의 해결책 기능 (임의없이 가변 변수 사용!) reduceflatten의해 제공 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>

비고 :이 솔루션은 http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/ 에서 영감을 얻었습니다 .


이 답변에 오타가있다, a "는, 사실은"(이 게시물을 만든 이후 어쩌면 lodash 변경?)이 안
크리스 제퍼슨

@ChrisJefferson의 두 번째 매개 변수 flatten는 평탄화를 얕게 만드는 것입니다. 여기서는 필수입니다!
viebel

4
죄송합니다. 이것은 lodash / 밑줄 비 호환성이며 플래그를 바꿨습니다.
Chris Jefferson

1
병합 때, 사용 true밑줄 사용 falselodash는 얕은 플래트 닝을 보장합니다.
Akseli Palén

배열 배열을 받아들이도록이 함수를 어떻게 수정합니까?

44

다음은 라이브러리를 사용하지 않고 일반 자바 스크립트로 된 @viebel 코드의 수정 된 버전입니다.

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
CartesianProduct ([[[1], [2], [3]], [ 'a', 'b'], [[ 'gamma'], [[ 'alpha']]], [ 'zii', 'faa']]) [ '감마']를 '감마'로, [[ '알파']]를 [ '알파'로
평 평화

때문에 .concat(y)대신.concat([ y ])
당신을 감사

@ 감사합니다 당신은 댓글 대신 직접 답변을 편집 할 수 있습니다. 그냥 그렇게 했으므로 지금 필요하지 않습니다 : P
Olivier Lalonde

28

커뮤니티는 이것이 사소하고 참조 구현을 찾기가 쉽다고 생각하는 것 같습니다. 간단한 검사를 통해 내가 할 수 없었거나 어쩌면 내가 바퀴를 다시 발명하거나 교실과 같은 프로그래밍 문제를 해결하는 것을 좋아하는 것 같습니다. :

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]
   ]

상대적으로 효율적인 전체 참조 구현 ... :-D

효율성에 대한 : 루프에서 if를 취하고 기술적으로 일정하고 분기 예측과 모든 혼란을 돕고 있기 때문에 2 개의 개별 루프를 가지면 얻을 수 있지만 그 요점은 자바 스크립트에서 일종의 논쟁입니다.

누구든지 즐기세요 -ck


1
자세한 답변에 대해 @ckoz에게 감사드립니다. 왜 reduce배열 의 기능 을 사용하지 않습니까?
viebel

1
@viebel 왜 감소를 사용하고 싶습니까? 우선, reduce는 이전 브라우저에 대한 지원이 매우 열악하며 (참조 : developer.mozilla.org/en-US/docs/JavaScript/Reference/… ), 어쨌든 다른 답변의 미친 코드가 실제로 읽을 수있는 것처럼 보입니다. ? 나에게는 그렇지 않습니다. 더 짧지 만 일단 축소되면이 코드는 길이가 거의 같고 디버그 / 최적화가 더 쉬우 며 두 번째로 모든 "감소"솔루션이 동일한 것으로 분류됩니다. 단, 클로저 조회 (이론적으로 더 느림)를 제외하고는 더 어렵습니다. 디자인은 ... 무한 집합을 처리 할 수 있도록
ckozl

5
: 나는 빠르고 (IMO) 클리너 버전 2+ 시간을 만들어 pastebin.com/YbhqZuf7 그것은 사용하지 않음으로써 속도 향상을 달성 result = result.concat(...)하고 사용하지 않음으로써을 args.slice(1). 불행히도, 나는 curr.slice()재귀를 제거하는 방법을 찾지 못했습니다 .
Pauan 2014 년

2
@Pauan 잘 했어, 내가보고있는 것에 따라 리그에서 10 % -50 %의 성능 향상을 위해 전체적으로 핫스팟의 멋진 감소. "청결성"에 대해 말할 수는 없지만 클로저 범위 변수를 사용하기 때문에 귀하의 버전을 실제로 따르기가 더 어렵다고 생각합니다. 그러나 일반적으로 더 성능이 좋은 코드는 따라 가기가 더 어렵습니다. ) 아마도 나중에 ... 내가 읽기 쉽도록 원래 버전을 썼다, 나는 내가 던져 성능에서 당신을 참여 할 수 있도록 더 많은 시간이 있었으면 좋겠다
ckozl

이건 정말 이러한 문제 중 하나입니다
제임스

26

다음의 효율적인 생성 기능은 모두 제공의 직교 제품 반환 반복 가능 객체를 :

// 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]));

반복 가능한 프로토콜을 구현하는 배열, 문자열, 집합 및 기타 모든 개체를 허용 합니다 .

n 항 데카르트 곱 의 사양에 따라 다음 을 산출합니다.

  • []하나 이상의 주어진 이터 러블이 비어있는 경우, 예 : []또는''
  • [[a]]단일 값을 포함하는 단일 iterable a이 제공되는 경우.

다른 모든 케이스는 다음 테스트 케이스에서 설명하는대로 예상대로 처리됩니다.


이 문제에 대해 설명해 주시겠습니까? 감사합니다!
LeandroP

제너레이터 함수 + 테일 재귀 + 더블 레이어 루프를 사용하는 아주 멋진 예제를 가르쳐 주셔서 감사합니다! 그러나 코드에서 첫 번째 for 루프의 위치를 ​​변경하여 출력 하위 배열의 순서를 올바로 만들어야합니다. 고정 코드 :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의 주석에 의해 주어진 데카르트 곱 튜플의 순서를 재현하려면 수정이 정확합니다. 그러나 제품 내 튜플의 순서는 일반적으로 관련이 없습니다. 예를 들어 수학적으로 결과는 순서가 지정되지 않은 집합입니다. 이 순서는 재귀 호출이 훨씬 덜 필요하고 따라서 약간 더 성능이 뛰어 나기 때문에 선택했습니다.하지만 벤치 마크를 실행하지는 않았습니다.
le_m

정오표 : 위의 설명에서 "꼬리 재귀"는 "재귀"여야합니다 (이 경우 꼬리 호출이 아님).
ooo

20

다음은 비판적이고 간단한 재귀 솔루션입니다.

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
이것은이 주제에서 가장 효율적인 순수 JS 코드로 밝혀졌습니다. 길이가 1M 인 배열을 생성하기 위해 3 x 100 항목 배열을 완료하는 데 약 600msec가 걸립니다.
Redu

1
CartesianProduct ([[[1], [2], [3]], [ 'a', 'b'], [[ 'gamma'], [[ 'alpha']]], [ 'zii', 'faa']]); 원래 값을 병합하지 않고
Mzn

10

다음은 ECMAScript 2015 생성기 함수 를 사용하는 재귀 적 방법 이므로 한 번에 모든 튜플을 만들 필요가 없습니다.

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 응답이 배열 인수를 지원하도록 업데이트되었습니다.
heenenee

.concat()내장 된 스프레드 연산자는 때때로 속일 수 있습니다.
Redu

10

다음은 네이티브 ES2019를 사용하는 한 줄짜리 flatMap입니다. 라이브러리가 필요하지 않습니다. 최신 브라우저 (또는 트랜스 파일러) 만 있으면됩니다.

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

본질적으로 lodash가없는 viebel의 답변의 현대 버전입니다.


9

ES6 생성기와 함께 일반적인 역 추적을 사용하여,

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%; }

아래에는 이전 브라우저와 호환되는 유사한 버전이 있습니다.


9

이것은 화살표 기능을 사용하는 순수한 ES6 솔루션입니다.

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가 포함 된 coffeescript 버전 :

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

7

들여 쓰기로 더 나은 읽기를위한 한 줄 접근 방식.

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

원하는 데카르트 항목의 배열이있는 단일 배열을 사용합니다.

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; }


내가 제대로 배열이 하나의 요소가 사건을 처리하기 위해 가드 문을 추가했다 :if (arr.length === 1) return arr[0].map(el => [el]);
JacobEvelyn

5

이 태그는 기능 프로그래밍 이므로 List 모나드를 살펴 보겠습니다 .

이 모나 딕 목록에 대한 한 가지 응용 프로그램은 비 결정적 계산을 나타냅니다. 알고리즘의 List 모든 실행 경로 에 대한 결과를 보유 할 수 있습니다 .

유사한 그런데 그 소리를 완벽하게 적합 cartesian. 자바 스크립트는 우리에게 제공 Array하고 모나 딕 바인딩 함수는입니다 Array.prototype.flatMap.

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]))

loop위의 대신 t카레 매개 변수로 추가 할 수 있습니다.

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

입력 배열에 배열 항목이 포함 된 경우이 항목의 일부 답변이 실패합니다. 당신은 그것을 확인하는 것이 좋습니다.

어쨌든 밑줄, lodash는 필요하지 않습니다. 나는 이것이 기능적으로 순수한 JS ES6로 그것을해야한다고 믿습니다.

이 코드는 축소 및 중첩 맵을 사용하여 단순히 두 배열의 데카르트 곱을 가져 오지만 두 번째 배열은 배열이 하나 더 적은 동일한 함수에 대한 재귀 호출에서 비롯됩니다. 그 후.. 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

내 특정 설정에서 "구식"접근 방식은보다 현대적인 기능을 기반으로하는 방법보다 더 효율적으로 보였습니다. 다음은 다른 사람에게도 유용한 것으로 입증 된 코드 (@rsp 및 @sebnukem이이 스레드에 게시 한 다른 솔루션과의 작은 비교 포함)입니다.

아이디어는 다음과 같습니다. 각각 구성 요소 가있는 N배열 의 외부 곱을 구성한다고 가정 해 보겠습니다 . 이 배열의 외부 곱에는 요소가 있으며 구성 요소가 양의 정수이고- 번째 구성 요소가 위에서부터 엄격하게 경계가 지정된 차원 벡터로 각 요소를 식별 할 수 있습니다 . 예를 들어, 벡터 는 각 배열에서 첫 번째 요소를 가져 오는 특정 조합에 해당하는 반면, 각 배열 에서 마지막 요소를 가져 오는 조합으로 식별됩니다. 따라서 모든 것을 구성하기 위해a_1,...,a_Nm_iM=m_1*m_2*...*m_NN-im_i(0, 0, ..., 0)(m_1-1, m_2-1, ..., m_N-1)M 아래의 함수는 이러한 모든 벡터를 연속적으로 구성하고 각각에 대해 입력 배열 요소의 해당 조합을 식별합니다.

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);
});

와 함께 node v6.12.2다음과 같은 타이밍을 얻습니다.

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

3

TypeScript가 필요한 사람들을 위해 (@Danny의 답변이 다시 구현 됨)

/**
 * 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

선택을 위해 배열을 사용하는 실제 간단한 구현 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

단 몇 줄의 최신 JavaScript. Lodash와 같은 외부 라이브러리 또는 종속성이 없습니다.

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

당신 수 reduce2 차원 배열. flatMap누산기 배열에서 사용 하여 acc.length x curr.length각 루프의 조합 수 를 가져옵니다 . [].concat(c, n)c첫 번째 반복의 숫자이고 이후의 배열 이기 때문에 사용됩니다 .

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))

(이것은 Nina Scholz의 답변을 기반으로 함 )


1

제품을 실제로 결과 세트에 추가하기 전에 제품을 필터링하고 수정하는 기능을 추가하는 비재 귀적 접근 방식입니다. .forEach 대신 .map을 사용합니다. 일부 브라우저에서는 .map이 더 빠르게 실행됩니다.

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

간단한 "마음과 시각적으로 친숙한"솔루션.

여기에 이미지 설명 입력


// 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

일반 자바 스크립트로 된 @viebel 코드의 간단한 수정 버전 :

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

더 읽기 쉬운 구현

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])))

이것은 3 개의 배열을위한 것입니다.
일부 답변은 여러 배열에 대한 방법을 제공했습니다.
이는 더 적거나 많은 어레이로 쉽게 축소하거나 확장 할 수 있습니다.
한 세트와 반복의 조합이 필요했기 때문에 다음을 사용할 수있었습니다.

f(a,a,a)

그러나 사용 :

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

0

아무도 함수를 전달하여 각 조합을 처리 할 수있는 솔루션을 게시하지 않았으므로 여기에 내 솔루션이 있습니다.

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))

산출:

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

0

배열 배열을 입력으로 사용하는 일반 JS 무차별 대입 접근 방식입니다.

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>

@dummersl의 답변을 CoffeScript에서 JavaScript 로 변환했습니다 . 그냥 작동합니다.

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

또 다른 구현. 짧거나 화려하지는 않지만 빠릅니다.

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

라이브러리가 필요하지 않습니다! :)

그래도 화살표 기능이 필요하며 그다지 효율적이지 않습니다. : /

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

기록을 위해

여기에 내 버전이 있습니다. 가장 간단한 자바 스크립트 반복자 "for ()"를 사용하여 만들었으므로 모든 경우에 호환되며 최상의 성능을 제공합니다.

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;
}

친애하는.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.