Multiple iterations

风格不统一 提交于 2020-01-14 08:03:06

问题


Is there a easier, cleaner way to write code like this:

(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

Ideally I'd be able to do something like...

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

Or even better...

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

If there's not something built in, how would I write a method like the last one?


回答1:


If you're on Ruby 1.9, you can do this:

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

In Ruby 1.8:

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



回答2:


I've taken the liberty of changing the order of your magic parameters under the assumption that base 10 is more common and optional:

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]

Explanation:

By translating the original problem from 1..10 to 0..9 and concatenating the digits we see that the output is just counting, with access to each digit.

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

So that's what my code above does. It counts from 0 up to the maximum number (based on the number of digits and allowed values per digit), and for each number it:

  1. Converts the number into the appropriate 'base':
    i.to_s(base)            # e.g. 9.to_s(8) => "11", 11.to_s(16) => "b"

  2. Uses String#% to pad the string to the correct number of characters:
    "%#{digits}s" % ...     # e.g. "%4s" % "5" => "   5"

  3. Turns this single string into an array of single-character strings:
    str.scan(/./)           # e.g. " 31".scan(/./) => [" ","3","1"]
    Note that in Ruby 1.9 this is better done with str.chars

  4. Converts each of these single-character strings back into a number:
    n.to_i(base)            # e.g. "b".to_i(16) => 11, " ".to_i(3) => 0

  5. Adds 1 to each of these numbers, since the desire was to start at 1 instead of 0

  6. Splats this new array of numbers as arguments to the block, one number per block param:
    yield *parts




回答3:


dmarkow's solution (I believe) materializes the ranges which, at least in theory, uses more memory than you need. Here's a way to do it without materializing the ranges:

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

That said, performance is a tricky thing and I don't know how efficient Ruby is with function calls, so maybe sticking to library functions is the fastest way to go.

Update: Took Phrogz' suggestion and put magic_ inside magic. (Update: Took Phrogz' suggestion again and hopefully did it right this time with lambda instead of def).

Update: Array#product returns an Array, so I'm assuming that is fully materialized. I don't have Ruby 1.9.2, but Mladen Jablanović pointed out that Array#repeated_permutation probably doesn't materialize the whole thing (even though the initial range is materialized with to_a).



来源:https://stackoverflow.com/questions/5543896/multiple-iterations

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!