Arrayの重複チェックをする

Arrayの重複チェックに手間取ったのでメモ。


普通に考えると、ユニークにして元のものとの差分を取ればいいやと思うのだけど、うまく行かない。

irb(main):001:0> a = [1,1,1,2,2,3]
=> [1, 1, 1, 2, 2, 3]
irb(main):002:0> a - a.uniq
=> []
   ※ここで[1,1,2]が返ってきて欲しい。


Array同士の差を取るときは、集合とみなして重複を削ってしまうそうな。。
http://www.ruby-lang.org/ja/man/html/Array.html#self.20-.20other


というわけで以下のコードを書いた。

def overlapped(an_array)
  an_hash = {}
  overlapped = []
  an_array.each{|a|
    if an_hash.include?(a)
      overlapped << a
    else
      an_hash[a] = nil
    end
  }
  overlapped
end


実行すると、以下になる。うまく行ったね。

irb(main):001:0> a = [1,1,1,2,2,3]
=> [1, 1, 1, 2, 2, 3]
irb(main):002:0> overlapped a
=> [1, 1, 2]


もちろん、Arrayのメソッドに組み込んでしまっても良い。
a.uniqのように、a.overlappedと書けるのでこっちの方が良いな。(破壊的メソッドにするのはさすがに気持ち悪い。。)

class Array
  def overlapped
    an_hash = {}
    overlapped = []
    self.each{|a|
      if an_hash.include?(a)
        overlapped << a
      else
        an_hash[a] = nil
      end
    }
    overlapped
  end
end


うん、injectを使った方が素敵!ということに気づいたので、以下に差し替え。(2008/05/04)

class Array
  def overlapped
    an_hash = Hash.new
    self.inject([]){|r,i|
      an_hash.include?(i) ? (r << i) : (an_hash[i] = nil; r)
    }
  end
end


こんな感じで使える。

irb(main):015:0> a = [1,1,1,2,2,3]
=> [1, 1, 1, 2, 2, 3]
irb(main):016:0> a.overlapped
=> [1, 1, 2]


当然のことながら、uniqしたものと、overlappedしたものを足せば、元に戻る。
順番は変わってしまうので、お互いにsortしないと == にはならない事に注意。

irb(main):055:0> a.sort == (a.uniq + a.overlapped).sort
=> true