Reputation: 1784
Python has a nice feature:
print([j**2 for j in [2, 3, 4, 5]]) # => [4, 9, 16, 25]
In Ruby it's even simpler:
puts [2, 3, 4, 5].map{|j| j**2}
but if it's about nested loops Python looks more convenient.
In Python we can do this:
digits = [1, 2, 3]
chars = ['a', 'b', 'c']
print([str(d)+ch for d in digits for ch in chars if d >= 2 if ch == 'a'])
# => ['2a', '3a']
The equivalent in Ruby is:
digits = [1, 2, 3]
chars = ['a', 'b', 'c']
list = []
digits.each do |d|
chars.each do |ch|
list.push d.to_s << ch if d >= 2 && ch == 'a'
end
end
puts list
Does Ruby have something similar?
Upvotes: 10
Views: 6875
Reputation: 67850
As you know Ruby has no syntactic sugar for list-comprehensions, so the closer you can get is by using blocks in imaginative ways. People have proposed different ideas, take a look at lazylist and verstehen approaches, both support nested comprehensions with conditions:
require 'lazylist'
list { [x, y] }.where(:x => [1, 2], :y => [3, 4]) { x+y>4 }.to_a
#=> [[1, 4], [2, 3], [2, 4]]
require 'verstehen'
list { [x, y] }.for(:x).in { [1, 2] }.for(:y).in { [3, 4] }.if { x+y>4 }.comprehend
#=> [[1, 4], [2, 3], [2, 4]]
Of course that's not what you'd call idiomatic Ruby, so it's usually safer to use the typical product
+ select
+ map
approach.
Upvotes: 2
Reputation: 66837
The common way in Ruby is to properly combine Enumerable and Array methods to achieve the same:
digits.product(chars).select{ |d, ch| d >= 2 && ch == 'a' }.map(&:join)
This is only 4 or so characters longer than the list comprehension and just as expressive (IMHO of course, but since list comprehensions are just a special application of the list monad, one could argue that it's probably possible to adequately rebuild that using Ruby's collection methods), while not needing any special syntax.
Upvotes: 12
Reputation: 365637
As suggested by RBK above, List comprehension in Ruby provides a whole slew of different ways to do things sort of like list comprehensions in Ruby.
None of them explicitly describe nested loops, but at least some of them can be nested quite easily.
For example, the accepted answer by Robert Gamble suggests adding an Array#comprehend method.
class Array
def comprehend(&block)
return self if block.nil?
self.collect(&block).compact
end
end
Having done that, you can write your code as:
digits.comprehend{|d| chars.comprehend{|ch| d.to_s+ch if ch =='a'} if d>=2}
Compare to the Python code:
[str(d)+ch for d in digits for ch in chars if d >= 2 if ch == 'a']
The differences are pretty minor:
Of course the real strength of Python here is that if you decide you want to evaluate the list lazily, you can convert your comprehension into a generator expression just by removing the brackets (or turning them into parentheses, depending on the context). But even there, you could create an Array#lazycomprehend or something.
Upvotes: 1