9

在 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

那么,有没有更简单、更优雅的方法呢?

4

5 回答 5

10

假设您的流不包含 element ,只需稍微重构您的代码: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

使用关键字 likeloop应该比使用方法 like 更快each

于 2011-06-26T20:11:06.333 回答
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]如果您的流包含falsenil但检查是否存在密钥可以解决该问题,则仅检查将导致问题。

于 2011-06-26T20:36:08.640 回答
2

这是通过创建替代 for 来做到这一点的镜头Enumerable#zip,它可以懒惰地工作并且不会创建整个数组。它在这里结合了我对 Closure 的实现interleave和其他两个答案(使用哨兵值来指示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
于 2011-06-27T07:27:00.793 回答
1

在评论中的讨论之后,这里是基于 zip 的解决方案,首先将块版本包装zip在 anEnumerator中,然后使用它来比较相应的元素。

它有效,但已经提到了边缘情况:如果第一个流比另一个短,则另一个流的剩余元素将被丢弃(参见下面的示例)。

我已将此答案标记为社区 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
于 2011-06-27T13:44:01.000 回答
0

这是一个使用纤维/协同程序的 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
于 2011-07-06T02:35:15.813 回答