配列で重複する値を見つけて返す方法


170

arr 文字列の配列です:

["hello", "world", "stack", "overflow", "hello", "again"]

arr重複があるかどうかを確認する簡単で洗練された方法は何でしょうか?

例:

["A", "B", "C", "B", "A"]    # => "A" or "B"
["A", "B", "C"]              # => nil

arr == arr.uniqarr重複があるかどうかを確認する簡単でエレガントな方法ですが、重複しているものは提供されません。
Joel AZEMAR

回答:


249
a = ["A", "B", "C", "B", "A"]
a.detect{ |e| a.count(e) > 1 }

これはあまりエレガントな答えではないことは知っていますが、私はそれが大好きです。綺麗なワンライナーコードです。また、巨大なデータセットを処理する必要がない限り、問題なく動作します。

より高速なソリューションをお探しですか?どうぞ!

def find_one_using_hash_map(array)
  map = {}
  dup = nil
  array.each do |v|
    map[v] = (map[v] || 0 ) + 1

    if map[v] > 1
      dup = v
      break
    end
  end

  return dup
end

線形、O(n)ですが、複数のコード行を管理する必要があり、テストケースが必要です。

さらに高速なソリューションが必要な場合は、代わりにCを試してください。

そして、さまざまなソリューションを比較する要点は次のとおりです。https//gist.github.com/naveed-ahmad/8f0b926ffccf5fbd206a1cc58ce9743e


59
線形時間で解けるものの二次式を除いて。
jasonmp85

18
線形問題に対してO(n ^ 2)ソリューションを提供することは、成功する方法ではありません。
tdgs 2013年

21
@ jasonmp85-真; ただし、これはBig-Oランタイムのみを考慮しています。実際には、いくつかの巨大なスケーリングデータ用にこのコードを記述しているのでない限り(そうであれば、実際にはCまたはPythonのみを使用できます)、提供される回答ははるかにエレガントで読みやすいものであり、実行される速度がはるかに遅くなることはありません。線形時間解に。さらに、理論的には、線形時間ソリューションには線形スペースが必要ですが、これは利用できない場合があります
David T.

26
@Kalanamithでは、これを使用して重複した値を取得できますa.select {|e| a.count(e) > 1}.uniq
2013

26
「検出」メソッドの問題は、最初の重複が見つかると停止し、すべての重複が得られないことです。
Jaime Bellmyer、2014年

214

これにはいくつかの方法がありますが、最初のオプションが最速です。

ary = ["A", "B", "C", "B", "A"]

ary.group_by{ |e| e }.select { |k, v| v.size > 1 }.map(&:first)

ary.sort.chunk{ |e| e }.select { |e, chunk| chunk.size > 1 }.map(&:first)

そして、O(N ^ 2)オプション(つまり、効率が悪い):

ary.select{ |e| ary.count(e) > 1 }.uniq

17
最初の2つは、大規模な配列の場合、はるかに効率的です。最後はO(n * n)なので、遅くなる可能性があります。2万個以下の要素を持つ配列にこれを使用する必要があり、最初の2つはほぼ瞬時に返されました。3つ目は時間がかかったのでキャンセルしました。ありがとう!!
Venkat D.

5
単なる観察ですが、.map(&:first)で終わる最初の2つは.keysで終わる可能性があります。これは、その部分がハッシュのキーをプルしているだけだからです。
engineerDave 14年

使用されているルビのバージョンに依存する@engineerDave。1.8.7には&:firstまたは{| k、_ |が必要です k} ActiveSupportなし。
エミリコル2014年

ここにいくつかのベンチマークはありgist.github.com/equivalent/3c9a4c9d07fff79062a3 勝者は明らかにされた性能で group_by.select
equivalent8

6
Ruby> 2.1を使用している場合は、以下を使用できますary.group_by(&:itself)。:-)
ドレンミ2017年

44

オブジェクトのインデックス(左から数える)がオブジェクトのインデックス(右から数える)と等しくない最初のインスタンスを単に見つけます。

arr.detect {|e| arr.rindex(e) != arr.index(e) }

重複がない場合、戻り値はnilになります。

私は、これは、それは追加のオブジェクトの作成に依存しないため、これまでのところ、同様のスレッドに投稿された最速の解決策であると考えている、と#indexして#rindex大きな-Oランタイムはこれより遅いN ^ 2で、Cで実装されていますセルジオのものですが、「遅い」パーツがCで実行されるため、ウォールタイムははるかに速くなる可能性があります。


