배열에서 중복 값을 찾아 반환하는 방법


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

21
@ jasonmp85-진실; 그러나 그것은 단지 큰 런타임을 고려하고 있습니다. 실제로, 거대한 스케일링 데이터에 대해이 코드를 작성하지 않으면 (그렇다면 실제로 C 또는 Python을 사용할 수 있음) 제공된 대답은 훨씬 우아하고 읽기 쉽고 더 느리게 실행되지 않습니다. 선형 시간 솔루션. 또한, 이론적으로 선형 시간 솔루션은 선형 공간을 필요로하며,이 공간은 이용 가능하지 않을 수 있습니다
David T.

26
@Kalanamith 당신은 이것을 사용하여 중복 값을 얻을 수 있습니다a.select {|e| a.count(e) > 1}.uniq
Naveed

26
"detect"메소드의 문제점은 첫 번째 복제본을 발견하면 중지되고 모든 딥을 제공하지는 않는다는 것입니다.
Jaime Bellmyer

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
처음 두 개는 큰 배열에 훨씬 효율적입니다. 마지막은 O (n * n)이므로 느려질 수 있습니다. ~ 20k 요소가있는 배열에 이것을 사용해야했고 처음 두 가지는 거의 즉시 돌아 왔습니다. 너무 오래 걸리기 때문에 세 번째를 취소해야했습니다. 감사!!
Venkat D.

5
.map (& : first)로 끝나는 처음 두 개는 .keys 로 끝날 수 있습니다. 해당 부분은 해시에서 키를 당기기 때문입니다.
engineerDave

사용되는 루비 버전에 의존하는 @engineerDave. 1.8.7에는 & : 첫번째 또는 심지어 {| k, _ | k} ActiveSupport가 없습니다.
Emirikol

여기에 몇 가지 벤치 마크는 gist.github.com/equivalent/3c9a4c9d07fff79062a3 승자는 분명히 성능 group_by.select
equivalent8

6
Ruby> 2.1을 사용하는 경우 다음을 사용할 수 있습니다 ary.group_by(&:itself).. :-)
Drenmi

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
당신의 대답은 또한 삼중이 있는지 찾는 방법이나 "CAT"철자를 위해 배열에서 요소를 그릴 수 있는지 여부를 보여주지 않습니다.
Cary Swoveland

3
@ bruno077이 선형 시간은 어떻습니까?
beauby

4
@ chris 큰 대답이지만, 이것으로 조금 더 잘할 수 있다고 생각합니다 arr.detect.with_index { |e, idx| idx != arr.rindex(e) }. 를 사용 with_index하면 첫 번째 index검색 의 필요성이 없어 집니다.
ki4jnq

이것을 열의 중복을 비교하여 2D 배열에 어떻게 적용 하시겠습니까?
ahnbizcad

30

detect하나의 사본 만 찾습니다. find_all그들 모두를 찾을 것입니다 :

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

