多次迭代

时间:2011-04-04 20:21:59

标签: ruby loops iteration coding-style

是否有更简单,更清晰的方式来编写这样的代码:

(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}" }

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

3 个答案:

答案 0 :(得分:9)

如果你使用的是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

答案 1 :(得分:2)

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,但是MladenJablanović指出Array#repeated_permutation可能没有实现整个事情(尽管初始范围是用to_a实现的。)

答案 2 :(得分:2)

我假设基础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) => "11", 11.to_s(16) => "b"

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

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

  4. 将每个单字符字符串转换回数字:
    n.to_i(base)            # e.g. "b".to_i(16) => 11, " ".to_i(3) => 0

  5. 为这些数字中的每一个添加1,因为希望从1开始而不是0

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