【问题标题】:Algorithm to produce Cartesian product of arrays in depth-first order以深度优先顺序生成数组笛卡尔积的算法
【发布时间】:2011-04-06 23:34:35
【问题描述】:

我正在寻找一个示例,说明如何在 Ruby、类似 C 的语言或伪代码中创建可变数量的整数数组的笛卡尔积,每个数组的长度不同,并逐步查看结果一个特定的顺序:

所以给定,[1,2,3],[1,2,3],[1,2,3]:

[1, 1, 1]
[2, 1, 1]
[1, 2, 1]
[1, 1, 2]
[2, 2, 1]
[1, 2, 2]
[2, 1, 2]
[2, 2, 2]
[3, 1, 1]
[1, 3, 1]
etc.

而不是我看到的典型结果(包括我在下面给出的示例):

[1, 1, 1]
[2, 1, 1]
[3, 1, 1]
[1, 2, 1]
[2, 2, 1]
[3, 2, 1]
[1, 3, 1]
[2, 3, 1]
etc.

此示例的问题在于,在尝试了前两个位置的所有组合之前,根本不会探索第三个位置。在使用它的代码中,这意味着即使正确答案通常是(更大的等值)1、1、2,它也会检查几百万个可能性,而不是在找到它之前只检查几千个。

我正在处理一百万到数亿的结果集,因此在这里生成它们然后排序是不可行的,并且会破坏在第一个示例中对它们进行排序的原因,即更快地找到正确的答案所以更早地突破了笛卡尔积。

以防万一它有助于澄清上述任何内容,这就是我现在如何执行此操作(这具有正确的结果和正确的性能,但不是我想要的顺序,即它创建的结果如上面第二个列表所示):

def cartesian(a_of_a)
  a_of_a_len = a_of_a.size
  result = Array.new(a_of_a_len)
  j, k, a2, a2_len = nil, nil, nil, nil
  i = 0
  while 1 do
    j, k = i, 0
    while k < a_of_a_len
      a2 = a_of_a[k]
      a2_len = a2.size
      result[k] = a2[j % a2_len]
      j /= a2_len
      k += 1
    end

    return if j > 0
    yield result

    i += 1
  end

end

更新: 我没有说得很清楚,我正在寻求一个解决方案,其中在添加 3 之前检查 1,2 的所有组合,然后是所有 3 和 1,然后是所有 3、2 和 1,然后是所有 3,2 .换句话说,在“垂直”之前“水平”探索所有早期的组合。探索这些可能性的确切顺序(即 1,1,2 或 2,1,1)无关紧要,只需在混合 3 之前探索所有 2 和 1,依此类推。

【问题讨论】:

  • 我不确定你的算法。如果你“水平”地用尽组合,不是所有 3、2 都在所有 3、2、1 之前吗?
  • 如果您的意思是 3,2,2 和 2,3,2 会在 3,2,1 之前出现,那么不,不是我更喜欢它的工作方式。但是您的订单会比 1,1,1 2,1,1, 3,1,1, 1,2,1, 1,3,1 等要好得多。我想要摆脱的是第一列中的每个值都被尝试过,然后第二列中的每个值都被尝试过——这意味着最后一列甚至没有前进一个位置,直到进入序列的很远(因此在我正在使用的几百万次毫无意义的迭代之后。 )

标签: ruby algorithm cartesian-product


【解决方案1】:

嘿,Marc-André,cartesian gem 完全符合您的要求:

require 'cartesian'
[1,2,3].x([1,2,3]).to_a #=> [[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1], [3, 2], [3, 3]]

您也可以使用 **(幂)运算符来简洁

for a,b,c in [1,2,3]**3 ; p [a,b,c] ; end
# output:
#    [1, 1, 1]
#    [1, 1, 2]
#    [1, 1, 3]
#    [1, 2, 1]
#    ...
#    [3, 3, 3]

该项目托管在 github 上,在其 homepage 中有 RDoc 文档的链接。

