Dizide yinelenen bir değeri bulma ve döndürme


170

arr dizeler dizisidir:

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

Kopyaların olup olmadığını kontrol etmenin kolay ve zarif bir yolu ne olabilir arrve eğer öyleyse bunlardan birini iade edin (hangisi olursa olsun)?

Örnekler:

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

arr == arr.uniqarryinelemelerin olup olmadığını kontrol etmenin kolay ve zarif bir yolu olabilir , ancak yinelenenleri sağlamaz.
Joel AZEMAR

Yanıtlar:


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

Bunun çok zarif bir cevap olmadığını biliyorum, ama seviyorum. Güzel bir astar kodu. Büyük veri kümelerini işlemeniz gerekmedikçe mükemmel şekilde çalışır.

Daha hızlı bir çözüm mü arıyorsunuz? Hadi bakalım!

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

Doğrusal, O (n), ancak şimdi birden fazla kod satırını yönetmesi gerekiyor, test senaryoları gerekiyor vb.

Daha hızlı bir çözüme ihtiyacınız varsa, bunun yerine C'yi deneyin.

Ve farklı çözümleri karşılaştıran özü: https://gist.github.com/naveed-ahmad/8f0b926ffccf5fbd206a1cc58ce9743e


59
Doğrusal zamanda çözülebilecek bir şey için karesel hariç.
jasonmp85

18
Doğrusal problemler için O (n ^ 2) çözümleri sunmak, gidilecek yol değildir.
tdgs

21
@ jasonmp85 - Doğru; ancak bu sadece büyük-O çalışma zamanını göz önünde bulundurur. pratikte, bu kodu bazı büyük ölçekleme verileri için yazmıyorsanız (ve eğer öyleyse, aslında sadece C veya Python'u kullanabilirsiniz), verilen cevap çok daha zarif / okunabilirdir ve daha yavaş çalışmayacak lineer zaman çözümüne. ayrıca, teorik olarak, lineer zaman çözümü mevcut olmayan lineer alan gerektirir
David T.

26
@Kalanamith bunu kullanarak yinelenen değerler alabilirsiniza.select {|e| a.count(e) > 1}.uniq
Naveed

26
"Algıla" yöntemiyle ilgili sorun, ilk kopyayı bulduğunda durması ve size tüm kopyaları vermemesidir.
Jaime Bellmyer

214

İlk seçenek en hızlı olacak şekilde bunu birkaç şekilde yapabilirsiniz:

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)

Ve bir O (N ^ 2) seçeneği (yani daha az verimli):

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

17
İlk ikisi büyük diziler için çok daha verimlidir. Sonuncusu O (n * n) 'dir, böylece yavaşlayabilir. Bu ~ 20k elemanları ile bir dizi için kullanmak gerekiyordu ve ilk ikisi neredeyse anında döndü. Üçüncü olanı iptal etmek zorunda kaldım çünkü çok uzun sürüyordu. Teşekkürler!!
Venkat D.

5
Sadece bir gözlem ama .map (&: ilk) ile biten ilk ikisi sadece .keys ile bitebilir, çünkü bu bölüm sadece bir hash üzerindeki anahtarları çekiyor.
engineerDave

@engineerKullanılan yakut sürümüne bağlı olarak çalışın. 1.8.7 &: ilk veya hatta {| k, _ | k} ActiveSupport olmadan.
Emirikol

Burada bazı kriterler vardır gist.github.com/equivalent/3c9a4c9d07fff79062a3 kazanan açıkça performans group_by.select
equivalent8

6
Ruby'yi> 2.1 kullanıyorsanız, şunları kullanabilirsiniz: ary.group_by(&:itself). :-)
Drenmi

44

Nesnenin dizininin (soldan sayarak) nesnenin dizinine (sağdan sayarak) eşit olmadığı ilk örneği bulmanız yeterlidir.

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

Hiçbir kopya yoksa, dönüş değeri sıfır olur.

