大家好,
请问 要 Ruby 去判断一个数组中是否有重复元素(只要知道是否有重复,不需要知道具体有哪些重复),最简单高效的写法是什么?
我现在能给出的写法只有判断 array.size > array.uniq.size
,虽然看上去很简单,但是不够高效,请问大家还有什么更好的建议嘛?
uniq
是很慢,但很难写出一个更高效简单的。
不一定简单的方法肯定是有的,比方说取所有元素放 Hash
里再判断大小。但性能上的改善微乎其微。
坐等达人出高性能算法 ^-^
OK 我想出一个在一般情况下能提高一定量级的方法:
module DuplicationChecker
def uniq?
set = Hash.new
each do |e|
return false if set[e]
set[e] = e
end
set.size < size
end
end
Array.class_eval do
include DuplicationChecker
end
当数组元素比较多且有不少重复时,可以在遇到第一个重复元素时结果判断提高速度。大致猜想如果重复元素很少(比方说 100 万大的数组中只有一两个重复,我猜会慢到跟直接 uniq
差不多)。
#19 楼 @zw963 enumerator 没有 uniq!方法
另外 #3 楼 @ashchan 试了一下你的方法,速度真的不错 我自己也用 sort 方法试了一下,不过有时快有时慢,速度不是很稳定,有谁知道原因吗?
require 'benchmark'
module DuplicationChecker
def uniq?
set = Hash.new
each do |e|
return false if set[e]
set[e] = e
end
set.size < size
end
end
Array.class_eval do
include DuplicationChecker
end
arr = 100000.times.inject([]) do |arr, i|
arr << rand(100000000000000000)
end
arr << arr[-1]
Benchmark.bm do |x|
x.report('uniq') do
puts arr.size > arr.uniq.size
end
x.report('sort') do
arr.sort do |a, b|
eql = (a <=> b)
if eql == 0
puts a
break
end
eql
end
end
x.report('uniq?') do
puts arr.uniq?
end
x.report('dup') do
puts arr.size > arr.dup.uniq!.size
end
end
68-a8-6d-43-46-cc:ruby can$ ruby benchmark.rb
user system total real
uniqtrue
0.080000 0.000000 0.080000 ( 0.080204)
sort88757701264796045
0.040000 0.000000 0.040000 ( 0.044535)
uniq?false
0.090000 0.000000 0.090000 ( 0.092339)
duptrue
0.100000 0.010000 0.110000 ( 0.103186)
这个算法时间复杂度是 O(N),时间不稳定是因为有时运气好比较靠前,有时运气差。 但是平摊下来趋于稳定。 如 knwang 说的,小数组无所谓,性能没什么区别,个人认为用有 side effect 的函数是很不好的 practice。
如果是很大数据量的话,这个应用场景还是很多的,比如文字的 dedupe,像 twitter 搜索结果里要去掉重复的内容(其实有点不一样,每个 retweet 都有少部分不同的内容)。一个简单的做法是计算文字的 min hash,然后可以放到 map reduce 中去去重。
tmp = []
array.each do |i|
tmp[i] = 'true'
end
array.size > tmp.compact .size
array.size > array.to_set.size 我不太知道 to_set 的内部实现,是否比 uniq 高,但是 to_set 应该没有 uniq 里面的 sort 方法