Как группировать перечисления в ruby

В моем стремлении понять рубиновый enumerable у меня есть что-то похожее на следующее

FileReader.read(very_big_file)
          .lazy
          .flat_map {|line| get_array_of_similar_words } # array.size is ~10
          .each_slice(100) # wait for 100 items
          .map{|array| process_100_items}

Поскольку каждый вызов flat_map генерирует массив из ~10 элементов, я ожидал, что вызов each_slice будет группировать элементы по 100, но это не так. То есть подождите, пока не будет 100 элементов, прежде чем передавать их на последний вызов .map.

Как добиться функциональности, аналогичной функции buffer в реактивное программирование?


person user789    schedule 10.01.2020    source источник
comment
но это не так - что происходит вместо этого?   -  person Sergio Tulentsev    schedule 10.01.2020
comment
Также не используйте map там, где вы имеете в виду each.   -  person Sergio Tulentsev    schedule 10.01.2020
comment
Что такое FileReader? В Ruby есть File.read, но он не возвращает перечислитель.   -  person Stefan    schedule 10.01.2020
comment
@СергиоТуленцев. each_slice попытается разделить перечисляемое из 10 элементов на пакеты по 100. Таким образом, оно вернет перечисляемое из 10 элементов без изменений. Использование каждого/карты не имеет значения для проблемы.   -  person user789    schedule 10.01.2020
comment
@Stefan FileReader — это мой класс, который использует гем rio для чтения файла.   -  person user789    schedule 10.01.2020
comment
1.upto(3).lazy.flat_map { |i| [i, i] }.each_slice(3).to_a возвращает [[1, 1, 2], [2, 3, 3]], что мне кажется правильным. Может быть, вы слишком упростили свой пример?   -  person Stefan    schedule 10.01.2020
comment
Таким образом, он вернет перечисление из 10 элементов без изменений - это не то, что происходит для меня.   -  person Sergio Tulentsev    schedule 10.01.2020
comment
Что вы пытаетесь сделать? Начиная с пустого массива arr, хотите ли вы читать переменное количество строк файла в цикле, где в каждом цикле строки считываются и элементы добавляются к arr до тех пор, пока arr не будет содержать не менее 100 объектов, и в это время первые 100 элементы из arr удаляются и обрабатываются? Если это так, вы не можете просто связать методы; вам нужно прочитать файл построчно, используя IO::foreach, скажем, а затем выполнить необходимые операции в блоке foreach.   -  person Cary Swoveland    schedule 10.01.2020
comment
@CarySwoveland: нет, методы идеально соединяются в цепочку, при условии, что FileReader::read (что бы это ни было) действительно возвращает перечислитель по строкам, например IO::foreach.   -  person Sergio Tulentsev    schedule 10.01.2020


Ответы (1)


Чтобы увидеть, как lazy влияет на вычисления, давайте рассмотрим пример. Сначала создайте файл:

str =<<~_
Now is the
time for all
good Ruby coders
to come to
the aid of
their bowling
team
_

fname = 't' 
File.write(fname, str)
  #=> 82

и укажите размер среза:

slice_size = 4

Теперь я буду читать строки одну за другой, разбивать строки на слова, удалять повторяющиеся слова и затем добавлять эти слова в массив. Как только массив будет содержать как минимум 4 слова, я возьму первые четыре и сопоставлю их с самым длинным словом из 4. Ниже приведен код для этого. Чтобы показать, как продвигаются вычисления, я солю код с операторами puts. Обратите внимание, что IO::foreach без блок возвращает перечислитель.

IO.foreach(fname).
   lazy.
   tap { |o| puts "o1 = #{o}" }.
   flat_map { |line|
     puts "line = #{line}"
     puts "line.split.uniq = #{line.split.uniq} "
     line.split.uniq }.
   tap { |o| puts "o2 = #{o}" }.
   each_slice(slice_size).
   tap { |o| puts "o3 = #{o}" }.
   map { |arr|
     puts "arr = #{arr}, arr.max = #{arr.max_by(&:size)}"
     arr.max_by(&:size) }.
   tap { |o| puts "o3 = #{o}" }.
   to_a
  #=> ["time", "good", "coders", "bowling", "team"] 

Отображается следующее:

o1 = #<Enumerator::Lazy:0x00005992b1ab6970>
o2 = #<Enumerator::Lazy:0x00005992b1ab6880>
o3 = #<Enumerator::Lazy:0x00005992b1ab6678>
o3 = #<Enumerator::Lazy:0x00005992b1ab6420>
line = Now is the
line.split.uniq = ["Now", "is", "the"] 
line = time for all
line.split.uniq = ["time", "for", "all"] 
arr = ["Now", "is", "the", "time"], arr.max = time
line = good Ruby coders
line.split.uniq = ["good", "Ruby", "coders"] 
arr = ["for", "all", "good", "Ruby"], arr.max = good
line = to come to
line.split.uniq = ["to", "come"] 
line = the aid of
line.split.uniq = ["the", "aid", "of"] 
arr = ["coders", "to", "come", "the"], arr.max = coders
line = their bowling
line.split.uniq = ["their", "bowling"] 
arr = ["aid", "of", "their", "bowling"], arr.max = bowling
line = team
line.split.uniq = ["team"] 
arr = ["team"], arr.max = team

Если строка lazy. удалена, возвращаемое значение остается тем же, но отображается следующее (.to_a в конце теперь лишнее):

o1 = #<Enumerator:0x00005992b1a438f8>
line = Now is the
line.split.uniq = ["Now", "is", "the"] 
line = time for all
line.split.uniq = ["time", "for", "all"] 
line = good Ruby coders
line.split.uniq = ["good", "Ruby", "coders"] 
line = to come to
line.split.uniq = ["to", "come"] 
line = the aid of
line.split.uniq = ["the", "aid", "of"] 
line = their bowling
line.split.uniq = ["their", "bowling"] 
line = team
line.split.uniq = ["team"] 
o2 = ["Now", "is", "the", "time", "for", "all", "good", "Ruby",
      "coders", "to", "come", "the", "aid", "of", "their",
      "bowling", "team"]
o3 = #<Enumerator:0x00005992b1a41a08>
arr = ["Now", "is", "the", "time"], arr.max = time
arr = ["for", "all", "good", "Ruby"], arr.max = good
arr = ["coders", "to", "come", "the"], arr.max = coders
arr = ["aid", "of", "their", "bowling"], arr.max = bowling
arr = ["team"], arr.max = team
o3 = ["time", "good", "coders", "bowling", "team"]
person Cary Swoveland    schedule 10.01.2020