如何在Ruby数组中计算相同的字符串元素


91

我有以下 Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

如何为每个相同的元素产生计数?

Where:
"Jason" = 2, "Judah" = 3, "Allison" = 1, "Teresa" = 1, "Michelle" = 1?

产生散列其中:

其中:hash = {“ Jason” => 2,“ Judah” => 3,“ Allison” => 1,“ Teresa” => 1,“ Michelle” => 1}


2
从Ruby 2.7开始,您可以使用Enumerable#tally。更多信息在这里
SRack,

Answers:


82
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
counts = Hash.new(0)
names.each { |name| counts[name] += 1 }
# => {"Jason" => 2, "Teresa" => 1, ....

127
names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}

给你

{"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1} 

3
+1与所选答案类似,但我更喜欢使用inject且不使用“外部”变量。

18
如果您使用each_with_object而不是,inject则不必;total在块中返回()。
mfilej 2013年

12
对于后代来说,这就是@mfilej的意思:array.each_with_object(Hash.new(0)){|string, hash| hash[string] += 1}
Gon Zifroni 2015年

2
在Ruby 2.7中,您可以简单地执行:names.tally
Hallgeir Wilhelmsen,

99

Ruby v2.7 +(最新)

从ruby v2.7.0(2019年12月发布)开始,核心语言现在包括Enumerable#tally-一种专门针对此问题设计的新方法

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

names.tally
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Ruby v2.4 +(当前受支持,但版本更旧)

首次提出此问题时(2011年2月),以下代码无法在标准红宝石中使用,因为它使用了:

这些对Ruby的现代添加实现了以下实现:

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

names.group_by(&:itself).transform_values(&:count)
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Ruby v2.2 +(已弃用)

如果使用的是较旧的ruby版本,而无法访问上述Hash#transform_values方法,则可以使用Array#to_h,它已添加到Ruby v2.1.0(2013年12月发行)中:

names.group_by(&:itself).map { |k,v| [k, v.length] }.to_h
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

对于甚至更老的红宝石版本(<= 2.1),也有几种解决方法,但是(我认为)没有明确的“最佳”方法。请参阅此帖子的其他答案。


我正要发布:P。使用count代替size/之间有没有明显的区别length
iceツ

1
@SagarPandya不,没有区别。与Array#size和不同Array#lengthArray#count 可以采用可选参数或块;但如果两者都不使用,则其实现是相同的。更具体地说,这三种方法都LONG2NUM(RARRAY_LEN(ary))在后台调用:计数/长度
Tom Lord

1
这是惯用Ruby的一个很好的例子。好答案。
slhck '19

1
额外的信用!按计数排序.group_by(&:itself).transform_values(&:count).sort_by{|k, v| v}.reverse
艾布拉姆(Abram)

2
@Abram可以sort_by{ |k, v| -v}reverse不需要!;-)
Sony Santos

26

现在使用Ruby 2.2.0,您可以利用该itself方法

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
counts = {}
names.group_by(&:itself).each { |k,v| counts[k] = v.length }
# counts > {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

3
同意,但我稍微偏爱names.group_by(&:itself).map {| k,v | [k,v.count]}。to_h,这样您就不必声明哈希对象了
Andy Day

8
@andrewkday进一步迈出了一步,ruby v2.4添加了方法:Hash#transform_values它使我们可以进一步简化您的代码:names.group_by(&:itself).transform_values(&:count)
Tom Lord

另外,这是一个非常微妙的观点(可能不再与未来的读者有关!),但请注意,您的代码还使用了Array#to_h-已添加到Ruby v2.1.0(2013年12月发布)中,即原始问题后将近三年被问到!)
Tom Lord

17

实际上有一个数据结构可以做到这一点: MultiSet

不幸的是,这儿没有 MultiSet Ruby核心库或标准库中实现,但是网上有一些实现。

这是一个很好的例子,说明了如何选择数据结构可以简化算法。实际上,在此特定示例中,算法甚至完全消失了。从字面上看只是:

Multiset.new(*names)

就是这样。示例,使用https://GitHub.Com/Josh/Multimap/

require 'multiset'

names = %w[Jason Jason Teresa Judah Michelle Judah Judah Allison]

histogram = Multiset.new(*names)
# => #<Multiset: {"Jason", "Jason", "Teresa", "Judah", "Judah", "Judah", "Michelle", "Allison"}>

histogram.multiplicity('Judah')
# => 3

例如,使用http://maraigue.hhiro.net/multiset/index-en.php

require 'multiset'

names = %w[Jason Jason Teresa Judah Michelle Judah Judah Allison]

histogram = Multiset[*names]
# => #<Multiset:#2 'Jason', #1 'Teresa', #3 'Judah', #1 'Michelle', #1 'Allison'>

MultiSet概念是否起源于数学或另一种编程语言?
Andrew Grimm

2
@Andrew格林:无论他的字“多集”(德布鲁因,1970)和概念(戴德金1888年)起源于数学。Multiset通过的方式,是严格的数学规则,支持典型的集合操作(并,交,补体,...)管辖大多与公理,法律的“正常”数学集理论定理一致,但也有一些重要的法律做当您尝试将它们概括为多集时成立。但这超出了我对此事的理解。我将它们用作编程数据结构,而不是数学概念。
约尔格W¯¯米塔格

在这一点稍作扩展:“ ...的方式与公理基本一致...”:通常,“正态”集通常由一组称为“ Zermelo-Frankel集理论”的公理(假设)正式定义。 ”。但是,这些公理之一:可扩展性公理指出,集合是由其成员精确定义的,例如{A, A, B} = {A, B}。这显然违反了多集的定义!
汤姆·罗德

