В этом коде я создаю массив строк "1" к "10 000":
array_of_strings = (1..10000).collect {|i| String(i)}
Действительно ли Ruby удаляет сердцевину 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 является Счетным, таким образом, xform
не создает 1 миллион массивов строк элемента, но просто генерирует и возвращает строки формы "хакер _ [N]" по требованию.
Между прочим, вот то, на что это могло бы быть похожим в C#:
var awesomeNames = from i in Range(1, 1000000) select "hacker_" + i;
var name = awesomeNames.First((n) => !stackoverflow.UserExists(n));
(Одно Решение)
Вот расширение Перечислителя, который добавляет 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}
Ruby 2.0 представил Enumerable # lazy
, который позволяет связать map
, select
и т. Д., И генерировать окончательные результаты только в конце with 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, поэтому требует '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}"}
Похоже, вам нужен объект Enumerator, но не совсем.
То есть объект Enumerator - это объект, который можно использовать для вызова next
по запросу (а не 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
Также есть раздел об этом в книге Pickaxe ( Programming Руби Дэйва Томаса).
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"
Следующее, что нужно сделать, - это вернуть перечислитель при вызове без блока ...