Array#fuzzy_equal?

テストの時

assert [1,2,3].fuzzy_equal?(User.find_by_school("hoge").map(&:id))

とかやりたい。eachで比較するのが面倒だし、==メソッドは順序まであってないとfalseになるから。

実装

  • バグあります
def fuzzy_equal?(other)
  original_size = self.size
  size = (self | other).size
  original_size == size
end
で和集合をとってきても元とサイズが同じなら2つは同じ要素からなる。

全然だめだった

  • 反例
[1,1].fuzzy_equal?([1,2]) #=> true
[1,2,3].fuzzy_equal?([1]) #=> true


うーん。同じ要素のこととか考えると、uniqしてなんかしようかな。でも、そうすると、同じ要素が1つになってしまうから、[1]と[1,1]が同じになる。
include?使っても同じ要素が面倒だしなー。


あ、ソートかけて==すればいいのか。

sort使う

これもバグあります。

def fuzzy_equal?(other)
  self.sort_by{|e| e} == other.sort_by{|e| e}
end

要素にnilが含まれている時にあっけなくエラー。
nilを含むソートと、Enumerable#partitionメソッド - @luke_silvia.diaryで使ったpartitionを使うことにした。

最終的にこうなりました

class Array
  def fuzzy_equal?(other)
    self.sort_by_element_with_nil == other.sort_by_element_with_nil
  end

  protected

  def sort_by_element_with_nil
    self_not_nil,self_nil   = partition_by_nil
    self_sorted  = self_not_nil.sort_by_element
    self_sorted + self_nil
  end

  def partition_by_nil
    partition{|e| e}
  end

  def sort_by_element
    sort_by{|e| e}
  end
end

if $0 == __FILE__
  require 'test/unit'

  class TestArrayExt < Test::Unit::TestCase
    def test_fuzzy_equal
      arr = [1,2,3,nil]
      _true_  = [[1,2,3,nil],[3,2,nil,1],[nil,1,3,2]]
      _false_ = [[1],[1,2],[1,1,2,3],[1,2,3,3],[1,2,3,nil,nil]]

      _true_.each{|other| assert arr.fuzzy_equal?(other), other.inspect}
      _false_.each{|other| assert !arr.fuzzy_equal?(other), other.inspect}
    end
  end
end