【问题标题】:Multiple iterations多次迭代
【发布时间】:2011-07-29 11:17:57
【问题描述】:

有没有一种更简单、更简洁的方式来编写这样的代码:

(1..10).each do |i| 
  (1..10).each do |j|
    (1..10).each do |k|
      (1..10).each do |l|
         puts "#{i} #{j} #{k} #{l}"
      end
    end
  end
end

理想情况下,我可以做类似...

(1..10).magic(4) { |i, j, k, l| puts "#{i} #{j} #{k} #{l}" }

甚至更好...

magic(10, 4) { |i, j, k, l| puts "#{i} #{j} #{k} #{l}" }

如果没有内置的东西,我怎么写像上一个这样的方法?

【问题讨论】:

  • 你期望什么输出?是1 2 3 4 \n 5 6 7 8 \n 9 10 还是0 0 0 1 \n 0 0 0 2 ...
  • @nash 没有;第一个代码自行运行。如果您将所有(1..10) 调整为(0..9),那么您将得到'0 0 0 0', '0 0 0 1',...'9 9 9 8', '9 9 9 9'
  • 这是 stackoverflow.com/questions/5226895/… 的副本,但我更喜欢这个问题的答案。

标签: ruby loops iteration coding-style


【解决方案1】:

如果您使用的是 Ruby 1.9,则可以这样做:

range = (1..10).to_a
range.repeated_permutation(4) do |set|
  puts set.join(" ")
end

在 Ruby 1.8 中:

range = (1..10).to_a
range.product(range, range, range).each do |set|
  puts set.join(" ")
end

【讨论】:

  • 甚至range.product([range]*3).each …
【解决方案2】:

假设 base 10 更常见和可选,我冒昧地更改了 magic 参数的顺序:

def magic(digits,base=10)
  raise "Max magic base of 36" unless base <= 36
  (base**digits).times do |i|
    str   = "%#{digits}s" % i.to_s(base)
    parts = str.scan(/./).map{ |n| n.to_i(base)+1 }
    yield *parts
  end
end

magic(3,2){ |a,b,c| p [a,b,c] }
#=> [1, 1, 1]
#=> [1, 1, 2]
#=> [1, 2, 1]
#=> [1, 2, 2]
#=> [2, 1, 1]
#=> [2, 1, 2]
#=> [2, 2, 1]
#=> [2, 2, 2]

magic(2,16){ |a,b| p [a,b] }
#=> [1, 1]
#=> [1, 2]
#=> [1, 3]
#=> ...
#=> [16, 15]
#=> [16, 16]

解释

通过将原始问题从 1..10 转换为 0..9 并连接数字,我们看到输出只是计数,可以访问每个数字。

0000
0001
0002
...
0010
0011
0012
...
9997
9998
9999

这就是我上面的代码所做的。它从 0 计数到最大数(基于位数和每个位数的允许值),并且对于每个数:

  1. 将数字转换为适当的“基数”:
    i.to_s(base)            # e.g. 9.to_s(8) =&gt; "11", 11.to_s(16) =&gt; "b"

  2. 使用String#% 将字符串填充到正确的字符数:
    "%#{digits}s" % ...     # e.g. "%4s" % "5" =&gt; "   5"

  3. 将此单个字符串转换为单个字符串数组:
    str.scan(/./)           # e.g. " 31".scan(/./) =&gt; [" ","3","1"]
    请注意,在 Ruby 1.9 中,最好使用 str.chars p>

  4. 将这些单字符串中的每一个转换回数字:
    n.to_i(base)            # e.g. "b".to_i(16) =&gt; 11, " ".to_i(3) =&gt; 0

  5. 将每个数字加 1,因为希望从 1 而不是 0 开始

  6. 将这个新的数字数组作为参数传递给块,每个块参数一个数字:
    yield *parts

【讨论】:

  • 这真的很酷。您能否解释一下str =parts = 行?我很难跟上他们。
  • @Drew 我已经更新了答案,并解释了它是如何工作的。
【解决方案3】:

dmarkow 的解决方案(我相信)具体化了范围,至少在理论上,使用的内存超出了您的需要。这是一种无需具体化范围的方法:

def magic(ranges, &block)
  magic_ = lambda do |ranges, args, pos, block|
    if pos == ranges.length
      block.call(*args)
    else
      ranges[pos].each do |i|
        args[pos] = i
        magic_.call(ranges, args, pos+1, block)
      end
    end
  end
  magic_.call(ranges, [nil]*ranges.length, 0, block)
end

magic([1..10] * 4) do |a,b,c,d|
  puts [a, b, c, d].inspect
end

也就是说,性能是一件棘手的事情,我不知道 Ruby 在函数调用方面的效率如何,所以坚持使用库函数可能是最快的方法。

更新:采纳 Phrogz 的建议并将 magic_ 放入 magic。 (更新再次采纳了 Phrogz 的建议,希望这次用lambda 而不是def 做到了。

更新Array#product 返回一个Array,所以我假设它已完全实现。我没有 Ruby 1.9.2,但 Mladen Jablanović 指出 Array#repeated_permutation 可能不会实现整个事情(即使初始范围是用 to_a 实现的)。

【讨论】:

  • repeated_permutation 不应该在没有块的情况下实现整个数组,而是一个枚举器,然后您可以使用each 遍历它,而无需在内存中创建大型结构(希望如此)。
  • 这个表格我差点写了,但是一想到递归就偷懒了。 :) 但是请注意,对于这样的事情,我提倡在您的 magic 方法中创建一个本地 magic_ lambda 并让它递归地调用自己。有了这个,不需要的额外方法就不会造成命名空间污染。但是,+1 是一个非常通用的解决方案。
  • 感谢您采纳我的建议,但您所做的并没有嵌套函数。每次运行magic 方法时,它都会定义一个新的外部magic_ 函数!相反,我建议:def magic(...); magic_ = lambda{ |r,a,p,b| ... magic_[ ... ] }; magic_[ ... ]; end
  • @Phrogz:哎呀!我想我认为它像 Python 一样工作。现在修好了,我希望。
猜你喜欢
  • 2015-07-11
  • 1970-01-01
  • 1970-01-01
  • 2020-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多