Bunun, şimdiye kadar iş parçacığında yayınlanan en hızlı çözüm olduğuna inanıyorum, çünkü ek nesnelerin oluşturulmasına bağlı değildir #indexve #rindexC'de uygulanmıştır. Big-O çalışma zamanı N ^ 2'dir ve bu nedenle Sergio's, ama duvar süresi çok daha hızlı olabilir çünkü gerçeği "yavaş" parçaları C.


5
Bu çözümü beğendim, ancak yalnızca ilk kopyayı döndürecek. Tüm kopyaları bulmak için:arr.find_all {|e| arr.rindex(e) != arr.index(e) }.uniq
Josh

1
Cevabınız ayrıca herhangi bir üçlü olup olmadığını veya "CAT" yazımını yapmak için diziden öğeler çizip çizemeyeceğini nasıl göstermez.
Cary Swoveland

3
@ bruno077 Bu doğrusal zaman nasıl?
beauby

4
Büyük cevabı @chris, ama biraz daha iyi bu işle düşünüyorum: arr.detect.with_index { |e, idx| idx != arr.rindex(e) }. Kullanmak with_indexilk indexaramanın gerekliliğini ortadan kaldırmalıdır .
ki4jnq

Bir sütundaki kopyaları karşılaştırarak bunu 2B diziye nasıl uyarlarsınız?
ahnbizcad

30

detectsadece bir kopya bulur. find_allhepsini bulacaktır:

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

