代码之家  ›  专栏  ›  技术社区  ›  Babakness

将两个数组合并为所有可能组合的数组的算法

  •  2
  • Babakness  · 技术社区  · 8 年前

    JavaScript示例:

    mergeEveryWayPossible([0,0,0],[1,1,1])
    // [ [0,0,0],[1,0,0], [0,1,0], [0,0,1], [1,1,0], [0,1,1], [1,0,1], [1,1,1] ]
    

    将数组合并为所有可能组合的数组。这与求笛卡尔积不同。

    6 回复  |  直到 8 年前
        1
  •  3
  •   Nina Scholz    8 年前

    [
        [0, 1],
        [0, 1],
        [0, 1]
    ]
    

    然后通过迭代外部和内部数组来构建新的结果集。

    var data = [[0, 0, 0], [1, 1, 1]],
        values = data.reduce((r, a, i) => (a.forEach((b, j) => (r[j] = r[j] || [])[i] = b), r), []),
        result = values.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
        
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }
        2
  •  2
  •   Mulan    8 年前

    延续

    这里有一个解决方案涉及 delimited continuations 可组合

    // identity :: a -> a
    const identity = x =>
      x
    
    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = f => ([x,...xs]) =>
      x === undefined
        ? []
        : f (x) .concat (concatMap (f) (xs))
    
    // cont :: a -> cont a
    const cont = x =>
      k => k (x)
    
    // reset :: cont a -> (a -> b) -> b
    const reset = m =>
      k => m (k)
    
    // shift :: ((a -> b) -> cont a) -> cont b
    const shift = f =>
      k => f (x => k (x) (identity))
      
    // amb :: [a] -> cont [a]
    const amb = xs =>
      shift (k => cont (concatMap (k) (xs)))
    
    // demo
    reset (amb (['J', 'Q', 'K', 'A']) (x =>
             amb (['♡', '♢', '♤', '♧']) (y =>
               cont ([[x, y]]))))
          (console.log)
          
    // [ ['J','♡'], ['J','♢'], ['J','♤'], ['J','♧'], ['Q','♡'], ['Q','♢'], ['Q','♤'], ['Q','♧'], ['K','♡'], ['K','♢'], ['K','♤'], ['K','♧'], ['A','♡'], ['A','♢'], ['A','♤'], ['A','♧'] ]

    const choices =
      [0,1]
    
    reset (amb (choices) (x =>
            amb (choices) (y =>
              amb (choices) (z =>
                cont ([[x, y, z]])))))
          (console.log)
    
    // [ [0,0,0], [0,0,1], [0,1,0], [0,1,1], [1,0,0], [1,0,1], [1,1,0], [1,1,1] ]
    

    但你一定想知道我们如何提取 amb

    const permute = (n, choices) =>
      {
        const loop = (acc, n) =>
          n === 0
            ? cont ([acc])
            : amb (choices) (x =>
                loop (acc.concat ([x]), n - 1))
        return loop ([], n)
      }
    
    permute (4, [true,false]) (console.log)
    // [ [ true , true , true , true  ],
    //   [ true , true , true , false ],
    //   [ true , true , false, true  ],
    //   [ true , true , false, false ],
    //   [ true , false, true , true  ],
    //   [ true , false, true , false ],
    //   [ true , false, false, true  ],
    //   [ true , false, false, false ],
    //   [ false, true , true , true  ],
    //   [ false, true , true , false ],
    //   [ false, true , false, true  ],
    //   [ false, true , false, false ],
    //   [ false, false, true , true  ],
    //   [ false, false, true , false ],
    //   [ false, false, false, true  ],
    //   [ false, false, false, false ] ]
    

    听起来像德语什么的

    如果我正确地理解了你的评论,你想要一种可以对输入进行压缩并对每一对进行排列的东西,我们称之为, zippermute ?

    const zippermute = (xs, ys) =>
      {
        const loop = (acc, [x,...xs], [y,...ys]) =>
          x === undefined || y === undefined
            ? cont ([acc])
            : amb ([x,y]) (choice =>
                loop (acc.concat ([choice]), xs, ys))
        return loop ([], xs, ys)
      }
    
    zippermute (['a', 'b', 'c'], ['x', 'y', 'z']) (console.log)
    // [ [ 'a', 'b', 'c' ],
    //   [ 'a', 'b', 'z' ],
    //   [ 'a', 'y', 'c' ],
    //   [ 'a', 'y', 'z' ],
    //   [ 'x', 'b', 'c' ],
    //   [ 'x', 'b', 'z' ],
    //   [ 'x', 'y', 'c' ],
    //   [ 'x', 'y', 'z' ] ]
    
        3
  •  1
  •   Mulan    8 年前

    列出单子

    delimited whatchamacallits 我花了3个小时想弄明白,我会在30秒内忘记一切!

    更严重的是,与这个答案相比 shift / reset 转移 / 重置

    我们不要忽视一个更简单的解决方案,列表单子 Array.prototype.chain 这里还要注意这个解和连续解之间的结构相似性。

    // monads do not have to be intimidating
    // here's one in 2 lines†
    Array.prototype.chain = function chain (f)
      {
        return this.reduce ((acc, x) =>
          acc.concat (f (x)), [])
      };
    
    const permute = (n, choices) =>
      {
        const loop = (acc, n) =>
          n === 0
            ? [acc]
            : choices.chain (choice =>
                loop (acc.concat ([choice]), n - 1))
        return loop ([], n)
      }
    
    console.log (permute (3, [0,1]))
    // [ [ 0, 0, 0 ],
    //   [ 0, 0, 1 ],
    //   [ 0, 1, 0 ],
    //   [ 0, 1, 1 ],
    //   [ 1, 0, 0 ],
    //   [ 1, 0, 1 ],
    //   [ 1, 1, 0 ],
    //   [ 1, 1, 1 ] ]
    
    const zippermute = (xs, ys) =>
      {
        const loop = (acc, [x,...xs], [y,...ys]) =>
          x === undefined || y === undefined
            ? [acc]
            : [x,y].chain (choice =>
                loop (acc.concat ([choice]), xs, ys))
        return loop ([], xs, ys)
      }
    
    console.log (zippermute (['a', 'b', 'c'], ['x', 'y', 'z']))
    // [ [ 'a', 'b', 'c' ],
    //   [ 'a', 'b', 'z' ],
    //   [ 'a', 'y', 'c' ],
    //   [ 'a', 'y', 'z' ],
    //   [ 'x', 'b', 'c' ],
    //   [ 'x', 'b', 'z' ],
    //   [ 'x', 'y', 'c' ],
    //   [ 'x', 'y', 'z' ] ]

    monad接口由一些 单元 a -> Monad a )和 绑定 Monad a -> (a -> Monad b) -> Monad b )功能 chain 绑定 [someValue] )提供我们的 单元


    好吧,有时候有很好的理由不去碰原生原型。不用担心,只需为数组创建一个数据构造函数;我们会叫它 List

    another answer I wrote

    const List = (xs = []) =>
      ({
        value:
          xs,
        chain: f =>
          List (xs.reduce ((acc, x) =>
            acc.concat (f (x) .value), []))
      })
    
    const permute = (n, choices) =>
      {
        const loop = (acc, n) =>
          n === 0
            ? List ([acc])
            : List (choices) .chain (choice =>
                loop (acc.concat ([choice]), n - 1))
        return loop ([], n) .value
      }
    
    console.log (permute (3, [0,1]))
    // [ [ 0, 0, 0 ],
    //   [ 0, 0, 1 ],
    //   [ 0, 1, 0 ],
    //   [ 0, 1, 1 ],
    //   [ 1, 0, 0 ],
    //   [ 1, 0, 1 ],
    //   [ 1, 1, 0 ],
    //   [ 1, 1, 1 ] ]
    
    const zippermute = (xs, ys) =>
      {
        const loop = (acc, [x,...xs], [y,...ys]) =>
          x === undefined || y === undefined
            ? List ([acc])
            : List ([x,y]).chain (choice =>
                loop (acc.concat ([choice]), xs, ys))
        return loop ([], xs, ys) .value
      }
    
    console.log (zippermute (['a', 'b', 'c'], ['x', 'y', 'z']))
    // [ [ 'a', 'b', 'c' ],
    //   [ 'a', 'b', 'z' ],
    //   [ 'a', 'y', 'c' ],
    //   [ 'a', 'y', 'z' ],
    //   [ 'x', 'b', 'c' ],
    //   [ 'x', 'b', 'z' ],
    //   [ 'x', 'y', 'c' ],
    //   [ 'x', 'y', 'z' ] ]
        4
  •  0
  •   Tony Tai Nguyen    8 年前

    您可以使用 洛达斯 ,以下是它们的实现:

    (function(_) {
    
        _.mixin({combinations: function(values, n) {
            values = _.values(values);
            var combinations = [];
            (function f(combination, index) {
                if (combination.length < n) {
                    _.find(values, function(value, index) {
                        f(_.concat(combination, [value]), index + 1);
                    }, index);
                } else {
                    combinations.push(combination);
                }
            })([], 0);
            return combinations;
        }});
    
    })((function() {
        if (typeof module !== 'undefined' && typeof exports !== 'undefined' && this === exports) {
            return require('lodash');
        } else {
            return _;
        }
    }).call(this));
    
    console.log(_.combinations('111000', 3))
    console.log(_.combinations('111000', 3).length + " combinations available");
    

    ["1", "1", "0"], ["1", "1", "0"], ["1", "1", "0"], ["1", "0", "0"], ["1", "0", "0"], ["1", "0", "0"], ["1", "1", "0"], ["1", "1", "0"], ["1", "1", "0"], ["1", "0", "0"], ["1", "0", "0"], ["1", "0", "0"], ["1", "0", "0"], ["1", "0", "0"], ["1", "0", "0"], ["0", "0", "0"]]

    “20种组合可用”

    https://github.com/SeregPie/lodash.combinations

        5
  •  0
  •   MBo    8 年前

    注意,对于数组长度 N 2^N 0..2^N-1

    P、 请注意,您的示例相当于数字的二进制表示。

        6
  •  0
  •   RobKohr    4 年前

    所有数字都代表数组,这是一个增加集合的问题,携带任何一个,最后在计数器中再次循环回到全零,以表明我们已经完成了集合。

    这也使得我们不必进行任何递归,只需使用while循环即可。

    function allPermutationsOfArrays(arrayOfArrays) {
      const out = [];
      // this counter acts like an incrementing number
      const incrementalSet = arrayOfArrays.map(() => 0);
      // max values for each "digit" of the counter
      const maxValues = arrayOfArrays.map((a) => a.length - 1);
      while (1) {
        const outRow = [];
        // for the current counter incrementer, get the array values and 
        // put them together for output
        for (let i = 0; i < incrementalSet.length; i++) {
          outRow[i] = arrayOfArrays[i][incrementalSet[i]];
        }
        out.push(outRow);
        //add one to incremental set - we are going right to left so it works like
        // normal numbers, but really that is arbitrary and we could go left to right
        let allZeros = true;
        for (let i = incrementalSet.length - 1; i >= 0; i--) {
          if (incrementalSet[i] + 1 > maxValues[i]) {
            incrementalSet[i] = 0;
            continue; //carry the one to the next slot
          } else {
            incrementalSet[i] += 1;
            allZeros = false;
            break; // nothing to carry over
          }
        }
        if (allZeros) {
          // we have done all combinations and are back to [0, 0,...];
          break; // break the while(1) loop
        }
      }
      return out;
    }
    console.log(
      allPermutationsOfArrays([
        [0, 1, 2],
        ["a", "b"],
      ])
    );
    // [
    //   [ 0, 'a' ],
    //   [ 0, 'b' ],
    //   [ 1, 'a' ],
    //   [ 1, 'b' ],
    //   [ 2, 'a' ],
    //   [ 2, 'b' ]
    // ]
    推荐文章