5
私はこのソリューションが好きですが、最初の複製のみを返します。すべての重複を見つけるには:arr.find_all {|e| arr.rindex(e) != arr.index(e) }.uniq
Josh

1
また、あなたの答えは、3連が存在するかどうか、または配列から要素を描画して「CAT」をスペルできるかどうかを見つける方法を示していません。
Cary Swoveland、2015

3
@ bruno077この線形時間はどうですか?
beauby 2016年

4
@chrisすばらしい答えですが、これでもう少し上手くできると思いますarr.detect.with_index { |e, idx| idx != arr.rindex(e) }。を使用with_indexすると、最初のindex検索の必要性がなくなります。
ki4jnq 2016

これを2D配列にどのように適用して、列の重複を比較しますか?
ahnbizcad 2016

30

detect重複を1つだけ見つけます。find_allそれらすべてを見つけるでしょう:

a = ["A", "B", "C", "B", "A"]
a.find_all { |e| a.count(e) > 1 }

3
質問は非常に具体的であり、1つの複製のみが返されます。イモは、すべての重複を見つける方法を示すのは問題ありませんが、質問の答えに答えるために、あなたがまだ行っていないものです。ところで、count配列内のすべての要素に対して呼び出すのは苦痛で非効率的です。(計数ハッシュは、例えば、はるかに効率的である;例えば、構築h = {"A"=>2, "B"=>2, "C"=> 1 }その後h.select { |k,v| v > 1 }.keys #=> ["A", "B"]
ケアリーSwoveland

24

重複を見つけるための2つの方法を次に示します。

セットを使用する

require 'set'

def find_a_dup_using_set(arr)
  s = Set.new
  arr.find { |e| !s.add?(e) }
end

find_a_dup_using_set arr
  #=> "hello" 

select代わりに使用してfind、すべての重複の配列を返します。

使用する Array#difference

class Array
  def difference(other)
    h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
    reject { |e| h[e] > 0 && h[e] -= 1 }
  end
end

def find_a_dup_using_difference(arr)
  arr.difference(arr.uniq).first
end

find_a_dup_using_difference arr
  #=> "hello" 

ドロップ.firstすると、すべての重複の配列が返されます。

nil重複がない場合、両方のメソッドが戻ります。

Rubyコアに追加することを提案しArray#differenceました。詳細については、こちらの私の回答をご覧ください

基準

提案された方法を比較してみましょう。まず、テスト用の配列が必要です。

CAPS = ('AAA'..'ZZZ').to_a.first(10_000)
def test_array(nelements, ndups)
  arr = CAPS[0, nelements-ndups]
  arr = arr.concat(arr[0,ndups]).shuffle
end

さまざまなテストアレイのベンチマークを実行する方法:

require 'fruity'

def benchmark(nelements, ndups)
  arr = test_array nelements, ndups
  puts "\n#{ndups} duplicates\n"    
  compare(
    Naveed:    -> {arr.detect{|e| arr.count(e) > 1}},
    Sergio:    -> {(arr.inject(Hash.new(0)) {|h,e| h[e] += 1; h}.find {|k,v| v > 1} ||
                     [nil]).first },
    Ryan:      -> {(arr.group_by{|e| e}.find {|k,v| v.size > 1} ||
                     [nil]).first},
    Chris:     -> {arr.detect {|e| arr.rindex(e) != arr.index(e)} },
    Cary_set:  -> {find_a_dup_using_set(arr)},
    Cary_diff: -> {find_a_dup_using_difference(arr)}
  )
end

返される重複は1つだけなので、@ JjPの回答は含めませんでした。その回答を変更して@Naveedの以前の回答と同じにしました。また、@ Marinの回答も含めませんでした。これは、@ Naveedの回答の前に投稿されたが、1つだけではなくすべての重複を返しました(マイナーなポイントですが、両方を評価するポイントはありません。

また、すべての重複を返す他の回答を変更して、最初に見つかったものだけを返すようにしました。

各ベンチマークの結果は、速いものから遅いものの順にリストされています。

まず、配列に100個の要素が含まれているとします。

benchmark(100, 0)
0 duplicates
Running each test 64 times. Test will take about 2 seconds.
Cary_set is similar to Cary_diff
Cary_diff is similar to Ryan
Ryan is similar to Sergio
Sergio is faster than Chris by 4x ± 1.0
Chris is faster than Naveed by 2x ± 1.0

benchmark(100, 1)
1 duplicates
Running each test 128 times. Test will take about 2 seconds.
Cary_set is similar to Cary_diff
Cary_diff is faster than Ryan by 2x ± 1.0
Ryan is similar to Sergio
Sergio is faster than Chris by 2x ± 1.0
Chris is faster than Naveed by 2x ± 1.0

benchmark(100, 10)
10 duplicates
Running each test 1024 times. Test will take about 3 seconds.
Chris is faster than Naveed by 2x ± 1.0
Naveed is faster than Cary_diff by 2x ± 1.0 (results differ: AAC vs AAF)
Cary_diff is similar to Cary_set
Cary_set is faster than Sergio by 3x ± 1.0 (results differ: AAF vs AAC)
Sergio is similar to Ryan

次に、10,000要素の配列について考えます。

benchmark(10000, 0)
0 duplicates
Running each test once. Test will take about 4 minutes.
Ryan is similar to Sergio
Sergio is similar to Cary_set
Cary_set is similar to Cary_diff
Cary_diff is faster than Chris by 400x ± 100.0
Chris is faster than Naveed by 3x ± 0.1

benchmark(10000, 1)
1 duplicates
Running each test once. Test will take about 1 second.
Cary_set is similar to Cary_diff
Cary_diff is similar to Sergio
Sergio is similar to Ryan
Ryan is faster than Chris by 2x ± 1.0
Chris is faster than Naveed by 2x ± 1.0

benchmark(10000, 10)
10 duplicates
Running each test once. Test will take about 11 seconds.
Cary_set is similar to Cary_diff
Cary_diff is faster than Sergio by 3x ± 1.0 (results differ: AAE vs AAA)
Sergio is similar to Ryan
Ryan is faster than Chris by 20x ± 10.0
Chris is faster than Naveed by 3x ± 1.0

benchmark(10000, 100)
100 duplicates
Cary_set is similar to Cary_diff
Cary_diff is faster than Sergio by 11x ± 10.0 (results differ: ADG vs ACL)
Sergio is similar to Ryan
Ryan is similar to Chris
Chris is faster than Naveed by 3x ± 1.0

以下のfind_a_dup_using_difference(arr)場合、はるかに効率的であることに注意してくださいArray#differenceRubyコアに追加された場合はCで実装された。

結論

答えの多くは合理的ですが、セットを使用するのが明らかに最良の選択です。中程度のハードケースで最速、最もハードで最速で、計算上は些細なケースでのみ選択できます。どちらを選択してもかまいません。

Chrisのソリューションを選択できる非常に特殊なケースの1つは、このメソッドを使用して数千の小さな配列を個別に重複排除し、通常は10アイテム未満の重複を見つけることを期待する場合です。これは少し高速になりますセットを作成するための小さな追加のオーバーヘッドを回避するため。


1
優れたソリューション。一部のメソッドほど最初は何が起こっているかは明らかではありませんが、少し線形のメモリを犠牲にして、本当に線形時間で実行する必要があります。
Chris Heald、2015

find_a_dup_using_setを使用すると、重複の1つではなく、Setが返されます。また、Rubyドキュメントで「find.with_object」を見つけることができません。
ScottJ 2016年

@Scottj、キャッチをありがとう!今まで誰もそれを捕らえなかったことは興味深いです。それを私が直した。これは、Enumerator#with_objectにチェーンされたEnumerable#find です。ベンチマークを更新して、ソリューションなどを追加します。
Cary Swoveland

1
優れた比較@CarySwoveland
16

19

悲しいかなほとんどの答えは O(n^2)です。

これがO(n)解決策です

a = %w{the quick brown fox jumps over the lazy dog}
h = Hash.new(0)
a.find { |each| (h[each] += 1) == 2 } # => 'the"

これの複雑さは何ですか?

  • 走る O(n)最初の試合で、中断します
  • O(n)メモリを使用しますが、最小量のみ

さて、配列内の重複の頻度に応じて、これらのランタイムは実際にはさらに良くなる可能性があります。たとえば、サイズの配列がさまざまな要素のO(n)母集団からサンプリングされたk << n場合、ランタイムとスペースの両方の複雑さだけO(k)がになりますが、元の投稿者が入力を検証しており、重複がないことを確認したいと考えています。その場合O(n)、要素が大多数の入力に対して繰り返しを持たないことが期待されるため、ランタイムとメモリの両方の複雑さ。


15

Ruby Arrayオブジェクトには素晴らしいメソッドがありselectます。

select {|item| block }  new_ary
select  an_enumerator

最初のフォームは、ここであなたが興味を持つものです。テストに合格したオブジェクトを選択できます。

Ruby Arrayオブジェクトには別のメソッドがありcountます。

count  int
count(obj)  int
count { |item| block }  int

この場合は、重複(配列に複数回出現するオブジェクト)に関心があります。適切なテストはa.count(obj) > 1です。

の場合a = ["A", "B", "C", "B", "A"]

a.select{|item| a.count(item) > 1}.uniq
=> ["A", "B"]

1つのオブジェクトのみが必要であると述べています。だから選んでください。


1
Iこの一つのロットがありますが、最後にUNIQを投げるために持っているか、あなたが買ってあげるような["A", "B", "B", "A"]
Joeyjoejoejr

1
すばらしい答えです。これはまさに私が探していたものです。@Joeyjoejoejrが指摘したように。.uniq配列に配置する編集を送信しました。
スーリヤ

これは非常に非効率的です。すべての重複を見つけて、1つを除いてすべて破棄するだけでなくcount、配列の各要素に対して呼び出しを行いますが、これは無駄で不要です。JjPの回答に関する私のコメントを参照してください。
Cary Swoveland、2015

ベンチマークを実行していただきありがとうございます。さまざまなソリューションが実行時間でどのように比較されるかを確認すると便利です。エレガントな答えは読みやすいですが、多くの場合、最も効率的ではありません。
Martin Velez、

9

find_all()は、array含まenumれてblockいないすべての要素を含むを返しますfalse

duplicate要素を取得するには

>> arr = ["A", "B", "C", "B", "A"]
>> arr.find_all { |x| arr.count(x) > 1 }

=> ["A", "B", "B", "A"]

またはuniq要素を複製する

>> arr.find_all { |x| arr.count(x) > 1 }.uniq
=> ["A", "B"] 

7

このようなものはうまくいきます

arr = ["A", "B", "C", "B", "A"]
arr.inject(Hash.new(0)) { |h,e| h[e] += 1; h }.
    select { |k,v| v > 1 }.
    collect { |x| x.first }

つまり、すべての値をハッシュに配置します。ここで、キーは配列の要素であり、値は出現回数です。次に、複数回出現するすべての要素を選択します。簡単です。


7

このスレッドは具体的にはRubyに関するものですが、Ruby on RailsのコンテキストでActiveRecordを使用してこれを行う方法を探してここにたどり着き、自分の解決策も共有したいと思いました。

class ActiveRecordClass < ActiveRecord::Base
  #has two columns, a primary key (id) and an email_address (string)
end

ActiveRecordClass.group(:email_address).having("count(*) > 1").count.keys

上記は、この例のデータベーステーブル(Railsでは「active_record_classes」になります)で複製されたすべての電子メールアドレスの配列を返します。


6
a = ["A", "B", "C", "B", "A"]
a.each_with_object(Hash.new(0)) {|i,hash| hash[i] += 1}.select{|_, count| count > 1}.keys

これはO(n)手順です。

または、次のいずれかの行を実行できます。また、O(n)ですが、反復は1つだけです

a.each_with_object(Hash.new(0).merge dup: []){|x,h| h[:dup] << x if (h[x] += 1) == 2}[:dup]

a.inject(Hash.new(0).merge dup: []){|h,x| h[:dup] << x if (h[x] += 1) == 2;h}[:dup]

2

これは、大量のデータ(重複するパーツを見つけるためのレガシーdBaseテーブルなど)に対する私の見解です。

# Assuming ps is an array of 20000 part numbers & we want to find duplicates
# actually had to it recently.
# having a result hash with part number and number of times part is 
# duplicated is much more convenient in the real world application
# Takes about 6  seconds to run on my data set
# - not too bad for an export script handling 20000 parts

h = {};

# or for readability

h = {} # result hash
ps.select{ |e| 
  ct = ps.count(e) 
  h[e] = ct if ct > 1
}; nil # so that the huge result of select doesn't print in the console

2
r = [1, 2, 3, 5, 1, 2, 3, 1, 2, 1]

r.group_by(&:itself).map { |k, v| v.size > 1 ? [k] + [v.size] : nil }.compact.sort_by(&:last).map(&:first)

1

each_with_object あなたの友だちです!

input = [:bla,:blubb,:bleh,:bla,:bleh,:bla,:blubb,:brrr]

# to get the counts of the elements in the array:
> input.each_with_object({}){|x,h| h[x] ||= 0; h[x] += 1}
=> {:bla=>3, :blubb=>2, :bleh=>2, :brrr=>1}

# to get only the counts of the non-unique elements in the array:
> input.each_with_object({}){|x,h| h[x] ||= 0; h[x] += 1}.reject{|k,v| v < 2}
=> {:bla=>3, :blubb=>2, :bleh=>2}

1

このコードは、重複する値のリストを返します。ハッシュキーは、既に表示されている値を確認する効率的な方法として使用されます。値が表示されたかどうかに基づいて、元の配列aryは2つの配列に分割されます。1つは一意の値を含み、2つ目は重複を含みます。

ary = ["hello", "world", "stack", "overflow", "hello", "again"]

hash={}
arr.partition { |v| hash.has_key?(v) ? false : hash[v]=0 }.last.uniq

=> ["hello"]

構文は少し複雑になりますが、さらに次の形式に短縮できます。

hash={}
arr.partition { |v| !hash.has_key?(v) && hash[v]=0 }.last.uniq


0

(1つではなく)2つの異なる配列を比較する場合、非常に高速な方法は&RubyのArrayクラスによって提供される交差演算子を使用することです。

# Given
a = ['a', 'b', 'c', 'd']
b = ['e', 'f', 'c', 'd']

# Then this...
a & b # => ['c', 'd']

1
これにより、両方の配列に存在するアイテムが検出され、1つの配列に重複するものは検出されません。
Kimmo Lehto

ご指摘いただきありがとうございます。回答の表現を変更しました。検索から来ている一部の人々にとって既に役立つことが証明されているので、ここには残しておきます。
IAmNaN

0

重複の数とそれらが何であるかを知る必要があったので、Naveedが以前に投稿したものから構築された関数を作成しました。

def print_duplicates(array)
  puts "Array count: #{array.count}"
  map = {}
  total_dups = 0
  array.each do |v|
    map[v] = (map[v] || 0 ) + 1
  end

  map.each do |k, v|
    if v != 1
      puts "#{k} appears #{v} times"
      total_dups += 1
    end
  end
  puts "Total items that are duplicated: #{total_dups}"
end

-1
  1. 要素の配列を入力として取る複製メソッドを作成しましょう
  2. メソッド本体で、2つの新しい配列オブジェクトを作成しましょう。1つは表示され、もう1つは複製されます。
  3. 最後に、指定された配列内の各オブジェクトを反復処理し、反復ごとに、表示された配列に存在するオブジェクトを見つけます。
  4. seen_arrayにオブジェクトが存在する場合、それは重複オブジェクトと見なされ、そのオブジェクトをduplication_arrayにプッシュします
  5. オブジェクトがseenに存在しない場合、それは一意のオブジェクトと見なされ、そのオブジェクトをseen_arrayにプッシュします。

コード実装で実演してみましょう

def duplication given_array
  seen_objects = []
  duplication_objects = []

  given_array.each do |element|
    duplication_objects << element if seen_objects.include?(element)
    seen_objects << element
  end

  duplication_objects
end

複製メソッドを呼び出して、戻り結果を出力します-

dup_elements = duplication [1,2,3,4,4,5,6,6]
puts dup_elements.inspect

このサイトでは、コードのみの回答は一般的に嫌われています。回答を編集して、コードのコメントや説明を含めていただけますか?説明では、次のような質問に答える必要があります。どうやってやるの?どこに行くの?OPの問題をどのように解決しますか?参照:アンサーの方法。ありがとう!
Eduardo Baitello、

-4

[1,2,3].uniq!.nil? => true [1,2,3,3].uniq!.nil? => false

上記は破壊的であることに注意してください


これは重複した値を返しません
andriy-baran
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.