В C# Вы могли сделать что-то вроде этого:
public IEnumerable GetItems()
{
for (int i=0; i<10000000; i++) {
yield return i;
}
}
Это возвращает счетную последовательность 10 миллионов целых чисел, никогда не выделяя набор в память о той длине.
Существует ли способ сделать эквивалентную вещь в Ruby? Определенным примером, с которым я пытаюсь иметь дело, является выравнивание прямоугольной антенной решетки в последовательность значений, которые будут перечислены. Возвращаемое значение не должно быть Array
или Set
, а скорее некоторая последовательность, которая может только быть выполнена итерации/перечислена в порядке, не индексом. Следовательно, вся последовательность не должна быть выделена в памяти одновременно. В.NET это IEnumerable
и IEnumerable
.
Любое разъяснение по поводу терминологии, используемой здесь в мире Ruby, было бы полезно, поскольку я более знаком с терминологией.NET.
Править
Возможно, мой исходный вопрос не был действительно достаточно ясен - я думаю факт это yield
имеет совсем другие значения в C#, и Ruby является причиной беспорядка здесь.
Я не хочу решения, которое требует, чтобы мой метод использовал блок. Я хочу решение, которое имеет фактическое возвращаемое значение. Возвращаемое значение позволяет удобную обработку последовательности (фильтрация, проекция, конкатенация, архивирование, и т.д.).
Вот простой пример того, как я мог бы использовать get_items
:
things = obj.get_items.select { |i| !i.thing.nil? }.map { |i| i.thing }
В C#, любом возврате метода IEnumerable
это использует a yield return
заставляет компилятор генерировать конечный автомат негласно, который обслуживает это поведение. Я подозреваю, что что-то подобное могло быть достигнуто с помощью продолжений Ruby, но я не видел примера, и не совсем ясно сам о том, как это было бы сделано.
Действительно кажется возможным, что я мог бы использовать Enumerable
достигнуть этого. Простое решение было бы нам Array
(который включает модуль Enumerable
), но я не хочу создавать промежуточный набор с объектами N в памяти, когда возможно просто обеспечить их лениво и избежать любого скачка памяти вообще.
Если это все еще не имеет смысла, то рассмотрите вышеупомянутый пример кода. get_items
возвращает перечисление, на который select
назван. Чему передается select
экземпляр, который знает, как обеспечить следующий объект в последовательности каждый раз, когда это необходимо. Значительно, целый набор объектов еще не был вычислен. Только, когда select
потребности объект будут он просить его, и скрытый код в get_items
ударит в действие и обеспечит его. Эта лень несет вдоль цепочки, такой что select
только тянет следующий объект из последовательности когда map
просит его. По сути, длинная цепочка операций может быть выполнена на одном элементе данных за один раз. На самом деле код, структурированный таким образом, может даже обработать бесконечную последовательность значений без любых видов ошибок памяти.
Так, этот вид лени легко кодируется в C#, и я не знаю, как сделать это в Ruby.
Я надеюсь, что это более ясно (я постараюсь не писать вопросы в 3:00 в будущем.)
Он поддерживается Enumerator
начиная с Ruby 1.9 (и обратно перенесен в 1.8.7). См. Генератор: Ruby.
Клишированный пример:
fib = Enumerator.new do |y|
y.yield i = 0
y.yield j = 1
while true
k = i + j
y.yield k
i = j
j = k
end
end
100.times { puts fib.next() }
Ваш конкретный пример эквивалентен 10000000.times
, но давайте на мгновение предположим, что метод times не существует, и вы хотел реализовать это самостоятельно, это будет выглядеть так:
class Integer
def my_times
return enum_for(:my_times) unless block_given?
i=0
while i<self
yield i
i += 1
end
end
end
10000.my_times # Returns an Enumerable which will let
# you iterate of the numbers from 0 to 10000 (exclusive)
Изменить: немного пояснить мой ответ:
В приведенном выше примере my_times может (и используется) использовать без блока, и он вернет объект Enumerable , что позволит вам перебирать числа от 0 до n. Так что это в точности эквивалентно вашему примеру на C #.
Это работает с использованием метода enum_for.Метод enum_for принимает в качестве аргумента имя метода, который возвращает некоторые элементы. Затем он возвращает экземпляр класса Enumerator (который включает модуль Enumerable), который при итерации выполнит данный метод и предоставит вам элементы, которые были получены этим методом. Обратите внимание, что если вы выполняете итерацию только по первым x элементам перечисляемого, метод будет выполняться только до тех пор, пока не будет получено x элементов (т.е. будет выполнено ровно столько, сколько необходимо для метода), и если вы выполните итерацию по перечисляемому дважды, метод будет выполнен дважды.
В 1.8.7+ стало определяться методы, которые возвращают элементы, чтобы при вызове без блока они возвращали Enumerator, который позволяет пользователю лениво перебирать эти элементы. Это делается путем добавления строки return enum_for (: name_of_this_method), если block_given?
в начало метода, как я сделал в моем примере.
C # извлек ключевое слово yield прямо из Ruby - подробнее см. Реализация итераторов здесь .
Что касается вашей реальной проблемы, у вас, вероятно, есть массив массивов, и вы хотите создать одностороннюю итерацию по всей длине списка? Возможно, стоит взглянуть на array.flatten как на отправную точку - если производительность в порядке, вам, вероятно, не нужно заходить слишком далеко.
Не имея большого опыта работы с Ruby, то, что C# делает в yield return
, обычно известно как ленивая оценка или ленивое выполнение: предоставление ответов только по мере их необходимости. Речь идет не о выделении памяти, а об откладывании вычислений до тех пор, пока они действительно не понадобятся, что выражается в способе, похожем на простое линейное выполнение (а не на лежащий в основе итератор с сохранением состояния).
Быстрый гугл нашел библиотеку ruby в бета-версии. Посмотрите, может это то, что вам нужно.