3
Soru, yalnızca bir kopyasının döndürüleceği konusunda çok belirgindir. Imo, tüm kopyaları nasıl bulacağınızı göstermenin iyi olduğunu, ancak yapmadığınız soruyu cevaplayan bir yanıtı bir kenara bıraktıkça. btw, countdizideki her öğe için çağırmak acı verici bir şekilde verimsizdir . (Bir sayma karma, örneğin, çok daha verimli olduğu; örneğin, yapı h = {"A"=>2, "B"=>2, "C"=> 1 }daha sonra h.select { |k,v| v > 1 }.keys #=> ["A", "B"].
Cary Swoveland

24

Burada bir kopya bulmanın iki yolu daha var.

Bir set kullanın

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" 

Tüm kopyaların bir dizisini döndürmek için selectyerine kullanın find.

kullanım 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" 

.firstTüm kopyaların bir dizisini döndürmek için bırakın .

nilHiçbir kopya yoksa her iki yöntem de geri döner .

Bunu Ruby çekirdeğine eklenmesini önerdimArray#difference . Cevabımda daha fazla bilgi var .

Karşılaştırma

Önerilen yöntemleri karşılaştıralım. İlk olarak, test için bir diziye ihtiyacımız var:

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

ve farklı test dizileri için karşılaştırmaları çalıştırma yöntemi:

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

@ JjP'nin cevabını dahil etmedim çünkü sadece bir kopya geri döndürülecek ve cevabı bunun için değiştirildiği zaman @ Naveed'in önceki cevabı ile aynı. Ayrıca @ Naveed'in cevabından önce yayınlanırken, sadece bir kopyadan ziyade tüm kopyaları döndüren @ Marin cevabını dahil etmedim (küçük bir nokta, ancak sadece bir kopyayı döndürdüğünde aynı oldukları için her ikisini de değerlendirmenin bir anlamı yok).

Ayrıca tüm kopyaları sadece ilk bulunanı döndürmek için döndüren diğer cevapları değiştirdim, ancak bir tane seçmeden önce tüm kopyaları hesapladıkları için performans üzerinde hiçbir etkisi olmamalıdır.

Her kıyaslama ölçütü için sonuçlar en hızlıdan en yavaşa doğru listelenir:

İlk önce dizinin 100 öğe içerdiğini varsayalım:

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

Şimdi 10.000 öğeden oluşan bir dizi düşünün:

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

Not find_a_dup_using_difference(arr)ise çok daha verimli olacaktır Array#differenceBu Ruby çekirdeğe eklenmesi durumunda durum olurdu, C uygulanmıştır.

Sonuç

Cevapların çoğu makul ancak bir Set kullanmak en iyi seçimdir . Orta-sert durumlarda en hızlı, en zor ve en hesaplı önemsiz durumlarda en hızlı eklem - seçiminiz hiçbir zaman önemli olmayacaksa - yenilebilir.

Chris'in çözümünü seçebileceğiniz çok özel bir durum, binlerce küçük diziyi ayrı olarak çoğaltmak ve genellikle 10 öğeden daha az bir kopya bulmayı beklemek istiyorsanız yöntemi kullanmak isteyeceksiniz. Bu biraz daha hızlı olacak Set oluşturmanın ek ek yükünü ortadan kaldırır.


1
Mükemmel çözüm. İlk başta neler olduğu gibi bazı yöntemler kadar açık değildir, ancak biraz bellek pahasına, gerçekten doğrusal bir zamanda çalışmalıdır.
Chris Heald

Find_a_dup_using_set ile, kopyalardan biri yerine Set'i geri alıyorum. Ayrıca hiçbir yerde Ruby belgelerinde "find.with_object" bulamıyorum.
ScottJ

@Scottj, yakaladığınız için teşekkürler! Şimdiye kadar hiç kimsenin bunu yakalamaması ilginç. Onardım. Budur Enumerable # bulmak zincirlenmiş Listeleyicisi # with_object . Çözümlerinizi ve diğerlerini ekleyerek karşılaştırmaları güncelleyeceğim.
Cary Swoveland

1
Mükemmel karşılaştırma @CarySwoveland
Naveed

19

Ne yazık ki cevapların çoğu O(n^2).

İşte bir O(n)çözüm,

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

Bunun karmaşıklığı nedir?

  • O(n)İlk maçta kaçar ve kırılır
  • O(n)Belleği kullanır , ancak yalnızca minimum miktarda

Şimdi, dizinizde kopyaların sıklığına bağlı olarak, bu çalışma zamanları aslında daha da iyi olabilir. Örneğin, boyut dizisi farklı elemanlardan O(n)oluşan bir popülasyondan örneklenmişse, k << nyalnızca çalışma zamanı ve alan için karmaşıklık olur O(k), ancak orijinal posterin girişi doğrulaması ve yinelenen bir şey olmadığından emin olmak daha olasıdır. Bu durumda O(n), öğelerin girdilerin çoğunluğu için tekrarlanmamasını beklediğimizden hem çalışma zamanı hem de bellek karmaşıklığı .


15

Ruby Array nesnelerinin harika bir yöntemi var select.

select {|item| block }  new_ary
select  an_enumerator

İlk form sizi burada ilgilendiren şeydir. Bir testi geçen nesneleri seçmenizi sağlar.

Ruby Array nesnelerinin başka bir yöntemi var count.

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

Bu durumda, kopyalarla (dizide birden çok görünen nesneler) ilgilenirsiniz. Uygun test a.count(obj) > 1.

Eğer a = ["A", "B", "C", "B", "A"],

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

Sadece bir nesne istediğinizi söylersiniz. Öyleyse birini seç.


1
Bunu çok beğendim, ama sonunda bir uniq atmak zorundasınız ya da alacaksınız["A", "B", "B", "A"]
Joeyjoejoejr

1
Mükemmel cevap. Tam da aradığım şey buydu. @Joeyjoejoejr'in işaret ettiği gibi. .uniqDizi koymak için bir düzenleme gönderdim .
Surya

Bu son derece verimsiz. Sadece tüm kopyaları bulmakla kalmaz, aynı zamanda bir tanesi hariç hepsini atarsınız count, savurgan ve gereksiz olan dizinin her öğesi için çağırırsınız. JjP'nin cevabı hakkındaki yorumuma bakın.
Cary Swoveland

Kriterlere katıldığınız için teşekkür ederiz. Çalışma süresinde farklı çözümlerin nasıl karşılaştırıldığını görmek yararlıdır. Zarif cevaplar okunabilir, ancak çoğu zaman en verimli değildir.
Martin Velez

9

find_all () , bir döner arraytüm elemanları ihtiva eden enumiçin blockdeğil false.

duplicateElement almak için

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

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

Veya yinelenen uniqöğeler

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

7

Böyle bir şey işe yarayacak

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 }

