It's possible to implement an each method in a class mixing in
Enumerable that runs forever, and thus causing, for example, to_a to run
forever. One such class is mathn's Prime class:
require 'mathn'
Prime.new.to_a # <- runs forever, never returns
This seems a bit broken to me, and I was writing a program where I
needed the first n primes, or all the primes under 200, for example, so
I re-wrote Enumerable#to_a like so to make my code more idiomatic:
----8<----
module Enumerable
def to_a(n = nil)
result = []
inclusion_condition = true
return result if n.to_i <= 0 and !block_given?
each do |*elements|
elements = elements.first if elements.size == 1
if block_given?
values = result.last
inclusion_condition = yield(*values) unless values.nil?
else
inclusion_condition = n != result.size
end
if inclusion_condition
result << elements
else
result.pop if block_given?
break result
end
end
result
end
end
----8<----
This allows, for example, the following:
Prime.new.to_a(10) # => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] , the first
10 primes
Prime.new.to_a {|prime| prime < 20} # => [2, 3, 5, 7, 11, 13, 17, 19] ,
all the primes under 20
Passed an integer paramater, the code will return an array that long, or
until the Enumerable runs out. Passed a block, it will return an array
of elements, adding elements until the block evaluates to false.
This code surely could use some hacking, refactoring, and possibly bug
fixing. I've made a post[1] on http://refactormycode.com just for that
purpose.
Thoughts?
[1]
http://refactormycode.com/codes/196-enumerable-to_a-with-terminating-conditions
-Ben Kudria
--
http://ben.kudria.net | Jabber: ben@kudria.net
on 25.12.2007 00:47
on 25.12.2007 03:19
Hi,
In message "Re: Enumerable#to_a can run forever"
on Tue, 25 Dec 2007 08:47:48 +0900, Benjamin Kudria <ben@kudria.net>
writes:
|require 'mathn'
|Prime.new.to_a # <- runs forever, never returns
|
|This seems a bit broken to me, and I was writing a program where I
|needed the first n primes, or all the primes under 200, for example, so
|I re-wrote Enumerable#to_a like so to make my code more idiomatic:
|This allows, for example, the following:
|Prime.new.to_a(10) # => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] , the first
|10 primes
|Prime.new.to_a {|prime| prime < 20} # => [2, 3, 5, 7, 11, 13, 17, 19] ,
|all the primes under 20
Hmm, in 1.9, you have Enumerable#take to work like your to_i(num), and
Enumerable#take_while like your to_i{...}.
p Prime.new.take(10) # => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
p Prime.new.take_while{|prime| prime < 20} # => [2, 3, 5, 7, 11, 13,
17, 19]
matz.
on 25.12.2007 03:27
Yukihiro Matsumoto wrote: > Hmm, in 1.9, you have Enumerable#take to work like your to_i(num), and > Enumerable#take_while like your to_i{...}. > > p Prime.new.take(10) # => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] > p Prime.new.take_while{|prime| prime < 20} # => [2, 3, 5, 7, 11, 13, > 17, 19] > > matz. Aaah, ok. Those would be excellent. I look forward to 1.9! Thank you! However, what about Prime.new.to_a ? That would still run forever, and the unsuspecting coder might be caught by surprise.
on 25.12.2007 04:39
Hi,
In message "Re: Enumerable#to_a can run forever"
on Tue, 25 Dec 2007 11:27:29 +0900, Benjamin Kudria <ben@kudria.net>
writes:
|Aaah, ok. Those would be excellent. I look forward to 1.9! Thank you!
|However, what about Prime.new.to_a ? That would still run forever, and
|the unsuspecting coder might be caught by surprise.
It's no way to avoid entering infinite loop when you do some
operations on infinite enumerable, such as Prime. For example, trying
#sort or #max on Prime, or even putting it as a splat will cause
infinite loop. Adding an argument or a block to #to_a does not
improve the situation a lot, I think.
matz.
on 25.12.2007 05:00
Yukihiro Matsumoto wrote: > It's no way to avoid entering infinite loop when you do some > operations on infinite enumerable, such as Prime. For example, trying > #sort or #max on Prime, or even putting it as a splat will cause > infinite loop. Adding an argument or a block to #to_a does not > improve the situation a lot, I think. > > matz. Fair enough - but then does it make sense to have these operations defined? Enumerable seems to mean the same thing as "countable", but it includes methods that apply to only countably finite sets. Perhaps there should be a way to distinguish between sets that are countable finite and those that are countably infinite? Does 1.9 include provisions for handling infinite lists or sets? Thanks, Ben Kudria -- http://ben.kudria.net | Jabber: ben@kudria.net
on 25.12.2007 05:10
Hi,
In message "Re: Enumerable#to_a can run forever"
on Tue, 25 Dec 2007 13:00:48 +0900, Benjamin Kudria <ben@kudria.net>
writes:
|Fair enough - but then does it make sense to have these operations
|defined?
Because it's very pragmatically useful for most of the case.
Theoretical cleanliness is not my concern.
|Perhaps there should
|be a way to distinguish between sets that are countable finite and those
|that are countably infinite?
And adding one burden to library implementer (including me)?
|Does 1.9 include provisions for handling
|infinite lists or sets?
I think you mean generator. If so, it has been supported since 1.8.
matz.
on 26.12.2007 01:33
Yukihiro Matsumoto wrote: > |Fair enough - but then does it make sense to have these operations > |defined? > > Because it's very pragmatically useful for most of the case. > Theoretical cleanliness is not my concern. Aah, I see. I guess I agree. > |Perhaps there should > |be a way to distinguish between sets that are countable finite and those > |that are countably infinite? > > And adding one burden to library implementer (including me)? Fair enough :). It would probably be overly confusing for the coder, too. > |Does 1.9 include provisions for handling > |infinite lists or sets? > > I think you mean generator. If so, it has been supported since 1.8. You are correct, I see Generators in 1.8 can do everything I want. Seems I don't know Ruby as well as I thought. :) Thanks for clarifying everything. Ben Kudria -- http://ben.kudria.net | Jabber: ben@kudria.net