开发者

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