それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Rubyで配列から重複したモノ(要素)を抜き出す(Uniqの逆)

Rubyで配列の重複しているモノを調べるには。

Rubyって配列から重複しているモノだけを知りたい時どうするか?Yahoo!知恵袋の回答はアテにならないし、OKWaveもイマイチだった。そこでマニュアルとにらめっこして考えた。

配列から重複を取り出す例。

=> a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 4, 6, 8]    #偶数がダブりの配列
>> a.select{|e| a.index(e)!=a.rindex(e)}
=> [2, 4, 6, 8, 2, 4, 6, 8]
>> a.select{|e| a.index(e)!=a.rindex(e)}.uniq
=> [2, 4, 6, 8]

rindex/indexの活用。

index(val) 最初の形式では、val と == で等しい最初の要素の位置を返 します。
rindex(val) val と == で等しい最後の要素の位 置を返します。

と言うわけ。後ろから探した場合と前から探した場合で見つかったIndex番号が違うとその要素は重複ってわけ。

ま、当たり前なんだけどね。

コレを使ってどの要素が重複していたか調べることが出来る。

ちなみに重複がある調べるなら

a.size - a.uniq.size

っかなぁ

2010-01-04追記

上記のやり方だと1000件以上重複があるときにかなり遅い。ループに無駄があるからなんだ。
先にuniqしておくと無駄が少ないかも

a.uniq.select{|i| a.index(i) != a.rindex(i)}

2012-06-29追記

sort/each_with_index で新しい配列を作れば出来る。ってid:Kirovさんからトラックバックを頂いた。たしかにそうなんだけど。言い訳させて下さい。このエントリを書いてる時に処理してた配列データは数GBあって、新しくソート済み配列作るとメモリ上限で即死だったんだ。しかもEnumerableがActiveRecordで扱いがめんどくさかった。

ちなみに、Ruby1.9のEnumratorの導入後ならもっと簡単に出来ますよ。

sortかつ、each_with_indexが使えると、かなり楽です

a =[1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9]
a.sort!
a.select.with_index{|e,i| e==a[i+1] } #=> [2, 4, 6, 8]

(当時はirbのconf.echo=falseを知らなかったので、irbを1行書くたび大量データ表示のechoがあって表示待ちで時間かかる。さらに表示データが大きすぎるとirbがメモリ上限で死んだりしてたりしてて困っていたと思う。今にして思えば。可愛い悩みだわ)

追記2013-11-20:「最後の間違ってるなぁ。 重複が3つあった時に正常に動作しない」⇐間違ってはないはず

ブクマコメントが気になったので追記
id:hisatake_iのブクマコメントが気になりました
http://b.hatena.ne.jp/hisatake_i/20130723#bookmark-28550154

確かに、重複がn個あったときに、n-1 個出てきてしまう。たしかに「間違い」かも知れない。
でも、重複の個数も知りたい時もあり、私にとっては個数複数個でも正解。

Uniq の逆をやりたい時の期待値

[1,2,3,3,3] - [1,2,3]  #=> [3] または [3,3] のいずれかを期待。結果は[]

重複がn個の場合は利用場面に依るんじゃないでしょうか。

Uniqの逆の動作 1.9版

a=[1,2,3,3,3]
a.sort!
a.select.with_index{|e,i| e==a[i+1] }          #=> [3, 3] -- uniq 無し
a.uniq.select.with_index{|e,i| e==a[i+1] }   #=> [3]     -- uniq 有り

このエントリの前半でもuniq付きとuniqなしの2つを紹介しています。


ruby の配列の引き算でできたら便利なんだけど、比較演算子の仕様でソレは出来ない

[1,2,3,3,3] - [1,2,3] #=> [] 当然空になる