我在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
那么,有更简单,更优雅的方式吗?
答案 0 :(得分:10)
只需对您的代码进行轻微重构,假设您的流不包含元素: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
这样的方法更快。
答案 1 :(得分:7)
一次比较一个元素可能是你能做的最好的但是你可以做得比你的“呃”解决方案更好:
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
异常推送到单独的函数中并使用哈希中缺少键是一种相当方便的方法。如果您的信息流包含vals[:s1]
或false
,只检查nil
会导致问题,但检查是否存在密钥可以解决该问题。
答案 2 :(得分:2)
这是通过为Enumerable#zip
创建替代方法来实现这一目标,该方法可以懒惰地工作并且不会创建整个数组。它结合了my implementation Closure的interleave
和其他两个答案(使用sentinel值表示已经到达Enumerable
的结尾 - 导致问题的事实是next
倒带Enumerable
一旦到达终点。)
此解决方案支持多个参数,因此您可以一次比较 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
答案 3 :(得分:1)
根据评论中的讨论,这里是基于zip的解决方案,首先在zip
中包装Enumerator
的块版本,然后使用它来比较相应的元素。
它有效,但已经提到了边缘情况:如果第一个流比另一个流短,则另一个的剩余元素将被丢弃(参见下面的例子)。
我已将此答案标记为社区维基,因为其他成员可以改进它。
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
答案 4 :(得分:0)
这是使用光纤/协同例程的双源示例。这有点啰嗦,但它的行为非常明确,这很好。
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