久保清隆のブログ

ライフハック、健康、旅行など、役立つ情報を書きます。

Rubyで配列内の重複する値を抽出する方法

  • Rubyで重複する値を排除したい場合は、uniqメソッドを使えばOK。
a = [1, 2, 3, 4, 5, 6, 5, 4]
a.uniq

#=> [1, 2, 3, 4, 5, 6]
  • 重複している値があるかどうかを調べるなら、uniqを利用すれば簡単にわかる。
a = [1, 2, 3, 4, 5, 6, 5, 4]
a.size == a.uniq.size

#=> false


でも、配列から重複している値を抽出するメソッドは見当たらない(たぶん)。
そこで、配列から重複している値を抽出するスクリプトを書いてみた。

  • 配列から重複している値を抽出する
a = [1, 2, 3, 4, 5, 6, 5, 4]
a.uniq.map{|v| v if a.inject(Hash.new(0)) {|h, key| h[key] += 1; h}[v] >= 2}.compact

#=> [4, 5]
    • やっていることは、
      • 配列aの値をキーとしてハッシュを作り、出現した回数をカウントする ⇒ a.inject(Hash.new(0)) {|h, key| h[key] += 1; h}
      • バリューが2以上の場合、キーを返す。 ⇒ v if a.inject(Hash.new(0)) {|h, key| h[key] += 1; h}[v] >= 2
      • 配列aが持つユニークな値それぞれについて上記を繰り返す。 ⇒ a.uniq.map{|v| v if a.inject(Hash.new(0)) {|h, key| h[key] += 1; h}[v] >= 2}
      • nilの値を排除する。 ⇒ a.uniq.map{|v| v if a.inject(Hash.new(0)) {|h, key| h[key] += 1; h}[v] >= 2}.compact
    • ちょっと入り組んでいるが、1行でさくっと、もれなく抽出できる。
    • ちなみに、2を3にすると3つ以上あるものを抽出できたりする。
    • メソッドとして定義しておくと便利かも。


しかし、このコードには大きな問題があった。
これだとn+1問題的な問題が発生し、パフォーマンスが著しく悪い。a.inject・・・が毎回行われてしまっている。
そこで、メソッドを以下のように修正。

a = [1, 2, 3, 4, 5, 6, 5, 4]
a.inject(Hash.new(0)){|h, key| h[key] += 1; h}.map {|k,v| k if v >= 2}.compact

#=> [4, 5]
  • @yohfeeさんから紹介して頂いた下記のやり方もシンプルで良いと思う。
    • ただし、ruby1.8.7以上に限る。1.8.6だとone?メソッドがないので、v.size == 1に書き換える必要がある。
a = [1, 2, 3, 4, 5, 6, 5, 4]
a.group_by{|i| i}.reject{|k,v| v.one?}.keys

#=> [4, 5]
  • パフォーマンスチェック
require 'benchmark'
a = (1..5000).to_a
a += [1,3,4,5,2000,4,2]

Benchmark.bm do |x|
  x.report("hash_before:") do
    a.uniq.map{|v| (a.inject(Hash.new(0)) {|h, key| h[key] += 1; h}[v] >= 2) ? v : nil}.compact
  end

  x.report("hash_after :") do
    a.inject(Hash.new(0)){|h, key| h[key] += 1; h}.map {|k,v| k if v >= 2}.compact
  end

  x.report("use_group_by:") do 
    a.group_by{|i| i}.reject{|k,v| v.one?}.keys
  end
end


#       user     system      total        real
# hash_before: 99.370000  25.070000 124.440000 (200.303787)
# hash_after :  0.020000   0.010000   0.030000 (  0.038642)
# use_group_by:  0.040000   0.010000   0.050000 (  0.052790)
    • 最初に作ったものに比べて、パフォーマンスが5000倍くらい改善
    • group_byを使ったやり方も、パフォーマンスはほとんど遜色ない。

※injectメソッドは目新しいかもしれないけど、ここのサイトを見るとよくわかる。

Rubyのオススメ書籍

作りながら学ぶRuby入門 第2版

作りながら学ぶRuby入門 第2版

プログラミング初心者がRubyを学ぶのにオススメ。
言語を学ぶのは文法から入るより実践から入ったほうがよいというのがよくわかる。

初めてのRuby

初めてのRuby

プログラミング中級者がRubyを学ぶのにオススメ。
Rubyのポイントがシンプルにまとめられていて、Rubyへの理解が深まる。



お読み頂きありがとうございます。
もしブログの内容を気に入って頂けましたら、RSSリーダーの登録よろしくお願いします。
twitter@kbktもやってます。IT系の最新情報やライフハック情報をよくつぶやいています。よろしければフォローしてください。
  にほんブログ村 IT技術ブログ プログラム・プログラマへ  人気ブログランキングへ


◆◆このブログのサイトマップへ◆◆