3

The thing I love about Ruby is its elegance: if we use inject or map along with take_while and select, we can chain blocks together to achieve a lot while writing little.

Sticking with the idea of single line solutions, how would one write a nested for loop in Ruby without writing the entire nested for loop? I feel it must be possible, I just can't for the life of me figure out what it is. I am looking for something like this:

10.times {|a| 10.times {|b| a*b}}

The only solution I can come up with that is at all elegant is nested for loops. Does anyone have a better solution?

array = []
for a in (1..10)
  for b in (1..10)
    array << a*b
  end
end
Mike H-R
  • 7,726
  • 5
  • 43
  • 65
  • "how would one write a nested for loop in ruby without writing the entire nested for loop"? You have to define the loop somehow. You can't define an incomplete loop and have Ruby run it because it would be a syntax error. – the Tin Man Jul 09 '13 at 04:59
  • The code in the answer above contains parse error: the parser will see the `< – SasQ Aug 23 '13 at 05:47
  • @SasQ good point, fixed now. – Mike H-R Aug 24 '13 at 02:15

7 Answers7

7

Array has some cool methods.

 Array(1..10).repeated_permutation(2).map {|a, b| a*b }

#repeated_permutation will take an array and generate an array of all permutations of that array of a given length (2, in this case), permitting for repetition (ie, [1,1]). We can then just map the product of each pair into a final array.

You can generalize this by using inject(:*). This will take the resultant permutations and multiply all the elements of each. For example, to generate (1*1*1*1)..(10*10*10*10) (resulting in an output set of 10,000 elements!):

Array(1..10).repeated_permutation(4).map {|v| v.inject :*}
Chris Heald
  • 61,439
  • 10
  • 123
  • 137
  • It's an effective trick, but somehow, `#repeated_permutation` is not meant for this. +1 for teaching the noobs the method, but please, don't use it like this. – Boris Stitnicky Jul 09 '13 at 06:42
  • Your Matrix solution is more code, is an order of magnitude slower, and uses nearly twice as much RAM to achieve the same result. For a square matrix, `#repeated_permutation` is just fine. – Chris Heald Jul 09 '13 at 07:11
  • this is the kind of thing I was looking for, thanks for teaching me a cool new trick! – Mike H-R Jul 09 '13 at 11:32
4
(1..10).to_a.product((1..10).to_a).map { |a,b| a*b }

http://ruby-doc.org/core-2.0/Array.html#method-i-product

falsetru
  • 357,413
  • 63
  • 732
  • 636
  • @BorisStitnicky would you consider this to be more efficient than the repeated_perutation method above? I would assume it would be slower given the intermedite arrays it would be creating and then flattening. – Mike H-R Jul 09 '13 at 11:39
  • Benchmarking shows that #repeated_permutation is indeed faster and more efficient, likely for the reasons you guessed. It's effectively `x.product(x)`. – Chris Heald Jul 09 '13 at 14:25
3

The only solution I can come up with that is at all elegant is nested for loops

for-in loops call each() on the object to the right of in, so rubyists don't use for-in loops--they call each() directly on the object:

array = []

(1..10).each do |a|
  (1..3).each do |b|
    array << a*b
  end
end

Sticking with the idea of single line solutions

Doing that will nearly guarantee you don't write elegant ruby code--just look at the proposed solutions.

7stud
  • 46,922
  • 14
  • 101
  • 127
2
arr = (1..10).map {|a| (1..10).map {|b| a*b}}.flatten
Platinum Azure
  • 45,269
  • 12
  • 110
  • 134
  • ahh, this is what I was originally trying to do, made me realise I should pay attention to the difference between [map and each](http://stackoverflow.com/questions/9429034/what-is-the-difference-between-map-each-and-collect) can anyone explain this though: `(1..10).each {|a|a} => 1..10 ` – Mike H-R Jul 09 '13 at 11:31
  • `#each` will return the original object passed to it after iteration (in this case, a range). `#map` will collect the results of the block into an array and return it. – Chris Heald Jul 09 '13 at 14:26
1

Looking at all the answers to this question, I don't feel any of them appear more "elegant" or easier to read than the OP's nested for loops. If you want a less verbose notation for nested iteration, I don't think you'll do better than defining your own shorthand. Something like:

 module Enumerable
   def combinations(*others)
     return enum_for(:combinations,*others) if not block_given?
     return if self.empty?
     if others.empty?
       self.each { |x| yield [x] }
     else
       others.first.combinations(*others.drop(1)) { |a| self.each { |x| yield (a + [x]) }}
     end
   end
 end

Having defined this utility method, you can write your example of nested iteration as:

array = []
(1..10).combinations(1..10) { |a,b| array << a*b }
Alex D
  • 29,755
  • 7
  • 80
  • 126
  • I'm not sure if my invocation of `enum_for` is quite right, I'd have to check. If anyone else knows the right way, please edit. – Alex D Jul 09 '13 at 07:00
0
(1..10).inject([]) { |result,a| result + (1..10).to_a.map { |b| a*b } }

Or

def arithmetic(range, &block)
  range.inject([]) { |result,a| result + range.to_a.map { |b| block.call(a,b) } }
end

range = (1..10)
arithmetic(range) {|a,b| a*b }
arithmetic(range) {|a,b| a+b }
Matt Dressel
  • 2,194
  • 16
  • 18
0

Facing problems like these, remember your high school calculus:

a = *1..10
b = *1..10
require 'matrix'

Matrix.column_vector( a ) * Matrix[ b ]
# or equivalent
Matrix[ a ].transpose * Matrix[ b ]

Matrix is a part of Ruby stdlib and every serious Ruby speaker should learn its interface.

Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74