Reputation: 11764
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
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
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
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
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
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
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
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