新手问题 Ruby 如何判断一个数组中是否有重复元素?

ibachue · 2012年11月22日 · 最后由 shunzi_gong 回复于 2015年06月02日 · 18521 次阅读

大家好, 请问 要 Ruby 去判断一个数组中是否有重复元素(只要知道是否有重复,不需要知道具体有哪些重复),最简单高效的写法是什么? 我现在能给出的写法只有判断 array.size > array.uniq.size ,虽然看上去很简单,但是不够高效,请问大家还有什么更好的建议嘛?

你测试过 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 差不多)。

弄个 hash 一个个往里塞…… 遇到塞过的就是有重复的了…… 不知道快慢……

5 楼 已删除

如果原数组可以改,判断 a.uniq! 返回值是否为 nil 就可以了

#4 楼 @dotnil 我看过 javascript 就是这么做。javascript 那个是弄个 object。据说效率是最高的,问题也有遇到非字符串的很蛋疼了。例如 1,2,3,4,5 这样

#7 楼 @metal 你是说纯数字的 array 么?也一样执行的哇

#6 楼 @luikore 哇,原来这里的返回值还有差别

#6 楼 @luikore Great! 就这个了!

#11 楼 @iBachue 这个有 side effect,不推荐这么写。

#13 楼 @gaicitadie 于是越来越慢了……

#12 楼 @pongyo 不要紧啊 其实调用这个方法的数组是先经过 map 方法处理过的,本来就可以被修改的

#16 楼 @skandhas 我说错了吗?

#17 楼 @dotnil 这里的 ~呵呵~ 是表示赞同的微笑:)

可以试试

array.to_enum.uniq!, to_enum 方法为一个数组创建了一个不可变的代理对象,不知道性能比 dup 是不是好点。

#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)

#20 楼 @cantin

貌似你是对的。 : )

@iBachue 能说下具体的用例么?如果是数据量不大,不值得纠结;如果数据量很大,感觉总有更好的解决渠道,而不是把大数组加进内存来判断

这个算法时间复杂度是 O(N),时间不稳定是因为有时运气好比较靠前,有时运气差。 但是平摊下来趋于稳定。 如 knwang 说的,小数组无所谓,性能没什么区别,个人认为用有 side effect 的函数是很不好的 practice。

如果是很大数据量的话,这个应用场景还是很多的,比如文字的 dedupe,像 twitter 搜索结果里要去掉重复的内容(其实有点不一样,每个 retweet 都有少部分不同的内容)。一个简单的做法是计算文字的 min hash,然后可以放到 map reduce 中去去重。

#22 楼 @knwang 没有很大的量其实 传进来的都是些参数而已 在实际使用过程中我确定决定不会超过 5 个 之所以提出来只是为了找出更美观的代码(自己定义方法绝对是没有必要的,太麻烦)效率还是其次的 uniq! 应该完全满足要求了。 之所以没更早的说出来不过是抛砖引玉而已,我自己也可以多多学习下:)

如果数组里面都是数字的话 这样会好点吗?
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 方法

需要 登录 后方可回复, 如果你还没有账号请 注册新账号