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

时间:2012-01-19 06:38:51

标签: ruby arrays

arr是字符串数组,例如:["hello", "world", "stack", "overflow", "hello", "again"]

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

示例:

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

21 个答案:

答案 0 :(得分:225)

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)但现在需要管理多个LOC,需要测试用例和东西!

如果您需要更快的解决方案,可以尝试C代替:)

以下是比较不同解决方案的gits:https://gist.github.com/naveed-ahmad/8f0b926ffccf5fbd206a1cc58ce9743e

答案 1 :(得分:200)

您可以通过几种方式完成此操作,第一个选项是最快的:

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

答案 2 :(得分:43)

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

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

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

我相信这是迄今为止在帖子中发布的最快的解决方案,因为它不依赖于创建其他对象,#index#rindex在C中实现。 big-O运行时间是N ^ 2,因此比Sergio慢,但由于“慢”部分在C中运行,因此可以更快地打开时间。

答案 3 :(得分:27)

detect只发现一个副本。 find_all会找到所有人:

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

答案 4 :(得分:19)

以下是另外两种查找副本的方法。

使用

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

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

<强>结论

许多答案都是合理的,但使用Set是明确的最佳选择。它在中等难度的情况下是最快的,在最困难的情况下联合最快,并且只在计算上微不足道的情况下 - 当你的选择无论如何都无所谓 - 它可以被打败。

一个非常特殊的案例,你可以选择克里斯&#39;解决方案是,如果你想使用该方法单独去重复数千个小数组,并期望找到通常少于10个项目的副本。这将更快一点,因为它避免了创建Set的额外开销。

答案 5 :(得分:14)

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

您声明只需一个对象。所以选一个。

答案 6 :(得分:14)

唉,大部分答案都是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 :(得分:7)

我知道这个主题是关于Ruby的,但是我在这里寻找如何在使用ActiveRecord的Ruby on Rails的上下文中做到这一点,并且我认为我也会分享我的解决方案。

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中将是&#34; active_record_classes&#34;)。

答案 8 :(得分:7)

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 :(得分: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是出现次数。然后选择多次出现的所有元素。容易。

答案 10 :(得分:5)

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]

答案 11 :(得分:3)

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

答案 12 :(得分: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

答案 13 :(得分:1)

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)

答案 14 :(得分:1)

如果要比较两个不同的数组(而不是一个对应的数组),一种非常快速的方法是使用Ruby's Array class提供的交叉运算符self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none) self.tableView(self.tableView, didSelectRowAt: indexPath)

&

答案 15 :(得分: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}

答案 16 :(得分: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

答案 17 :(得分:0)

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

答案 18 :(得分:0)

试试看! 如果要查找最大重复元素及其重复次数,则应尝试

    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

答案 19 :(得分:-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

答案 20 :(得分:-3)

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

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