【讨论】:

    【解决方案2】:

    在问题的准确性之后,这是一个修订版。我保留上一个答案,因为它也很有用并且使用的顺序不太复杂。

    # yields the possible cartesian products of [first, *rest], where the total
    # of the indices that are "distributed" is exactly +nb+ and each index doesn't
    # go beyong +depth+, but at least one of them is exactly +depth+
    def distribute(nb, depth, reached, first, *rest)
      from  = [nb - rest.size * depth, 0].max
      to    = [first.size-1, depth, nb].min
      from.upto(to) do |i|
        obj = first[i]
        reached ||= i == depth
        if rest.empty?
          yield [obj] if reached
        else
          distribute(nb - i, depth, reached, *rest) do |comb|
            yield [obj, *comb]
          end
        end
      end
    end
    
    def depth_first_cartesian(*arrays)
      return to_enum __method__, *arrays unless block_given?
      lengths = arrays.map(&:length)
      total = lengths.inject(:+)
      lengths.max.times do |depth|
        depth.upto(arrays.size * depth) do |nb|
          distribute(nb, depth, false, *arrays) {|c| yield c}
        end
      end
    end
    
    p depth_first_cartesian([1, 2, 3], [1, 2, 3, 4], [1, 2, 3]).to_a
    # => [[1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [1, 2, 2], [2, 1, 2], [2, 2, 1], [2, 2, 2],
    #     [1, 1, 3], [1, 3, 1], [3, 1, 1], [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2],
    #     [3, 2, 1], [1, 3, 3], [2, 2, 3], [2, 3, 2], [3, 1, 3], [3, 2, 2], [3, 3, 1], [2, 3, 3],
    #     [3, 2, 3], [3, 3, 2], [3, 3, 3], [1, 4, 1], [1, 4, 2], [2, 4, 1], [1, 4, 3], [2, 4, 2],
    #     [3, 4, 1], [2, 4, 3], [3, 4, 2], [3, 4, 3]]
    

    【讨论】:

    • 感谢 Marc-André,这正是我所需要的。我必须找到一种方法来创建临时数组(当结果达到数千万时,GC 会杀死),如果您对此有想法,我很想知道。但是,这确实超出了问题的范围,因此再次感谢您的两个答案。
    • @Yuri:应该不会太难,这取决于你的 Ruby 技能。但基本上,修改你的“答案”数组并产生它,而不是像我一样产生新的数组。此外,最好避免像我一样传递数组数组并拆分它(首先,*rest),因为这也会创建中间数组。我们可以在 SO 上提供服务吗? ;-)
    【解决方案3】:

    尚不清楚元素 [1, 1, 3] 在所需输出中的位置。如果我的猜测是正确的,下面的作品(虽然它可能会被优化)

    # yields the possible cartesian products of [first, *rest], where the total
    # of the indices that are "distributed" is exactly +nb+.
    def distribute(nb, first, *rest)
      if rest.empty?                    # single array remaining?
        yield first.fetch(nb) {return}  # yield the right element (if there is one)
      else
        first.each_with_index do |obj, i|
          break if i > nb
          distribute(nb - i, *rest) do |comb|
            yield [obj, *comb]
          end
        end
      end
    end
    
    def strange_cartesian(*arrays, &block)
      return to_enum __method__, *arrays unless block_given?
      max = arrays.map(&:length).inject(:+)
      max.times do |nb|
        distribute(nb, *arrays, &block)
      end
    end
    
    p strange_cartesian([1, 2, 3], [1, 2, 3], [1, 2, 3]).to_a
    #  => [[1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [1, 1, 3], [1, 2, 2], [1, 3, 1], [2, 1, 2], [2, 2, 1], [3, 1, 1], [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 2, 2], [2, 3, 1], [3, 1, 2], [3, 2, 1], [1, 3, 3], [2, 2, 3], [2, 3, 2], [3, 1, 3], [3, 2, 2], [3, 3, 1], [2, 3, 3], [3, 2, 3], [3, 3, 2], [3, 3, 3]]
    

    注意:如果您仍在运行 Ruby 1.8.6,请至少升级到 1.8.7(或require 'backports'

    【讨论】:

    • 很好地使用了__method__。我完全忘记了这一点。我实际上记得想要一种从方法内部引用该方法的方法。必须立即挖掘该代码并对其进行重构...
    • 很好,它给出的订单比我的更接近我所追求的。我试图澄清我的问题以解决剩余的差异。但是如果我可以重新调整以不需要 gc 可能就足够了,我现在就试试。
    猜你喜欢
    • 2018-08-31
    • 2016-10-08
    • 1970-01-01
    • 2021-05-06
    • 1970-01-01
    • 2021-09-05
    • 2016-05-07
    • 2015-10-21
    • 2011-04-29
    相关资源
    最近更新 更多