Diğer bir deyişle, tüm değerleri bir karma değerine yerleştirin; burada anahtar dizi öğesidir ve değer olay sayısıdır. Sonra bir kereden fazla meydana gelen tüm elemanları seçin. Kolay.


7

Bu konu özellikle Ruby ile ilgili olduğunu biliyorum, ama buraya ActiveRecord ile Ruby on Rails bağlamında nasıl yapılacağını araştırdım ve çözümümü de paylaşacağımı düşündüm.

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

Yukarıdaki, bu örneğin veritabanı tablosunda (Rails'te "active_record_classes" olacaktır) çoğaltılan tüm e-posta adreslerinin bir dizisini döndürür.


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

Bu bir O(n)prosedür.

Alternatif olarak aşağıdaki satırlardan herhangi birini yapabilirsiniz. Ayrıca O (n) ama sadece bir yineleme

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

İşte benim büyük bir veri kümesi üzerine benim - çift parçaları bulmak için eski bir dBase tablo gibi

# 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 senin arkadaşın!

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

Bu kod, yinelenen değerlerin listesini döndürür. Karma anahtarlar, daha önce hangi değerlerin görüldüğünü kontrol etmenin etkili bir yolu olarak kullanılır. Değerin görülüp görülmediğine bağlı olarak, orijinal dizi ary2 diziye bölünür: birincisi benzersiz değerler ve ikincisi yinelemeler içerir.

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

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

=> ["hello"]

Biraz daha karmaşık bir sözdizimi pahasına da olsa - bu forma daha da kısaltabilirsiniz:

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

0
a = ["A", "B", "C", "B", "A"]
b = a.select {|e| a.count(e) > 1}.uniq
c = a - b
d = b + c

Sonuçlar

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

0

İki farklı diziyi (kendine karşı yerine) karşılaştırıyorsanız, çok hızlı bir yol Ruby's Array sınıfı& tarafından sağlanan kesişim operatörünü kullanmaktır .

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

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

1
Bu, bir dizide yinelenenleri değil, her iki dizide de bulunan öğeleri bulur.
Kimmo Lehto

Bunu işaret ettiğiniz için teşekkürler. Cevabımdaki ifadeyi değiştirdim. Onu burada bırakacağım çünkü aramadan gelen bazı insanlar için zaten yararlı olduğu kanıtlandı.
IAmNaN

0

Kaç tane kopya olduğunu ve ne olduklarını bulmam gerekiyordu, bu yüzden Naveed'in daha önce yayınladıklarından yola çıkarak bir fonksiyon yazdım:

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. Öğe dizisini girdi olarak alan çoğaltma yöntemini oluşturalım
  2. Yöntem gövdesinde, bir tanesi diğeri yinelenen 2 yeni dizi nesnesi oluşturalım
  3. Son olarak, verilen dizideki her bir nesneyi yinelemeye izin verir ve her yineleme için bu nesnenin görülen dizide var olduğunu bulmanızı sağlar.
  4. seen_array öğesinde nesne varsa, yinelenen nesne olarak kabul edilir ve bu nesneyi duplication_array içine iter
  5. nesne görülmemişse, benzersiz nesne olarak kabul edilir ve bu nesneyi seen_array içine iter

Kod Uygulamalarında gösterelim

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

Şimdi çoğaltma yöntemini ve çıktı döndürme sonucunu çağırın -

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

Yalnızca kod yanıtları genellikle bu sitede kaşlarını çatmaktadır. Cevabınıza bazı yorumlar veya açıklama eklemek için cevabınızı düzenleyebilir misiniz? Açıklamalar aşağıdaki gibi sorulara cevap vermelidir: Ne işe yarar? Bunu nasıl yapıyor? Nereye gidiyor? OP'nin problemini nasıl çözer? Bakınız: Nasıl cevap verilir . Teşekkürler!
Eduardo Baitello

-4

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

Yukarıdakilerin yıkıcı olduğuna dikkat edin


bu yinelenen değerler döndürmez
andriy-baran
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.