...但是,无需过多讨论(因为这是一个软件论坛,而不是高级数学!), 可以通过Crisp集,Peano公理和其他特定于MultiSet的公理通过数学形式正式定义多组。
汤姆·罗德

13

Enumberable#each_with_object 使您免于返回最终哈希值。

names.each_with_object(Hash.new(0)) { |name, hash| hash[name] += 1 }

返回值:

=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

同意,each_with_object变种更具有可读性对我来说比inject
列夫Lukomsky

9

Ruby 2.7以上

Ruby 2.7正是Enumerable#tally为此目的而引入的。有一个很好的总结在这里

在这种情况下:

array.tally
# => { "Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1 }

有关正在发布的功能的文档在这里

希望这对某人有帮助!


好消息!
tadman

6

这可行。

arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
result = {}
arr.uniq.each{|element| result[element] = arr.count(element)}

2
+1对于另一种方法-尽管理论上的复杂性较差- O(n^2)(这对于的某些值将很重要n),并且需要做更多的工作(例如,必须将“ Judah”计算为3倍)!我也建议each不要map(地图结果被丢弃)

感谢那!我已经将地图更改为每个地图。此外,我在遍历数组之前对其进行了统一。也许现在解决了复杂性问题?
Shreyas

6

以下是稍微实用的编程样式:

array_with_lower_case_a = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
hash_grouped_by_name = array_with_lower_case_a.group_by {|name| name}
hash_grouped_by_name.map{|name, names| [name, names.length]}
=> [["Jason", 2], ["Teresa", 1], ["Judah", 3], ["Michelle", 1], ["Allison", 1]]

的优点之一group_by是您可以使用它来分组等效但不完全相同的项目:

another_array_with_lower_case_a = ["Jason", "jason", "Teresa", "Judah", "Michelle", "Judah Ben-Hur", "JUDAH", "Allison"]
hash_grouped_by_first_name = another_array_with_lower_case_a.group_by {|name| name.split(" ").first.capitalize}
hash_grouped_by_first_name.map{|first_name, names| [first_name, names.length]}
=> [["Jason", 2], ["Teresa", 1], ["Judah", 3], ["Michelle", 1], ["Allison", 1]]

我听过函数式编程吗?+1 :-)这绝对是最好的方法,尽管可以说这并不节省内存。还要注意,构面具有Enumerable#frequency。
tokland 2011年


3
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
Hash[names.group_by{|i| i }.map{|k,v| [k,v.size]}]
# => {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

2

这里有很多很棒的实现。

但是作为一个初学者,我认为这是最容易阅读和实现的

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

name_frequency_hash = {}

names.each do |name|
  count = names.count(name)
  name_frequency_hash[name] = count  
end
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

我们采取的步骤:

  • 我们创建了哈希
  • 我们遍历了 names数组
  • 我们计算了每个名字出现在 names数组中的次数
  • 我们使用创建了一个键,name并使用创建了一个值count

它可能有点冗长(在性能方面,您将使用覆盖键进行一些不必要的工作),但我认为您更容易阅读和理解要实现的目标


2
我不认为这比公认的答案更容易阅读,而且显然这是一个较差的设计(做了很多不必要的工作)。
汤姆·罗德

@Tom Lord-我确实同意您的表现(我什至在我的回答中也提到过)-但是作为初学者,试图理解实际的代码和所需的步骤时,我发现它有助于更​​加冗长,然后可以重构以进行改进性能和使代码更具说明性
Sami Birnbaum

1
我有点同意@SamiBirnbaum。这是唯一一个几乎不使用像红宝石这样的特殊知识的人Hash.new(0)。最接近伪代码。对于可读性而言,这可能是一件好事,但对于那些注意到它的读者来说,做不必要的工作也会损害其可读性,因为在更复杂的情况下,他们会花一点时间认为自己会发疯,试图弄清楚这样做的原因。
Adamantish

1

这更多是评论,而不是答案,但是评论并不能做到公平。如果这样做Array = foo,则会导致至少一个IRB实现崩溃:

C:\Documents and Settings\a.grimm>irb
irb(main):001:0> Array = nil
(irb):1: warning: already initialized constant Array
=> nil
C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:3177:in `rl_redisplay': undefined method `new' for nil:NilClass (NoMethodError)
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:3873:in `readline_internal_setup'
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:4704:in `readline_internal'
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:4727:in `readline'
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/readline.rb:40:in `readline'
        from C:/Ruby19/lib/ruby/1.9.1/irb/input-method.rb:115:in `gets'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:139:in `block (2 levels) in eval_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:271:in `signal_status'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:138:in `block in eval_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:189:in `call'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:189:in `buf_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:103:in `getc'
        from C:/Ruby19/lib/ruby/1.9.1/irb/slex.rb:205:in `match_io'
        from C:/Ruby19/lib/ruby/1.9.1/irb/slex.rb:75:in `match'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:287:in `token'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:263:in `lex'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:234:in `block (2 levels) in each_top_level_statement'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `loop'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `block in each_top_level_statement'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `catch'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `each_top_level_statement'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:153:in `eval_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:70:in `block in start'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:69:in `catch'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:69:in `start'
        from C:/Ruby19/bin/irb:12:in `<main>'

C:\Documents and Settings\a.grimm>

那是因为Array是一堂课。


1
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

arr.uniq.inject({}) {|a, e| a.merge({e => arr.count(e)})}

经过的时间0.028毫秒

有趣的是,stupidgeek的实现基准化:

经过的时间0.041毫秒

和获奖答案:

经过的时间0.011毫秒

:)

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.