【问题标题】:How to find and return a duplicate value in array如何在数组中查找并返回重复值
【发布时间】:2012-02-13 20:56:43
【问题描述】:

arr 是字符串数组:

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

什么是检查arr 是否有重复项的简单而优雅的方法,如果有,返回其中一个(不管是哪个)?

例子:

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

【问题讨论】:

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

标签: ruby arrays


【解决方案1】:
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

【讨论】:

  • 对于可以在线性时间内解决的问题,除了二次方。
  • 为线性问题提供 O(n^2) 的解决方案是不可行的。
  • @jasonmp85 - 真;但是,这仅考虑了 big-O 运行时。在实践中,除非您为一些巨大的缩放数据编写此代码(如果是这样,您实际上可以只使用 C 或 Python),提供的答案更加优雅/可读,并且运行速度不会慢得多为线性时间解。此外,理论上线性时间解需要线性空间,可能不可用
  • @Kalanamith 你可以使用这个a.select {|e| a.count(e) > 1}.uniq得到重复值
  • “检测”方法的问题在于它在找到第一个重复项时停止,并且不会给您所有的重复项。
【解决方案2】:

您可以通过几种方式做到这一点,第一个选项是最快的:

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

【讨论】:

  • 前两个对于大型数组来说效率更高。最后一个是 O(n*n) 所以它会变慢。我需要将它用于具有约 20k 个元素的数组,前两个元素几乎立即返回。我不得不取消第三个,因为它花了很长时间。谢谢!!
  • 只是一个观察,但以 .map(&:first) 结尾的前两个可能只是以 .keys 结尾,因为那部分只是拉散列上的键。
  • @engineerDave 取决于正在使用的 ruby​​ 版本。 1.8.7 需要 &:first 甚至 {|k,_| k } 没有 ActiveSupport。
  • 这里有一些基准测试gist.github.com/equivalent/3c9a4c9d07fff79062a3 在性能方面获胜者显然是group_by.select
  • 如果你使用 Ruby > 2.1,你可以使用:ary.group_by(&:itself)。 :-)
【解决方案3】:

只需找到对象的索引(从左数)不等于对象的索引(从右数)的第一个实例。

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

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

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

【讨论】:

  • 我喜欢这个解决方案,但它只会返回第一个副本。要查找所有重复项:arr.find_all {|e| arr.rindex(e) != arr.index(e) }.uniq
  • 您的答案也没有显示如何查找是否有任何三元组,或者是否可以从数组中绘制元素来拼写“CAT”。
  • @bruno077 这个线性时间怎么样?
  • @chris 很好的答案,但我认为你可以做得更好:arr.detect.with_index { |e, idx| idx != arr.rindex(e) }。使用with_index 应该消除第一次index 搜索的必要性。
  • 如何将其调整为二维数组,比较列中的重复项?
【解决方案4】:

detect 只找到一个重复项。 find_all 会全部找到:

a = ["A", "B", "C", "B", "A"]
a.find_all { |e| a.count(e) > 1 }

【讨论】:

  • 问题非常具体,只返回一个副本。 Imo,展示如何查找所有重复项很好,但只是作为回答所问问题的答案的旁白,你还没有做过。顺便说一句,为数组中的每个元素调用count 效率极低。 (例如,计数哈希要高效得多;例如,构造 h = {"A"=>2, "B"=>2, "C"=> 1 } 然后构造 h.select { |k,v| v > 1 }.keys #=> ["A", "B"]
【解决方案5】:

这里还有两种查找重复项的方法。

使用集合

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

proposed that Array#difference 被添加到 Ruby 核心。更多信息在我的回答here

基准测试

让我们比较推荐的方法。首先,我们需要一个用于测试的数组:

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

请注意,如果Array#difference 是在 C 中实现的,find_a_dup_using_difference(arr) 的效率会更高,如果将它添加到 Ruby 核心中就会出现这种情况。

结论

许多答案都是合理的,但使用 Set 显然是最佳选择。它在中等难度的情况下最快,在最困难的情况下最快,并且只有在计算量很小的情况下——当你的选择无论如何都不重要时——才能被击败。

您可能会选择 Chris 的解决方案的一种非常特殊的情况是,如果您想使用该方法分别对数千个小数组进行去重,并期望找到通常少于 10 个项目的重复项。这将是更快一点,因为它避免了创建 Set 的额外开销。

【讨论】:

  • 优秀的解决方案。起初发生的事情不像某些方法那么明显,但它应该以真正的线性时间运行,但会消耗一点内存。
  • 使用 find_a_dup_using_set,我得到了 Set,而不是其中一个副本。我也无法在任何地方的 Ruby 文档中找到“find.with_object”。
  • @Scottj,谢谢你的收获!有趣的是,以前没有人发现这一点。我修好了它。那是 Enumerable#find 链接到 Enumerator#with_object。我将更新基准,添加您的解决方案和其他解决方案。
  • 优秀的比较@CarySwoveland
【解决方案6】:

唉,大多数答案都是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) 因为我们希望元素对于大多数输入没有重复。

