Эквивалент Ruby Enumerable.collect, который возвращает Enumerable?

В этом коде я создаю массив строк от «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}

person mackenir    schedule 24.02.2010    source источник
comment
... следует читать «от 2 до 20»   -  person mackenir    schedule 25.02.2010


Ответы (4)


В 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}"}
person Marc-André Lafortune    schedule 24.02.2010
comment
@marc, похоже на то! Знаете ли вы, как я мог бы превратить этот шаблон в более краткий повторно используемый метод, использующий «перечисляемую вещь» и «функцию-преобразователя»? В этом примере «функция преобразования» будет {|i| "hacker_" + String(i) }, а «перечисляемая вещь» будет (1..100000) или что-то в этом роде. - person mackenir; 24.02.2010
comment
Большое спасибо. Я обновил свой вопрос возможной реализацией, которую я разработал из вашего ответа, а также из чтения ссылки, опубликованной @Telemachus, до того, как я заметил ваше обновление :) Еще раз, спасибо! - person mackenir; 24.02.2010
comment
@mackenir: функция преобразования Enumerable - map. Вы должны просто изменить each на map и оставить алгоритм без изменений. - person Chuck; 25.02.2010
comment
@Chuck Enumerable#map является синонимом Enumerable#collect, поэтому он возвращает массив, а не Enumerator. - person mackenir; 25.02.2010
comment
@mackenir: На самом деле, я неправильно понял. Как указанный метод не соответствует тому, о чем вы просите? Он принимает функцию преобразования и предоставляет перечислитель, который дает результат применения этого преобразования. - person Chuck; 25.02.2010
comment
@Chuck - под «указанным методом» вы имеете в виду Enumerable#map? Это не то, что мне нужно, так как он возвращает массив. - person mackenir; 25.02.2010
comment
@mackenir: Под данным методом я имею в виду lazy_each Марка-Андре в этом ответе. Он принимает блок и возвращает перечислитель, который выдает результаты вызова блока для каждого элемента в коллекции. - person Chuck; 25.02.2010
comment
Э... ты потерял меня, @Chuck. Где я говорил об обратном? - person mackenir; 25.02.2010
comment
Предстоящие в 1.8.8 ... на неопределенный срок? :) - person Andrew Grimm; 22.03.2012
comment
@AndrewGrimm: Действительно. Отредактировано и дополнено предстоящим lazy - person Marc-André Lafortune; 22.03.2012
comment
Обновлен ответ теперь, когда вышли 2.0.0 и lazy - person Marc-André Lafortune; 11.03.2013

Похоже, вам нужен объект 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

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

Об этом также есть раздел в книге о кирках (Programming Ruby Дэйва Томаса).

person Telemachus    schedule 24.02.2010
comment
Спасибо. Хм. Find определенно прекращает перечисление, когда находит соответствующий элемент. Вы можете подтвердить это, запустив find в очень большом диапазоне, с предикатом «false» и «true», последний возвращается мгновенно. Если бы оба перечисляли все, они оба возвращали бы в одно и то же время. Re: перечисление с помощью next, я пытаюсь найти средство «перечисляемого преобразователя», чтобы написать более краткий декларативный код, и ручное перечисление на самом деле не добьется этого. Возможно, ответ заключается в том, чтобы просто реализовать это. - person mackenir; 24.02.2010
comment
Со всеми удаленными CR это менее понятно. Я имею в виду, что (1..1000000000000000000).find {|i|true} выполняется быстро, а (1..1000000000000000000).find {|i|false} медленно. Значение find просто перечисляет, пока не «найдет». - person mackenir; 24.02.2010
comment
Полезная ссылка - кажется, я ее понял, и она помогла ответить на вопрос. - person mackenir; 24.02.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 при вызове без блока...

person DigitalRoss    schedule 24.02.2010

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

(1..100000).each
person ennuikiller    schedule 24.02.2010
comment
... хорошо, теперь вы начинаете искать итерацию Ruby. - person Geo; 24.02.2010
comment
Но ваш код просто перебирает целочисленный диапазон. Он не генерирует новое перечисляемое количество строк. Пожалуйста, попробуйте поставить себя на мое место идиота :). - person mackenir; 24.02.2010
comment
(1..100000).each { |j| i = j.to_s ; do_something j } или вы хотели сделать что-то другое? - person anshul; 24.02.2010
comment
@anshul да, я хочу сделать что-то немного отличающееся от этого. Я хотел бы отделить код, генерирующий перечисляемое, от кода, работающего с перечисляемыми значениями. TBH это не единственный способ сделать это, но он сделает код чище (на мой взгляд, конечно). Спасибо. - person mackenir; 24.02.2010
comment
@mackenir, вам, вероятно, следует просто предоставить метод each, который принимает блок и делегирует его Range#each, как было предложено. Сохраняйте перечисляемую магию внутри реализации. - person molf; 25.02.2010
comment
@molf Я не думаю, что работать с объектами Enumerator напрямую - это волшебно, на самом деле это довольно полезно. - person mackenir; 25.02.2010