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?
a : a procedure in which repetition of a sequence of operations yields results successively closer to a desired result. b : the repetition of a sequence of computer instructions a specified number of times or until a condition is met — compare recursion.
The network was trained by processing 12 iterations of the complete training set. Lighting was also the subject of a computer simulation study, with five iterations undertaken before the final configuration of rooflight was determined.
When you want to skip n values, call next() n+1 times (don't forget to assign the value of the last call to something) and then "call" continue.
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
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:
Converts the number into the appropriate 'base':i.to_s(base) # e.g. 9.to_s(8) => "11", 11.to_s(16) => "b"
Uses String#%
to pad the string to the correct number of characters:"%#{digits}s" % ... # e.g. "%4s" % "5" => " 5"
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
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
Adds 1 to each of these numbers, since the desire was to start at 1 instead of 0
Splats this new array of numbers as arguments to the block, one number per block param:yield *parts
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
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With