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 enumerate 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}"}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With