如何在数组中查找并返回重复值


170

arr 是字符串数组:

["hello", "world", "stack", "overflow", "hello", "again"]

一种简单又优雅的方法来检查是否arr有重复项,如果有重复项,则返回其中一个(无论哪个)?

例子:

["A", "B", "C", "B", "A"]    # => "A" or "B"
["A", "B", "C"]              # => nil

arr == arr.uniq这将是一种简单而优雅的方法来检查是否arr有重复项,但是,它不提供重复项。
Joel AZEMAR

Answers:


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-是的; 但是,这仅考虑big-O运行时。在实践中,除非您为一些巨大的缩放数据编写此代码(如果是,则实际上可以使用C或Python),否则提供的答案将更加优雅/易于理解,并且运行速度不会慢得多线性时间解决方案。此外,理论上,线性时间解需要线性空间,而线性空间可能不可用
David T.

26
@Kalanamith您可以使用此方法获得重复的值a.select {|e| a.count(e) > 1}.uniq
Naveed

26
“检测”方法的问题在于,它在找到第一个重复项时会停止,并且不会为您提供所有重复项。
Jaime Bellmyer 2014年

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需要&:first甚至{| k,_ | 没有ActiveSupport。
Emirikol 2014年

这是一些基准gist.github.com/equivalent/3c9a4c9d07fff79062a3 在性能上获胜者很明显 group_by.select
等同

6
如果您使用的是Ruby> 2.1,则可以使用:ary.group_by(&:itself)。:-)
Drenmi'1

44

只需找到第一个实例,其中对象的索引(从左侧开始计数)不等于对象的索引(从右侧开始计数)。

arr.detect {|e| arr.rindex(e) != arr.index(e) }

如果没有重复项,则返回值为零。

我认为,这也是到目前为止线程中发布的最快的解决方案,因为它不依赖于其他对象的创建,#index并且#rindex是在C语言中实现的。big-O运行时为N ^ 2,因此比Sergio的,但是由于“慢速”部分在C中运行,因此挂墙时间可能要快得多。


5
我喜欢这种解决方案,但它只会返回第一个重复项。查找所有重复项:arr.find_all {|e| arr.rindex(e) != arr.index(e) }.uniq
Josh 2015年

1
您的答案也没有显示如何查找是否存在任何重复项,或者是否可以从数组中提取元素以拼写“ CAT”。
卡里·斯沃夫兰

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展示了如何查找所有重复项是很好的方法,但仅作为回答问题的答案的一个补充,您尚未完成。顺便说一句,调用count数组中的每个元素都非常麻烦。(A计数散列,例如,是更有效的;例如,构建体h = {"A"=>2, "B"=>2, "C"=> 1 }然后h.select { |k,v| v > 1 }.keys #=> ["A", "B"]
卡里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其添加到Ruby核心中。更多信息可在我的答案在这里

基准测试

让我们比较建议的方法。首先,我们需要一个数组进行测试:

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的早期答案相同。我也没有包括@Marin的答案,该答案发布在@Naveed的答案之前,返回的是所有重复项,而不仅仅是一个(略有一点,但没有必要对两者进行评估,因为当返回一个重复项时它们是相同的)。

我还修改了其他答案,这些答案返回所有重复项,仅返回找到的第一个重复项,但这对性能基本上没有影响,因为它们在选择一个重复项之前计算了所有重复项。

每个基准测试的结果从最快到最慢列出:

首先假设数组包含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实现,效率会大大提高,如果将其添加到Ruby核心中,情况将会更有效。

结论

许多答案都是合理的,但使用Set无疑是最佳选择。在中等难度的情况下,它是最快的;在最困难的情况下,它是最快的,并且仅在计算上不重要的情况下-当您的选择仍然无关紧要时-可以被击败。

您可能会选择克里斯的解决方案的一种非常特殊的情况是,如果您想使用该方法分别对数千个小型阵列进行重复数据消除,并希望找到通常少于10个项目的重复数据,则速度会更快一些。因为它避免了创建集合的少量额外开销。


1
优秀的解决方案。起初它并不像某些方法那样明显,但是它应该在真正的线性时间内运行,但会浪费一些内存。
克里斯·希尔德

使用find_a_dup_using_set,我可以取回Set,而不是重复项之一。另外,我在任何地方的Ruby文档中都找不到“ find.with_object”。
ScottJ

@Scottj,谢谢!有趣的是,在此之前没有人抓住这一点。我修好了它。那是Enumerable#find链接到Enumerator#with_object。我将更新基准测试,并添加您的解决方案和其他解决方案。
卡里·斯沃沃兰德

1
出色的比较@CarySwoveland
Naveed

19

las,大多数答案是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

第一种形式是您在这里感兴趣的。它允许您选择通过测试的对象。

Ruby Array对象还有另一种方法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
我非常喜欢这个,但是您必须在结尾处["A", "B", "B", "A"]
加上

1
好答案。这正是我想要的。正如@Joeyjoejoejr指出的那样。我已提交修改以放入.uniq数组。
Surya

这是非常低效的。您不仅会发现所有重复项,然后丢弃所有重复项,而且还会丢弃所有重复项,而且会count为数组的每个元素调用,这是浪费和不必要的。请参阅我对JjP答案的评论。
卡里·斯沃夫兰

感谢您运行基准测试。了解不同解决方案在运行时间上的比较很有用。简洁的答案是可读的,但通常不是最有效的。
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 }

也就是说,将所有值放入一个散列中,其中key是数组的元素,而value是出现的次数。然后选择所有出现多次的元素。简单。


7

我知道这个线程是专门针对Ruby的,但是我登陆这里寻找如何在Ruby on Rails中使用ActiveRecord来实现这一点,并认为我也将分享我的解决方案。

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划分为2个数组:第一个包含唯一值,第二个包含重复值。

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

如果要比较两个不同的数组(而不是将其与自身比较),一种非常快速的方法是使用Ruby的Array类&提供的相交运算符。

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

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

1
这样可以找到两个数组中都存在的项,而不是一个数组中的重复项。
Kimmo Lehto

感谢您指出了这一点。我已经更改了答案中的措辞。我将其保留在此处,因为它已经被证明对某些来自搜索的人有用。
IAmNaN '18年

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

注意上面是破坏性的


这不会返回重复的值
andriy-baran
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.