【问题标题】:Given an array of size y, containing arrays of size n, how can I return all logical combinations using Ruby?给定一个大小为 y 的数组,包含大小为 n 的数组,如何使用 Ruby 返回所有逻辑组合?
【发布时间】:2017-05-02 21:16:07
【问题描述】:

我想做的是处理 n 个集合,而我在下面提供的代码正好可以处理 4 个集合。

def show_combinations
  @combos = []
  ['A', 'no A'].each do |a|
    ['B', 'no B'].each do |b|
      ['C', 'no C'].each do |c|
        ['D', 'no D'].each do |d|
          @combos << [a, b, c, d]
        end
      end
    end
  end
end

如何重构以下代码来处理以下场景: 假设我有一个大小为 y 的数组,其中包含大小为 n 的数组,我想返回所有组合。 请务必注意,每个子数组中只能有一个项目出现在结果中。 (如“已完成配置文件”也不能出现在带有“未完成配置文件”的结果中)

背景:

用户可能有一些任务:例如,“完成个人资料”或“设置电子邮件”等。这些任务可以这样表示:

@task_states = [["Completed Profile, NOT Completed Profile"], ["Set up Email", "NOT set up Email"]]

然后,将@task_states 传递给方法,结果应该是这样的:

[
["Completed Profile", "Set up Email"],
["Completed Profile", "NOT set up Email"],
["NOT Completed Profile", "Set up Email"],
["NOT Completed Profile", "NOT Set up Email"]
]

所以一个代表所有组合的数组数组。显然“Completed Profile”也不能和“NOT Completed Profile”等在同一个数组中。

谢谢!

【问题讨论】:

标签: arrays ruby


【解决方案1】:

您似乎想要计算数组的笛卡尔积。计算笛卡尔积的方法(并不奇怪)称为Array#product

@task_states.first.product(*@task_states.drop(1))

所以,例如:

['A', 'no A'].product(['B', 'no B'], ['C', 'no C'], ['D', 'no D'])
#=> [[   "A",    "B",    "C",    "D"],
#    [   "A",    "B",    "C", "no D"],
#    [   "A",    "B", "no C",    "D"],
#    [   "A",    "B", "no C", "no D"],
#    [   "A", "no B",    "C",    "D"],
#    [   "A", "no B",    "C", "no D"],
#    [   "A", "no B", "no C",    "D"],
#    [   "A", "no B", "no C", "no D"],
#    ["no A",    "B",    "C",    "D"],
#    ["no A",    "B",    "C", "no D"],
#    ["no A",    "B", "no C",    "D"],
#    ["no A",    "B", "no C", "no D"],
#    ["no A", "no B",    "C",    "D"],
#    ["no A", "no B",    "C", "no D"],
#    ["no A", "no B", "no C",    "D"],
#    ["no A", "no B", "no C", "no D"]]

【讨论】:

    【解决方案2】:

    无论如何,请使用Array#product,但与Ruby 一样,还有其他方法。这里有两个。

    arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    

    使用数学计算每个arr.size**arr.first.size 组合

    def cartesian_product(arr)
      n, m = arr.size, arr.first.size
      (0..n**m-1).map do |x|
        (n-1).downto(0).map do |i|
          x, r = x.divmod(m)
          arr[i][r]
        end.reverse
      end
    end
    
    cartesian_product arr
      #=> [[1, 4, 7], [1, 4, 8], [1, 4, 9], [1, 5, 7], [1, 5, 8], [1, 5, 9],
      #    [1, 6, 7], [1, 6, 8], [1, 6, 9],
      #    [2, 4, 7], [2, 4, 8], [2, 4, 9], [2, 5, 7], [2, 5, 8], [2, 5, 9],
      #    [2, 6, 7], [2, 6, 8], [2, 6, 9],
      #    [3, 4, 7], [3, 4, 8], [3, 4, 9], [3, 5, 7], [3, 5, 8], [3, 5, 9],
      #    [3, 6, 7], [3, 6, 8], [3, 6, 9]]
    

    使用递归

    def cartesian_product(arr)
      first, *rest = arr
      return first.map { |o| [o] } if rest.empty?
      c = cartesian_product(rest)
      first.flat_map { |o| c.map { |a| [o] + a } }
    end
    
    cartesian_product arr
      #=> same as above
    

    在此应用程序中,arr 的每个元素(数组)不包含重复项。在其他情况下,可能存在重复项(并且返回的产品不包含重复项),应首先计算

    arr_without_dups = arr.map(&:uniq)
    

    然后从arr_without_dups计算组合。

    【讨论】:

      【解决方案3】:

      虽然提供的答案很棒而且我接受了 Jörg 的答案,但我想分享一下我的一位朋友提供的答案,以防其他人遇到这个问题。本质上这是 Jörg 的回答,但更深入一点。

      笛卡尔积 您所要求的称为Cartesian Product,它已经内置于Array#product

      例如:

      [0, 1].product([0, 1])
      # => [[0, 0], [0, 1], [1, 0], [1, 1]]
      

      请注意,这为我们提供了所有可能的 2 位数字,从 00 到 11

      你可以给 Array#product 任意数量的数组:

      [0, 1].product([0, 1], [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]]
      

      请注意,这为我们提供了所有可能的 3 位数字,从 000 到 111。

      您的问题 查看您的确切问题:

      ["A", "no A"].product(["B", "no B"], ["C", "no C"], ["D", "no D"])
      => [["A", "B", "C", "D"],
       ["A", "B", "C", "no D"],
       ["A", "B", "no C", "D"],
       ["A", "B", "no C", "no D"],
       ["A", "no B", "C", "D"],
       ["A", "no B", "C", "no D"],
       ["A", "no B", "no C", "D"],
       ["A", "no B", "no C", "no D"],
       ["no A", "B", "C", "D"],
       ["no A", "B", "C", "no D"],
       ["no A", "B", "no C", "D"],
       ["no A", "B", "no C", "no D"],
       ["no A", "no B", "C", "D"],
       ["no A", "no B", "C", "no D"],
       ["no A", "no B", "no C", "D"],
       ["no A", "no B", "no C", "no D"]]
      gives you all the possible combinations from "ABCD" to "noA noB noC noD"
      

      通用解决方案 我们可以通过使用 splat * 操作符来处理任何通用的数组数组。

      def combinations(arrays)
        first, *rest = arrays
        first.product(*rest)
      end
      

      那么我们可以说:

      arrays_to_combine = [["A", "no A"], ["B", "no B"], ["C", "no C"], ["D", "no D"]]
      combinations(arrays_to_combine)
      => [["A", "B", "C", "D"],
       ["A", "B", "C", "no D"],
       ["A", "B", "no C", "D"],
       ["A", "B", "no C", "no D"],
       ["A", "no B", "C", "D"],
       ["A", "no B", "C", "no D"],
       ["A", "no B", "no C", "D"],
       ["A", "no B", "no C", "no D"],
       ["no A", "B", "C", "D"],
       ["no A", "B", "C", "no D"],
       ["no A", "B", "no C", "D"],
       ["no A", "B", "no C", "no D"],
       ["no A", "no B", "C", "D"],
       ["no A", "no B", "C", "no D"],
       ["no A", "no B", "no C", "D"],
       ["no A", "no B", "no C", "no D"]]
      

      特别感谢 Thoughtbot 的 Joel Quenneville 帮助我以及其他提供答案的人。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-02-11
        • 1970-01-01
        • 1970-01-01
        • 2019-05-27
        • 1970-01-01
        • 2020-04-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多