3
이 질문은 하나의 사본 만 반환되도록 매우 구체적입니다. 모든 복제본을 찾는 방법을 보여주는 Imo는 훌륭하지만 질문하지 않은 질문에 대한 답변을 제외하고는 아닙니다. btw, count배열의 모든 요소 를 호출하는 것은 고통스럽게 비효율적 입니다. (예를 들어, 계산 해시가 훨씬 더 효율적입니다. 예를 들어 h = {"A"=>2, "B"=>2, "C"=> 1 }다음 과 같이 구성하십시오 h.select { |k,v| v > 1 }.keys #=> ["A", "B"].
Cary Swoveland

24

중복을 찾는 두 가지 방법이 더 있습니다.

세트 사용

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중복이 없으면 두 가지 방법이 모두 반환 됩니다.

루비 코어에 추가 할 것을 제안했습니다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

하나의 사본 만 반환되므로 @JjP의 답변을 포함하지 않았으며 답변을 수정하면 @Naveed의 이전 답변과 동일합니다. @Naveed의 답변 이전에 게시 된 동안 단 하나가 아닌 모든 복제본을 반환 한 @Marin의 답변도 포함하지 않았습니다 (사소한 점이지만 두 번만 반환하면 동일하므로 두 가지를 평가하는 포인트는 없습니다).

또한 모든 중복을 반환하는 첫 번째 답변 만 반환하도록 다른 답변을 수정했지만 하나를 선택하기 전에 모든 중복을 계산했기 때문에 본질적으로 성능에 영향을 미치지 않아야합니다.

각 벤치 마크에 대한 결과는 가장 빠름에서 가장 느리게 나열됩니다.

먼저 배열에 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그것이 루비 코어에 추가 된 경우 케이스가 될 것이다, C로 구현되었다.

결론

많은 답변이 합리적이지만 Set을 사용하는 것이 가장 좋습니다 . 중간 정도의 하드 케이스에서는 가장 빠르며, 가장 어려운 경우에는 가장 빠르며 계산 상 사소한 경우에만 빠릅니다.

Chris의 솔루션을 선택할 수있는 매우 특별한 경우는 수천 개의 작은 배열을 별도로 중복 제거하고 일반적으로 10 개 미만의 항목을 복제하려는 경우이 방법을 사용하려는 경우입니다. 세트 생성에 따른 작은 추가 오버 헤드를 피할 수 있습니다.


1
탁월한 솔루션. 처음에 무슨 일이 일어나고 있는지는 분명하지 않지만 약간의 메모리를 희생시키면서 실제로 선형으로 실행해야합니다.
크리스 힐드

find_a_dup_using_set을 사용하면 중복 중 하나 대신 Set을 다시 얻습니다. 또한 루비 문서에서 "find.with_object"를 찾을 수 없습니다.
ScottJ

@Scottj, 잡아 주셔서 감사합니다! 지금까지 아무도 그것을 잡지 못했다는 것이 흥미 롭습니다. 나는 그것을 고쳤다. 그건 Enumerable에서 #을 찾을 체인 열거 번호의 with_object . 솔루션 및 기타를 추가하여 벤치 마크를 업데이트하겠습니다.
Cary Swoveland

1
우수 비교 @CarySwoveland
Naveed

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

첫 번째 형태는 여기서 당신이 관심을 갖는 것입니다. 테스트를 통과 한 객체를 선택할 수 있습니다.

루비 배열 객체에는 또 다른 방법이 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
나는 이것을 많이 좋아하지만 결국에는 uniq를 던져야합니다. 그렇지 않으면 얻을 것입니다["A", "B", "B", "A"]
Joeyjoejoejr

1
좋은 대답입니다. 이것이 바로 내가 찾던 것입니다. @Joeyjoejoejr가 지적했듯이. .uniq배열을 편집하기 위해 제출했습니다 .
Surya

이것은 매우 비효율적입니다. 모든 중복 항목을 찾은 다음 하나만 모두 버리고 count배열의 각 요소 를 호출하면 낭비가되고 불필요합니다. JjP의 답변에 대한 내 의견을 참조하십시오.
Cary Swoveland

벤치 마크를 실행 해 주셔서 감사합니다. 실행 시간에 서로 다른 솔루션을 비교하는 것이 유용합니다. 우아한 답변은 읽을 수 있지만 종종 가장 효율적인 것은 아닙니다.
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에 관한 것이지만 ActiveRecord와 Ruby on Rails의 맥락 에서이 작업을 수행하는 방법을 찾고 여기에 내 솔루션을 공유 할 것이라고 생각했습니다.

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)이지만 단 하나의 반복

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은 두 개의 배열로 나뉩니다. 첫 번째는 고유 값을 포함하고 두 번째는 중복을 포함합니다.

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
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"]

0

서로 다른 두 배열을 비교하는 경우 Ruby의 Array 클래스에서& 제공 하는 교차 연산자를 사용하는 것이 가장 빠른 방법입니다 .

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

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

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 개의 새로운 배열 객체를 만들어 봅시다. 하나는 보이고 다른 하나는 복제입니다.
  3. 마지막으로 주어진 배열의 각 객체를 반복하고 모든 반복에 대해 표시된 배열에 존재하는 객체를 찾을 수 있습니다.
  4. seen_array에 오브젝트가 존재하면 오브젝트가 중복 오브젝트로 간주되어 해당 오브젝트를 duplication_array로 푸시합니다.
  5. 본에 존재하지 않는 객체는 고유 한 객체로 간주되어 해당 객체를 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의 문제를 어떻게 해결합니까? 다음을 참조하십시오 : anwser . 감사!
Eduardo Baitello

-4

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

위의 내용은 파괴적입니다.


이 중복 된 값을 반환하지 않습니다
안드리-바란
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.