Ecco altri due modi per trovare un duplicato.
Usa un set
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"
Utilizzare select
al posto di find
per restituire una matrice di tutti i duplicati.
Uso 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"
Rilascia .first
per restituire un array di tutti i duplicati.
Entrambi i metodi restituiscono nil
se non ci sono duplicati.
Ho proposto diArray#difference
aggiungerlo al nucleo di Ruby. Maggiori informazioni sono nella mia risposta qui .
Prova delle prestazioni
Confrontiamo i metodi suggeriti. Innanzitutto, abbiamo bisogno di un array per i test:
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
e un metodo per eseguire i benchmark per diversi array di test:
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
Non ho incluso la risposta di @ JjP perché deve essere restituito un solo duplicato e quando la sua risposta viene modificata per farlo è la stessa della risposta precedente di @ Naveed. Né ho incluso la risposta di @ Marin, che, sebbene pubblicata prima della risposta di @ Naveed, ha restituito tutti i duplicati anziché solo uno (un punto minore ma non ha senso valutare entrambi, poiché sono identici quando restituiscono un solo duplicato).
Ho anche modificato altre risposte che hanno restituito tutti i duplicati per restituire solo il primo trovato, ma che non dovrebbero avere alcun effetto sulle prestazioni, poiché hanno calcolato tutti i duplicati prima di selezionarne uno.
I risultati per ciascun benchmark sono elencati dal più veloce al più lento:
Supponiamo innanzitutto che l'array contenga 100 elementi:
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
Ora considera un array con 10.000 elementi:
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
Nota che find_a_dup_using_difference(arr)
sarebbe molto più efficiente se Array#difference
fosse implementato in C, il che sarebbe il caso se fosse aggiunto al core Ruby.
Conclusione
Molte delle risposte sono ragionevoli ma l' utilizzo di un set è la scelta migliore . È il più veloce nei casi medio-duri, il più veloce nei casi più difficili e solo in casi banali dal punto di vista computazionale - quando la tua scelta non conta comunque - può essere battuto.
L'unico caso molto particolare in cui potresti scegliere la soluzione di Chris sarebbe se vuoi usare il metodo per de-duplicare separatamente migliaia di piccoli array e prevedi di trovare un duplicato in genere inferiore a 10 elementi. Questo sarà un po 'più veloce in quanto evita il piccolo sovraccarico aggiuntivo della creazione del set.
arr == arr.uniq
sarebbe un modo semplice ed elegante per verificare searr
ha duplicati, tuttavia, non fornisce quali sono stati duplicati.