如何创建一个 Ruby 枚举器,实现对其他两个枚举器进行惰性迭代?

8
假设我有两个枚举器 enum1enum2,它们必须进行惰性迭代(因为它们具有副作用)。如何构建第三个枚举器 enum3,其中 enum3.each{|x| x} 会惰性地返回等同于 enum1 + enum2 的内容?
在我的实际应用中,我正在流式传输两个文件,并需要将它们连接起来进行流式输出。
3个回答

10

这似乎正是我想要的效果;

enums.lazy.flat_map{|enum| enum.lazy }

这是演示。使用带有副作用的方法定义这些可让步的方法;

def test_enum
  return enum_for __method__ unless block_given?
  puts 'hi'
  yield 1
  puts 'hi again'
  yield 2
end  

def test_enum2
  return enum_for __method__ unless block_given?
  puts :a
  yield :a
  puts :b
  yield :b
end  

concated_enum = [test_enum, test_enum2].lazy.flat_map{|en| en.lazy }

然后在结果上调用next,显示副作用是惰性发生的;

[5] pry(main)> concated_enum.next
hi
=> 1
[6] pry(main)> concated_enum.next
hi again
=> 2

1
非常感谢您提供的flat_map解决方案。尽管我们已经验证了flat_map的惰性,但这并没有让我们想到将其用作惰性附加! :) - ms-ati
这种解决方案的缺点是concated_enum.size始终返回nil - skalee
@skalee 我认为这是懒惰枚举的一个缺点;在迭代之前,你永远不知道它们有多大。它们的大小可能取决于你迭代所需的时间长度。它们甚至可以是无限的。 - Alex Altair
1
enums.lazy.flat_map(&:lazy) 这样写更简洁。你也可以加上它的工作原理解释。 - Jonah
最近,自Ruby 2.6以来,您可以使用Enumerable#chain/Enumerator::Chain。请参见下面的答案。 - bts

1

这是我之前写的一些有趣的代码,其中还涉及到了惰性枚举:

def cat(*args)
  args = args.to_enum

  Enumerator.new do |yielder|
    enum = args.next.lazy

    loop do
      begin
        yielder << enum.next
      rescue StopIteration
        enum = args.next.lazy
      end
    end
  end
end

你会像这样使用它:
enum1 = [1,2,3]
enum2 = [4,5,6]
enum3 = cat(enum1, enum2)

enum3.each do |n|
  puts n
end
# => 1
#    2
#    3
#    4
#    5
#    6

...或者只是:

cat([1,2,3],[4,5,6]).each {|n| puts n }

1
自 Ruby 2.6 开始,您可以使用 Enumerable#chain/Enumerator::Chain
a = [1, 2, 3].lazy
b = [4, 5, 6].lazy

a.chain(b).to_a
# => [1, 2, 3, 4, 5, 6]

Enumerator::Chain.new(a, b).to_a
# => [1, 2, 3, 4, 5, 6]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接