defhlt
defhlt

Reputation: 1784

Does Ruby have something like Python's list comprehensions?

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

Answers (3)

tokland
tokland

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

Michael Kohl
Michael Kohl

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

abarnert
abarnert

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:

  1. The Ruby code is a bit longer. But that's mostly just the fact that "comprehend" is spelled out; you can always call it something shorter if you want.
  2. The Ruby code puts things in a different order—the arrays come at the beginning instead of in the middle. But if you think about it, that's exactly what you'd expect, and want, because of the "everything is a method" philosophy.
  3. The Ruby code requires nested braces for nested comprehensions. I can't think of an obvious way around this that doesn't make things worse (you don't want to call "[str,digits].comprehend2" or anything…).

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

Related Questions