【问题标题】:algorithm to merge two arrays into an array of all possible combinations将两个数组合并为所有可能组合的数组的算法
【发布时间】:2018-03-15 04:20:40
【问题描述】:

JavaScript 中给出的示例:

假设我们有两个数组 [0,0,0] 和 [1,1,1]。产生这两个数组可以组合的所有可能方式的算法是什么。示例:

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

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

我也不确定这种组合叫什么。如果算法或技术有名称,请分享。

【问题讨论】:

标签: javascript arrays algorithm math functional-programming


【解决方案1】:

您可以将值转换为这种格式的数组

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

【讨论】:

  • 太棒了,谢谢!这种算法或技术有名称吗?
  • @Babak,我知道它没有特别的名字。
  • 本质上只是二进制计数;)
  • @MBo,对,在这​​种情况下,但是如果你采用像[[0, 1], [0, 1, 2], [0, 1]] 这样的值数组,那么你需要一种不同的方法。
  • @Nina Scholz 是的,虽然没有指定如何处理不同长度的数组。
【解决方案2】:

继续

这是一个涉及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 本身的嵌套——例如,在上面的代码中,我们有 3 层嵌套来生成长度为 3 的排列——如果我们想排列我们的选择 4 、5 次还是 N 次?

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

【讨论】:

  • 这是一个非常好的答案。如何将这些函数组合起来生成一个函数,该函数采用两个数组,每个数组都有不同的元素?例如f(['a','b','c'],['x','y','z']) 产生[ ['x','b','c'], ['a','y','c'], ....]
  • 我不确定我是否遵循这种模式;你能为我列举出整个结果吗?
  • @Babak,基于这里的其他一些 cmets,我想我拼凑了你想要完成的事情。另请参阅我发布的其他答案。希望你能原谅我^_^
  • 太棒了。我想在函数式编程方面做得更好。你推荐什么函数式编程书籍?
  • Structure and Interpretation of Computer Programs – 我花了很长时间才能通过它,因为它向我介绍了很多我不知道的东西;这导致了几个月的边学习,实际上并没有在书中进一步深入。已经有两年多的时间了——现在几乎完成了^_^
【解决方案3】:

列表单子

写这么长的关于delimited whatchamacallits 的东西的人太疯狂了——在我花了 3 个小时试图弄清楚之后,我会在 30 秒内忘记它的一切!

更严肃地说,与这个答案相比,shift/reset 是如此的不切实际,简直是个笑话。但是,如果我不先分享这个答案,我们就不会享受将我们的大脑翻过来的快乐!所以请不要联系shift/reset,除非他们对手头的任务很重要——如果你觉得学习一些非常酷的东西,请原谅我!

我们不要忽略一个更直接的解决方案,List monad - lovely 在此处使用 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 接口由一些 unit (a -> Monad a) 和 bind (Monad a -> (a -> Monad b) -> Monad b) 函数组成 – chain 是我们的 bind 在这里,JavaScript 的数组字面量语法 ([someValue]) 提供了我们的 unit – 仅此而已


哦,你不能碰原生原型!!

好的,有时我们有充分的理由不接触原生原型。别担心,只需为数组创建一个数据构造函数;我们将其称为List——现在我们有一个地方来定义我们的预期行为

如果您喜欢这个解决方案,您可能会发现another answer I wrote 很有用;该程序使用 list monad 使用查询路径从数据源中获取 1 个或多个值

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

【讨论】:

  • 很好的答案。对它的支持不多(但),但chain 可以替换为新的flatMap developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
【解决方案4】:

您可以使用 lodash,这是它们的实现:

(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”、“1”]、[“1”、“1”、“0”]、[“1”、“1”、“0”]、[“1” , "1", "0"], [“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】:

    请注意,数组长度 N2^N 组合。 0..2^N-1 范围内的每个整数都对应于某种组合:如果数字的第 k 位为零,则从第一个数组中获取结果的第 k 个元素,否则 - 从第二个数组中获取。

    附:请注意,您的示例等效于数字的二进制表示。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-08
      • 2021-07-16
      • 2020-02-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多