Ruby, эквивалентный из ключевого слова 'урожая' C#, или, создавая последовательности, не предварительно выделяя память

В 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 в будущем.)

7
задан Drew Noakes 18 February 2010 в 01:17
поделиться

4 ответа

Он поддерживается 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() }
14
ответ дан 6 December 2019 в 10:49
поделиться

Ваш конкретный пример эквивалентен 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? в начало метода, как я сделал в моем примере.

5
ответ дан 6 December 2019 в 10:49
поделиться

C # извлек ключевое слово yield прямо из Ruby - подробнее см. Реализация итераторов здесь .

Что касается вашей реальной проблемы, у вас, вероятно, есть массив массивов, и вы хотите создать одностороннюю итерацию по всей длине списка? Возможно, стоит взглянуть на array.flatten как на отправную точку - если производительность в порядке, вам, вероятно, не нужно заходить слишком далеко.

-2
ответ дан 6 December 2019 в 10:49
поделиться

Не имея большого опыта работы с Ruby, то, что C# делает в yield return, обычно известно как ленивая оценка или ленивое выполнение: предоставление ответов только по мере их необходимости. Речь идет не о выделении памяти, а об откладывании вычислений до тех пор, пока они действительно не понадобятся, что выражается в способе, похожем на простое линейное выполнение (а не на лежащий в основе итератор с сохранением состояния).

Быстрый гугл нашел библиотеку ruby в бета-версии. Посмотрите, может это то, что вам нужно.

1
ответ дан 6 December 2019 в 10:49
поделиться
Другие вопросы по тегам:

Похожие вопросы: