Equivalent of Ruby Enumerable.collect that returns an Enumerable?
In this code, I create an array of strings "1" to "10000":
array_of_strings = (1..10000).collect {|i| String(i)}
Does the Ruby Core API provide a way to get an enumerable object that lets me en开发者_开发问答umerate over the same list, generating the string values on demand, rather than generating an array of the strings?
Here's a further example which hopefully clarifies what I am trying to do:
def find_me_an_awesome_username
awesome_names = (1..1000000).xform {|i| "hacker_" + String(i) }
awesome_names.find {|n| not stackoverflow.userexists(n) }
end
Where xform
is the method I am looking for.
awesome_names is an Enumerable, so xform
isn't creating a 1 million element array of strings, but just generating and returning strings of the form "hacker_[N]" on demand.
By the way, here's what it might look like in C#:
var awesomeNames = from i in Range(1, 1000000) select "hacker_" + i;
var name = awesomeNames.First((n) => !stackoverflow.UserExists(n));
(One Solution)
Here is an extension to Enumerator that adds an xform method. It returns another enumerator which iterates over the values of the original enumerator, with a transform applied to it.
class Enumerator
def xform(&block)
Enumerator.new do |yielder|
self.each do |val|
yielder.yield block.call(val)
end
end
end
end
# this prints out even numbers from 2 to 10:
(1..10).each.xform {|i| i*2}.each {|i| puts i}
Ruby 2.0 introduced Enumerable#lazy
which allows one to chain map
, select
, etc..., and only generate the final results at the end with to_a
, first
, etc... You can use it in any Ruby version with require 'backports/2.0.0/enumerable/lazy'
.
require 'backports/2.0.0/enumerable/lazy'
names = (1..Float::INFINITY).lazy.map{|i| "hacker_" + String(i) }
names.first # => 'hacker_1'
Otherwise, you can use Enumerator.new { with_a_block }
. It's new in Ruby 1.9, so require 'backports/1.9.1/enumerator/new'
if you need it in Ruby 1.8.x.
As per your example, the following will not create an intermediate array and will only construct the needed strings:
require 'backports/1.9.1/enumerator/new'
def find_me_an_awesome_username
awesome_names = Enumerator.new do |y|
(1..1000000).each {|i| y.yield "hacker_" + String(i) }
end
awesome_names.find {|n| not stackoverflow.userexists(n) }
end
You can even replace the 100000 by 1.0/0 (i.e. Infinity), if you want.
To answer your comment, if you are always mapping your values one to one, you could have something like:
module Enumerable
def lazy_each
Enumerator.new do |yielder|
each do |value|
yielder.yield(yield value)
end
end
end
end
awesome_names = (1..100000).lazy_each{|i| "hacker_#{i}"}
It sounds like you want an Enumerator object, but not exactly.
That is, an Enumerator object is an object that you can use to call next
on demand (rather than each
which does the whole loop). (Many people use the language of internal versus external iterators: each
is internal, and an Enumerator is external. You drive it.)
Here's how an enumerator might look:
awesome_names = Enumerator.new do |y|
number = 1
loop do
y.yield number
number += 1
end
end
puts awesome_names.next
puts awesome_names.next
puts awesome_names.next
puts awesome_names.next
Here's a link, to more discussion of how you might use Enumerators lazily in Ruby: http://www.michaelharrison.ws/weblog/?p=163
There's also a section on this in the Pickaxe book (Programming Ruby by Dave Thomas).
class T < Range
def each
super { |i| yield String(i) }
end
end
T.new(1,3).each { |s| p s }
$ ruby rsc.rb
"1"
"2"
"3"
The next thing to do is to return an Enumerator when called without a block...
lists have an each method:
(1..100000).each
精彩评论