【讨论】:

    【解决方案7】:

    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"]
    

    您声明您只想要 一个 对象。所以选择一个。

    【讨论】:

    • 我非常喜欢这个,但你必须在最后扔一个 uniq 否则你会得到["A", "B", "B", "A"]
    • 很好的答案。这正是我一直在寻找的。正如@Joeyjoejoejr 指出的那样。我已提交修改以将 .uniq 放在数组中。
    • 这是非常低效的。您不仅会找到所有重复项,然后除了一个之外都丢弃所有重复项,您还为数组的每个元素调用 count,这是浪费且不必要的。请参阅我对 JjP 答案的评论。
    • 感谢您运行基准测试。查看不同解决方案在运行时间上的比较是很有用的。优雅的答案是可读的,但通常不是最有效的。
    【解决方案8】:

    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"] 
    

    【讨论】:

      【解决方案9】:

      这样的事情会起作用

      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 }
      

      也就是说,将所有值放入一个哈希中,其中键是数组的元素,值是出现次数。然后选择所有多次出现的元素。很简单。

      【讨论】:

      • 这工作完美而快速,谢谢
      【解决方案10】:

      我知道这个帖子是专门关于 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”)。

      【讨论】:

        【解决方案11】:
        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]
        

        【讨论】:

          【解决方案12】:

          此代码将返回重复值列表。哈希键被用作检查哪些值已经被看到的有效方法。根据是否看到值,将原始数组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
          

          【讨论】:

            【解决方案13】:

            这是我对大量数据的看法 - 例如用于查找重复部分的遗留 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
            

            【讨论】:

              【解决方案14】:
              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)
              

              【讨论】:

                【解决方案15】:

                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}
                

                【讨论】:

                  【解决方案16】:
                  a = ["A", "B", "C", "B", "A"]
                  b = a.select {|e| a.count(e) > 1}.uniq
                  c = a - b
                  d = b + c
                  

                  结果

                   d
                  => ["A", "B", "C"]
                  

                  【讨论】:

                    【解决方案17】:

                    如果您要比较两个不同的数组(而不是一个数组自己),一种非常快速的方法是使用Ruby's Array class 提供的相交运算符&amp;

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

                    【讨论】:

                    • 查找两个数组中都存在的项,而不是一个数组中的重复项。
                    • 感谢您指出这一点。我已经更改了答案中的措辞。我将把它留在这里,因为它已经证明对某些来自搜索的人有帮助。
                    【解决方案18】:

                    我需要找出有多少重复项以及它们是什么,所以我根据 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
                    

                    【讨论】:

                      【解决方案19】:

                      试试这个! 如果您想找到最大重复元素及其重复次数,那么应该尝试

                          def get_maximum_duplicated_element_with_count(input_array)
                              a = input_array
                              max_duplicated_val = max_duplicated_val_count = 0
                              a.each do |n| 
                                  max_duplicated_val, max_duplicated_val_count = n, a.count(n) if a.count(n) >  max_duplicated_val_count      
                              end
                              puts "Maximun Duplicated element Is => #{max_duplicated_val}"
                              puts "#{max_duplicated_val} is Duplicated #{max_duplicated_val_count} times"
                          end
                          get_maximum_duplicated_element_with_count([1, 4, 4, 5, 6, 6, 2, 6])
                      

                      输出将是

                      Maximun Duplicated element Is => 6
                      6 is Duplicated 3 times
                      

                      【讨论】:

                        【解决方案20】:
                        1. 让我们创建以元素数组为输入的复制方法
                        2. 在方法体中,我们创建 2 个新的数组对象,一个是可见的,另一个是重复的
                        3. 最后让我们遍历给定数组中的每个对象,并为每次迭代找到该对象存在于所见数组中。
                        4. 如果seen_array中存在对象,则将其视为重复对象并将该对象推入duplication_array
                        5. 如果seen中不存在对象,则将其视为唯一对象并将该对象推送到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
                        

                        【讨论】:

                        • 仅代码的答案在本网站上通常不受欢迎。您能否编辑您的答案以包含一些 cmets 或对您的代码的解释?解释应该回答这样的问题:它有什么作用?它是如何做到的?它去哪儿了?它如何解决OP的问题?请参阅:How to anwser。谢谢!
                        【解决方案21】:

                        [1,2,3].uniq!.nil? => true [1,2,3,3].uniq!.nil? => false

                        请注意以上内容具有破坏性

                        【讨论】:

                        • 这不会返回重复值
                        猜你喜欢
                        • 2014-11-10
                        • 2022-12-12
                        • 2018-04-02
                        • 1970-01-01
                        • 1970-01-01
                        • 2012-05-04
                        • 1970-01-01
                        • 2023-03-06
                        • 2011-03-27
                        相关资源
                        最近更新 更多