Эквивалент Ruby Enumerable.collect, который возвращает Enumerable? - PullRequest
8 голосов
/ 24 февраля 2010

В этом коде я создаю массив строк с «1» по «10000»:

array_of_strings = (1..10000).collect {|i| String(i)}

Предоставляет ли Ruby Core API способ получения перечислимого объекта, который позволяет мне перечислять по одному и тому же списку, генерируя строковые значения по требованию, а не генерируя массив строк?

Вот еще один пример, который, надеюсь, проясняет, что я пытаюсь сделать:

def find_me_an_awesome_username
  awesome_names = (1..1000000).xform {|i| "hacker_" + String(i) }
  awesome_names.find {|n| not stackoverflow.userexists(n) }
end

Где xform - метод, который я ищу. awesome_names является Enumerable, поэтому xform не создает массив строк из 1 миллиона элементов, а просто генерирует и возвращает строки вида "hacker_ [N]" по требованию.

Кстати, вот как это может выглядеть в C #:

var awesomeNames = from i in Range(1, 1000000) select "hacker_" + i;
var name = awesomeNames.First((n) => !stackoverflow.UserExists(n));

(одно решение)

Вот расширение Enumerator, которое добавляет метод xform. Он возвращает другой перечислитель, который перебирает значения исходного перечислителя с примененным к нему преобразованием.

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}

Ответы [ 4 ]

6 голосов
/ 24 февраля 2010

В Ruby 2.0 введено Enumerable#lazy, которое позволяет связывать map, select и т. Д. И генерировать только окончательные результаты в конце с to_a, firstи т. д. Вы можете использовать его в любой версии Ruby с 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'

В противном случае вы можете использовать Enumerator.new { with_a_block }.Это новое в Ruby 1.9, поэтому require 'backports/1.9.1/enumerator/new', если вам это нужно в Ruby 1.8.x.

В соответствии с вашим примером, следующее не создаст промежуточный массив и только построит необходимые строки:

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

Вы можете даже заменить 100000 на 1,0 / 0 (т. Е. Бесконечность), если хотите.

Чтобы ответить на ваш комментарий, если вы всегда сопоставляете свои значения один к одному, вы могли бы иметьчто-то вроде:

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}"}
1 голос
/ 24 февраля 2010

Звучит так, будто вы хотите объект Enumerator, но не совсем.

То есть объект Enumerator - это объект, который вы можете использовать для вызова next по требованию (вместо each, которыйвесь цикл).(Многие люди используют язык внутренних и внешних итераторов: each является внутренним, а перечислитель - внешним. Вы управляете им.)

Вот как может выглядеть перечислитель:

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

Вот ссылка на дальнейшее обсуждение того, как вы можете лениво использовать перечислители в Ruby: http://www.michaelharrison.ws/weblog/?p=163

В книге по кирке также есть раздел ( Программирование Ruby Дэйва Томаса).

1 голос
/ 24 февраля 2010
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"

Следующее, что нужно сделать, это вернуть Enumerator при вызове без блока ...

0 голосов
/ 24 февраля 2010

списки имеют каждый метод:

(1..100000).each
...