回答:
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
a.select {|e| a.count(e) > 1}.uniq
これにはいくつかの方法がありますが、最初のオプションが最速です。
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
group_by.select
ary.group_by(&:itself)
。:-)
オブジェクトのインデックス(左から数える)がオブジェクトのインデックス(右から数える)と等しくない最初のインスタンスを単に見つけます。
arr.detect {|e| arr.rindex(e) != arr.index(e) }
重複がない場合、戻り値はnilになります。
私は、これは、それは追加のオブジェクトの作成に依存しないため、これまでのところ、同様のスレッドに投稿された最速の解決策であると考えている、と#index
して#rindex
大きな-Oランタイムはこれより遅いN ^ 2で、Cで実装されていますセルジオのものですが、「遅い」パーツがCで実行されるため、ウォールタイムははるかに速くなる可能性があります。
arr.find_all {|e| arr.rindex(e) != arr.index(e) }.uniq
arr.detect.with_index { |e, idx| idx != arr.rindex(e) }
。を使用with_index
すると、最初のindex
検索の必要性がなくなります。
detect
重複を1つだけ見つけます。find_all
それらすべてを見つけるでしょう:
a = ["A", "B", "C", "B", "A"]
a.find_all { |e| a.count(e) > 1 }
count
配列内のすべての要素に対して呼び出すのは苦痛で非効率的です。(計数ハッシュは、例えば、はるかに効率的である;例えば、構築h = {"A"=>2, "B"=>2, "C"=> 1 }
その後h.select { |k,v| v > 1 }.keys #=> ["A", "B"]
。
重複を見つけるための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#difference
Rubyコアに追加された場合はCで実装された。
結論
答えの多くは合理的ですが、セットを使用するのが明らかに最良の選択です。中程度のハードケースで最速、最もハードで最速で、計算上は些細なケースでのみ選択できます。どちらを選択してもかまいません。
Chrisのソリューションを選択できる非常に特殊なケースの1つは、このメソッドを使用して数千の小さな配列を個別に重複排除し、通常は10アイテム未満の重複を見つけることを期待する場合です。これは少し高速になりますセットを作成するための小さな追加のオーバーヘッドを回避するため。
悲しいかなほとんどの答えは 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)
、要素が大多数の入力に対して繰り返しを持たないことが期待されるため、ランタイムとメモリの両方の複雑さ。
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つのオブジェクトのみが必要であると述べています。だから選んでください。
["A", "B", "B", "A"]
.uniq
配列に配置する編集を送信しました。
count
、配列の各要素に対して呼び出しを行いますが、これは無駄で不要です。JjPの回答に関する私のコメントを参照してください。
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"]
このようなものはうまくいきます
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 }
つまり、すべての値をハッシュに配置します。ここで、キーは配列の要素であり、値は出現回数です。次に、複数回出現するすべての要素を選択します。簡単です。
このスレッドは具体的には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」になります)で複製されたすべての電子メールアドレスの配列を返します。
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]
これは、大量のデータ(重複するパーツを見つけるためのレガシー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
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}
このコードは、重複する値のリストを返します。ハッシュキーは、既に表示されている値を確認する効率的な方法として使用されます。値が表示されたかどうかに基づいて、元の配列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
a = ["A", "B", "C", "B", "A"]
b = a.select {|e| a.count(e) > 1}.uniq
c = a - b
d = b + c
結果
d
=> ["A", "B", "C"]
(1つではなく)2つの異なる配列を比較する場合、非常に高速な方法は&
、RubyのArrayクラスによって提供される交差演算子を使用することです。
# Given
a = ['a', 'b', 'c', 'd']
b = ['e', 'f', 'c', 'd']
# Then this...
a & b # => ['c', 'd']
重複の数とそれらが何であるかを知る必要があったので、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
コード実装で実演してみましょう
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
[1,2,3].uniq!.nil? => true
[1,2,3,3].uniq!.nil? => false
上記は破壊的であることに注意してください
arr == arr.uniq
arr
重複があるかどうかを確認する簡単でエレガントな方法ですが、重複しているものは提供されません。