Ruby - 优雅地比较两个枚举器
Posted
技术标签:
【中文标题】Ruby - 优雅地比较两个枚举器【英文标题】:Ruby - Compare two Enumerators elegantly 【发布时间】:2011-06-26 19:10:56 【问题描述】:在 Ruby (1.9.2) 中,我有来自两个不同来源(二进制数据)的两个长数字流。
这两个来源被封装成两个Enumerators。
我想检查两个流是否完全相等。
我提供了几个解决方案,但似乎都不太优雅。
第一个简单地将两者都转换为数组:
def equal_streams?(s1, s2)
s1.to_a == s2.to_a
end
这可行,但在内存方面不是很高效,特别是在流包含大量信息的情况下。
另一种选择是……呃。
def equal_streams?(s1, s2)
s1.each do |e1|
begin
e2 = s2.next
return false unless e1 == e2 # Different element found
rescue StopIteration
return false # s2 has run out of items before s1
end
end
begin
s2.next
rescue StopIteration
# s1 and s2 have run out of elements at the same time; they are equal
return true
end
return false
end
那么,有没有更简单、更优雅的方法呢?
【问题讨论】:
Enumerable#zip 会更优雅,但在性能方面很可能不会有任何改进。 看起来在 Ruby 1.9 中处理这个问题的高性能方法是使用纤维/协程。见infoq.com/news/2007/08/ruby-1-9-fibers。 @Steve:zip
的问题在于它在内部创建数组,无论您传递什么 Enumerable。输入参数的长度还有另一个问题。
@Mladen:我知道——这就是为什么我说它很可能不会在性能上有任何改进。
@Mladen: 1.times.zip(1.times) |x,y| puts x, y; 42
打印出 0、0 并返回 nil
。
【参考方案1】:
假设您的流不包含元素 :eof
,只需对您的代码进行轻微重构。
def equal_streams?(s1, s2)
loop do
e1 = s1.next rescue :eof
e2 = s2.next rescue :eof
return false unless e1 == e2
return true if e1 == :eof
end
end
使用像loop
这样的关键字应该比使用像each
这样的方法更快。
【讨论】:
我非常喜欢这个。我没有想到我可以使用“保护”值,例如您示例中的 :eof 符号。谢谢! 哦——我在脑海中想到我们需要一个解决方案可枚举重复调用传递给#each 的块。只要我们可以拉动,这是一个很好的解决方案。如果它必须与枚举一起工作,那么就需要协程。【参考方案2】:一次比较一个元素可能是你能做到的最好的,但你可以做得比你的“呃”解决方案更好:
def grab_next(h, k, s)
h[k] = s.next
rescue StopIteration
end
def equal_streams?(s1, s2)
loop do
vals =
grab_next(vals, :s1, s1)
grab_next(vals, :s2, s2)
return true if(vals.keys.length == 0) # Both of them ran out.
return false if(vals.keys.length == 1) # One of them ran out early.
return false if(vals[:s1] != vals[:s2]) # Found a mismatch.
end
end
棘手的部分是区分只有一个流用完和两个流用完。将StopIteration
异常推送到单独的函数中并使用散列中缺少键是一种相当方便的方法。如果您的流包含 false
或 nil
,则仅检查 vals[:s1]
会导致问题,但检查是否存在密钥可以解决该问题。
【讨论】:
感谢您的回答。我更喜欢sawa,但为您的时间+1! @kikito:这很酷,我也赞成sawa 的回答。 Sawa 和我使用几乎相同的逻辑,我只是使用没有哈希键来表示结束而不是sentinel value。如果你知道你永远不会在你的流中使用:eof
符号,那么就不需要额外的机制来避免使用哨兵。
你是对的。我的与:eof
有轻微冲突的可能性。 +1 比我的更通用的解决方案。
@sawa:但:eof
可能是哨兵的最佳选择,具体情况涉及“数字流”,如果:eof
符号出现在数字流中,那么 kikito 有更多他手上的问题比保留的哨兵价值。【参考方案3】:
这是通过为Enumerable#zip
创建一个替代方案来实现它的一个镜头,该替代方案运行缓慢并且不会创建整个数组。它结合了 Closure 的 interleave
的 my implementation 和其他两个答案(使用标记值来指示已到达 Enumerable
的结尾 - 导致问题的事实是 next
一旦到达结束)。
此方案支持多个参数,因此您可以一次比较 n 个结构。
module Enumerable
# this should be just a unique sentinel value (any ideas for more elegant solution?)
END_REACHED = Object.new
def lazy_zip *others
sources = ([self] + others).map(&:to_enum)
Enumerator.new do |yielder|
loop do
sources, values = sources.map|s|
[s, s.next] rescue [nil, END_REACHED]
.transpose
raise StopIteration if values.all?|v| v == END_REACHED
yielder.yield values.map|v| v == END_REACHED ? nil : v
end
end
end
end
因此,当您有 zip
的变体,它可以延迟工作并且在第一个可枚举到达末尾时不会停止迭代,您可以使用 all?
或 any?
来实际检查相应元素的相等性。
# zip would fail here, as it would return just [[1,1],[2,2],[3,3]]:
p [1,2,3].lazy_zip([1,2,3,4]).all?|l,r| l == r
#=> false
# this is ok
p [1,2,3,4].lazy_zip([1,2,3,4]).all?|l,r| l == r
#=> true
# comparing more than two input streams:
p [1,2,3,4].lazy_zip([1,2,3,4],[1,2,3]).all?|vals|
# check for equality by checking length of the uniqued array
vals.uniq.length == 1
#=> false
【讨论】:
“延迟工作并且不会创建整个数组” - zip 通常也不会创建整个数组:请参阅 ***.com/questions/6486107/… 和 ***.com/questions/6487747/… 我的评论链接到这个问题。对不起,我完全失败了!【参考方案4】:根据 cmets 中的讨论,这里是基于 zip 的解决方案,首先将 zip
的块版本包装在 Enumerator
中,然后使用它来比较相应的元素。
它有效,但是已经提到了边缘情况:如果第一个流比另一个短,则另一个流的剩余元素将被丢弃(参见下面的示例)。
我已将此答案标记为社区 wiki,因为其他成员可以改进它。
def zip_lazy *enums
Enumerator.new do |yielder|
head, *tail = enums
head.zip(*tail) do |values|
yielder.yield values
end
end
end
p zip_lazy(1..3, 1..4).all?|l,r| l == r
#=> true
p zip_lazy(1..3, 1..3).all?|l,r| l == r
#=> true
p zip_lazy(1..4, 1..3).all?|l,r| l == r
#=> false
【讨论】:
【参考方案5】:这是一个使用纤维/协同例程的 2 源示例。它有点啰嗦,但它的行为非常明确,这很好。
def zip_verbose(enum1, enum2)
e2_fiber = Fiber.new do
enum2.each|e2| Fiber.yield true, e2
Fiber.yield false, nil
end
e2_has_value, e2_val = true, nil
enum1.each do |e1_val|
e2_has_value, e2_val = e2_fiber.resume if e2_has_value
yield [true, e1_val], [e2_has_value, e2_val]
end
return unless e2_has_value
loop do
e2_has_value, e2_val = e2_fiber.resume
break unless e2_has_value
yield [false, nil], [e2_has_value, e2_val]
end
end
def zip(enum1, enum2)
zip_verbose(enum1, enum2) |e1, e2| yield e1[1], e2[1]
end
def self.equal?(enum1, enum2)
zip_verbose(enum1, enum2) do |e1,e2|
return false unless e1 == e2
end
return true
end
【讨论】:
以上是关于Ruby - 优雅地比较两个枚举器的主要内容,如果未能解决你的问题,请参考以下文章