답변:
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
하나의 사본 만 찾습니다. 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"]
.
중복을 찾는 두 가지 방법이 더 있습니다.
세트 사용
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 개 미만의 항목을 복제하려는 경우이 방법을 사용하려는 경우입니다. 세트 생성에 따른 작은 추가 오버 헤드를 피할 수 있습니다.
아아 대부분의 대답은 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
첫 번째 형태는 여기서 당신이 관심을 갖는 것입니다. 테스트를 통과 한 객체를 선택할 수 있습니다.
루비 배열 객체에는 또 다른 방법이 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"]
하나의 객체 만 원한다고 진술 합니다. 하나를 선택하십시오.
["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에 관한 것이지만 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"가 됨)에 복제 된 모든 전자 메일 주소의 배열을 반환합니다.
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]
중복 된 부분을 찾기 위해 레거시 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
은 두 개의 배열로 나뉩니다. 첫 번째는 고유 값을 포함하고 두 번째는 중복을 포함합니다.
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
서로 다른 두 배열을 비교하는 경우 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
arr == arr.uniq
arr
중복 이 있는지 확인하는 쉽고 우아한 방법 이지만 중복 된 것을 제공하지는 않습니다.