Как я могу получить ленивый массив в Ruby? - PullRequest
30 голосов
/ 03 августа 2010

Как я могу получить ленивый массив в Ruby?

В Haskell я могу говорить о [1..], который является бесконечным списком, лениво генерируемым по мере необходимости.Я также могу делать такие вещи, как iterate (+2) 0, который применяет любую функцию, которую я ему даю, для генерации отложенного списка.В этом случае это дало бы мне все четные числа.

Я уверен, что могу делать такие вещи в Ruby, но не могу понять, как.

Ответы [ 8 ]

41 голосов
/ 03 августа 2010

В Ruby 1.9 вы можете использовать класс Enumerator.Это пример из документов:

  fib = Enumerator.new { |y|
    a = b = 1
    loop {
      y << a
      a, b = b, a + b
    }
  }

  p fib.take(10) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Кроме того, это хороший трюк:

  Infinity = 1.0/0

  range = 5..Infinity
  p range.take(10) #=> [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

Хотя этот работает только для последовательных значений.

21 голосов
/ 14 марта 2012

Недавно Enumerable :: Lazy был добавлен в рубиновый сундук.Мы увидим это в ruby ​​2.0.В частности:

a = data.lazy.map(&:split).map(&:reverse)

не будет оцениваться сразу.
Результатом является экземпляр Enumerable :: Lazy, который может быть в дальнейшем добавлен в цепочку ленивых.Если вы хотите получить реальный результат - используйте #to_a, #take(n) (#take теперь тоже ленивый, используйте #to_a или #force) и т. Д.
Если вы хотитебольше на эту тему и мой патч C - см. мой блог Ruby 2.0 Enumerable :: Lazy

6 голосов
/ 03 августа 2010

Ленивый диапазон (натуральные числа):

Inf = 1.0/0.0
(1..Inf).take(3) #=> [1, 2, 3]

Ленивый диапазон (четные числа):

(0..Inf).step(2).take(5) #=> [0, 2, 4, 6, 8]

Обратите внимание, вы также можете расширить Enumerable некоторыми способами, чтобы заставить работатьс ленивыми диапазонами (и т. д.) удобнее:

module Enumerable
  def lazy_select
    Enumerator.new do |yielder|
      each do |obj|
        yielder.yield(obj) if yield(obj)
      end
    end
  end
end

# first 4 even numbers
(1..Inf).lazy_select { |v| v.even? }.take(4)

output:
[2, 4, 6, 8]

Подробнее здесь: http://banisterfiend.wordpress.com/2009/10/02/wtf-infinite-ranges-in-ruby/

Существуют также реализации lazy_map и lazy_select для класса Enumerator, которые могутможно найти здесь: http://www.michaelharrison.ws/weblog/?p=163

4 голосов
/ 25 февраля 2013

В Ruby 2.0.0 был введен новый метод "Lazy" в классе Enumerable.

Вы можете проверить ленивое ядро ​​функции и использование здесь ..

http://www.ruby -doc.org / ядро-2,0 / Enumerator / Lazy.html
https://github.com/yhara/enumerable-lazy
http://shugomaeda.blogspot.in/2012/03/enumerablelazy-and-its-benefits.html

1 голос
/ 16 июля 2014

Это будет цикл до бесконечности:

0.step{|i| puts i}

Это будет цикл до бесконечности вдвое быстрее:

0.step(nil, 2){|i| puts i}

Это будет бесконечность, только если вы этого хотите (результатыв счетчике).

table_of_3 = 0.step(nil, 3)
1 голос
/ 03 августа 2010

Как я уже сказал в моих комментариях, реализация такой вещи, как ленивые массивы, не будет разумной.

Использование Enumerable вместо этого может хорошо работать в некоторых ситуациях, но отличается от ленивых списков в некоторых моментах: такие методы, как map и filter, не будут оцениваться лениво (поэтому они не будут работать на бесконечных перечислимых) и вычисленные элементы один раз не сохраняются, поэтому, если вы обращаетесь к элементу дважды, он рассчитывается дважды.

Если вам нужно точное поведение ленивых списков в haskell в ruby, есть lazylist gem , который реализует ленивые списки.

0 голосов
/ 16 сентября 2018

Правильный ответ уже определил «ленивый» метод, но приведенный пример оказался не слишком полезным. Я приведу лучший пример того, когда целесообразно использовать lazy с массивами. Как уже говорилось, lazy определяется как метод экземпляра модуля Enumerable, и он работает с объектами EITHER, которые реализуют модуль Enumerable (например, arrays - [] .lazy), или с перечислителями, которые возвращают значение итераторов в модуле enumerable (например, each_slice). - [] .each_slice (2) .lazy). Обратите внимание, что в модуле Enumerable некоторые методы экземпляра возвращают больше примитивных значений, таких как true или false, некоторые возвращают коллекции, такие как массивы, а некоторые возвращают перечислители. Некоторые возвращают перечислители, если блок не указан.

Но для нашего примера класс IO также имеет итератор each_line, которая возвращает перечислитель и, таким образом, может использоваться с "lazy". Прекрасная вещь в возвращении перечислителя состоит в том, что он фактически не загружает коллекцию (например, большой массив) в память, над которой он работает. Скорее, он имеет указатель на коллекцию, а затем рассказывает алгоритм (например, each_slice (2)), который он будет использовать в этой коллекции, когда вы хотите обработать коллекцию с чем-то вроде to_a, например.

Так что, если вы работаете с перечислителем для огромного повышения производительности, теперь вы можете прикрепить ленивый к перечислителю. Таким образом, вместо перебора всей коллекции, чтобы соответствовать этому условию:

file.each_line.select { |line| line.size == 5 }.first(5)

Вы можете вызвать ленивый:

file.each_line.lazy.select { |line| line.size == 5 }.first(5)

Если мы сканируем большой текстовый файл на первые 5 совпадений, то после того, как мы находим 5 совпадений, нет необходимости продолжать выполнение. Следовательно, сила ленива с любым типом перечисляемого объекта.

0 голосов
/ 03 августа 2010

Массивы Ruby динамически расширяются по мере необходимости. Вы можете применять блоки к ним, чтобы вернуть такие вещи, как четные числа.

array = []
array.size # => 0
array[0] # => nil
array[9999] # => nil
array << 1
array.size # => 1
array << 2 << 3 << 4
array.size # => 4

array = (0..9).to_a
array.select do |e|
  e % 2 == 0
end

# => [0,2,4,6,8]

Помогает ли это?

...