Dưới đây là hai cách khác để tìm một bản sao.
Sử dụng một 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"
Sử dụng select
thay thế find
để trả về một mảng của tất cả các bản sao.
Sử dụng 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"
Thả .first
để trả về một mảng của tất cả các bản sao.
Cả hai phương thức trả về nil
nếu không có trùng lặp.
Tôi đề xuất rằngArray#difference
sẽ được thêm vào lõi Ruby. Thêm thông tin trong câu trả lời của tôi ở đây .
Điểm chuẩn
Hãy so sánh các phương pháp được đề xuất. Đầu tiên, chúng ta cần một mảng để thử nghiệm:
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
và một phương pháp để chạy điểm chuẩn cho các mảng thử nghiệm khác nhau:
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
Tôi không bao gồm câu trả lời của @ JjP vì chỉ có một bản sao được trả lại và khi câu trả lời của anh ấy / cô ấy được sửa đổi để làm điều đó giống như câu trả lời trước đó của @ Naveed. Tôi cũng không bao gồm câu trả lời của @ Marin, trong khi được đăng trước câu trả lời của @ Naveed, đã trả lại tất cả các bản sao thay vì chỉ một (một điểm nhỏ nhưng không có điểm nào đánh giá cả hai, vì chúng giống hệt nhau khi trả về chỉ một bản sao).
Tôi cũng đã sửa đổi các câu trả lời khác trả về tất cả các bản sao để trả về chỉ một câu trả lời đầu tiên được tìm thấy, nhưng về cơ bản nó sẽ không ảnh hưởng đến hiệu suất, vì chúng đã tính toán tất cả các bản sao trước khi chọn một bản sao.
Kết quả cho mỗi điểm chuẩn được liệt kê từ nhanh nhất đến chậm nhất:
Đầu tiên giả sử mảng chứa 100 phần tử:
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
Bây giờ hãy xem xét một mảng với 10.000 phần tử:
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
Lưu ý rằng find_a_dup_using_difference(arr)
sẽ hiệu quả hơn nhiều nếuArray#difference
được triển khai trong C, đó là trường hợp nếu nó được thêm vào lõi Ruby.
Phần kết luận
Nhiều câu trả lời là hợp lý nhưng sử dụng Set là sự lựa chọn tốt nhất rõ ràng . Nó là nhanh nhất trong các trường hợp trung bình, khớp nhanh nhất trong các trường hợp khó nhất và chỉ trong các trường hợp tầm thường tính toán - khi sự lựa chọn của bạn không thành vấn đề - dù sao nó cũng có thể bị đánh bại.
Một trường hợp rất đặc biệt mà bạn có thể chọn giải pháp của Chris sẽ là nếu bạn muốn sử dụng phương pháp này để tách riêng hàng ngàn mảng nhỏ và mong muốn tìm thấy một bản sao thường có ít hơn 10 mục. Điều này sẽ nhanh hơn một chút vì nó tránh được chi phí bổ sung nhỏ khi tạo Set.
arr == arr.uniq
sẽ là một cách dễ dàng và thanh lịch để kiểm tra xemarr
có trùng lặp hay không, tuy nhiên, nó không cung cấp cái nào bị trùng lặp.