jcrossley3
jcrossley3

Reputation: 11764

Creating lists using yield in Ruby and Python

I'm trying to come up with an elegant way of creating a list from a function that yields values in both Python and Ruby.

In Python:

def foo(x):
    for i in range(x):
        if bar(i): yield i 
result = list(foo(100))

In Ruby:

def foo(x)
  x.times {|i| yield i if bar(i)}
end
result = []
foo(100) {|x| result << x}

Although I love working in both languages, I've always been a bit bothered by the Ruby version having to initialize the list and then fill it. Python's yield results in simple iteration, which is great. Ruby's yield invokes a block, which is also great, but when I just want to fill a list, it feels kinda clunky.

Is there a more elegant Ruby way?

UPDATE Reworked the example to show that the number of values yielded from the function isn't necessarily equal to x.

Upvotes: 3

Views: 7250

Answers (7)

womble
womble

Reputation: 12407

def squares(x)
  (0..x).map { |i| i * i }
end

Anything involving a range of values is best handled with, well, a range, rather than times and array generation.

Upvotes: 1

womble
womble

Reputation: 12407

So, for your new example, try this:

def foo(x)
  (0..x).select { |i| bar(i) }
end

Basically, unless you're writing an iterator of your own, you don't need yield very often in Ruby. You'll probably do a lot better if you stop trying to write Python idioms using Ruby syntax.

Upvotes: 10

horseyguy
horseyguy

Reputation: 29905

The exact equivalent of your Python code (using Ruby Generators) would be:

def foo(x)
    Enumerator.new do |yielder|
        (0..x).each { |v| yielder.yield(v) if bar(v) }
    end
end

result = Array(foo(100))

In the above, the list is lazily generated (just as in the Python example); see:

def bar(v); v % 2 == 0; end

f = foo(100)
f.next #=> 0
f.next #=> 2

Upvotes: 5

user21714
user21714

Reputation: 5951

For the Python version I would use a generator expression like:

(i for i in range(x) if bar(i))

Or for this specific case of filtering values, even more simply

itertools.ifilter(bar,range(x))

Upvotes: 7

recursive
recursive

Reputation: 86094

yield means different things ruby and python. In ruby, you have to specify a callback block if I remember correctly, whereas generators in python can be passed around and yield to whoever holds them.

Upvotes: 1

Wayne
Wayne

Reputation: 29

For the Python list comprehension version posted by stbuton use xrange instead of range if you want a generator. range will create the entire list in memory.

Upvotes: 1

Denis Hennessy
Denis Hennessy

Reputation: 7473

I know it's not exactly what you were looking for, but a more elegant way to express your example in ruby is:

result = Array.new(100) {|x| x*x}

Upvotes: 1

Related Questions