从数组和频率创建哈希表

17
我有一个数组 [1,2,4,5,4,7],我想找到每个数字的频率并将其存储在哈希中。我有这段代码,但它会返回 NoMethodError: undefined method '+' for nil:NilClass
def score( array )
  hash = {}
  array.each{|key| hash[key] += 1}
end

期望的输出是

{1 => 1, 2 => 1, 4 => 2, 5 => 1, 7 => 1 }

3
有人建议使用inject(也称为reduce), 这没问题,但你只需要将 hash = {} 改成 hash = Hash.new(0)。这会告诉Ruby,在遇到需要值的上下文中(例如 hash[key] += 1v = hash[key]),如果哈希表不包含键 key,就要在采取进一步动作之前向哈希表中添加 key=>0(例如 hash[key] += 1)。但是,如果哈希表中没有该键,则 if hash[key] == 7 将求值为 if nil == 7;哈希表不会添加 key=>0 - Cary Swoveland
@CarySwoveland 不错的教学...!! 喜欢它.. +1. - Arup Rakshit
2
@tokland,问题可能是相同的,但不是这个问题。在这里,先生想知道为什么他会得到一个特定的错误,这对我来说似乎非常合理。 - Cary Swoveland
好的,那么我们可以说它是相关的 :) https://dev59.com/XWkx5IYBdhLWcg3wCf8q - tokland
8个回答

25
在Ruby 2.4+中:
def score(array)
  array.group_by(&:itself).transform_values!(&:size)
end

23

按照以下步骤执行:

def score( array )
  hash = Hash.new(0)
  array.each{|key| hash[key] += 1}
  hash
end
score([1,2,4,5,4,7]) # => {1=>1, 2=>1, 4=>2, 5=>1, 7=>1}

或者更具有 Ruby 风格,使用 Enumerable#each_with_object

def score( array )
  array.each_with_object(Hash.new(0)){|key,hash| hash[key] += 1}
end
score([1,2,4,5,4,7]) # => {1=>1, 2=>1, 4=>2, 5=>1, 7=>1}

为什么会出现NoMethodError: undefined method '+' for nil:NilClass

hash = {}是一个空哈希,其默认值为nilnilNilClass的一个实例,而NilClass没有任何名为#+的实例方法。因此,你会得到NoMethodError

看一下Hash::new文档:

new → new_hash
new(obj) → new_hash

返回一个新的、空的哈希表。如果随后访问此哈希表的键不对应于哈希表中的任何条目,则返回的值取决于用于创建哈希表的 new 的类型。在第一种形式中,访问返回 nil。如果指定了 obj,则该单个对象将用于所有默认值。如果指定了块,则将使用哈希对象和键调用它,并应返回默认值。如果需要,块负责将值存储在哈希表中。

19

Ruby 2.7及以上版本将提供Enumerable#tally方法,可解决此问题。

从主干文档中可以看到:

对集合进行计数。返回一个哈希表,其中键为元素,值为与键对应的集合中的元素数。

["a", "b", "c", "b"].tally #=> {"a"=>1, "b"=>2, "c"=>1}

13

只需使用“inject”即可。这种类型的应用程序正是它的用途所在。 例如:

a.inject(Hash.new(0)) {|hash,word| hash[word] += 1; hash }

2

我喜欢使用inject

results = array.inject(Hash.new(0)) {|hash, arr_element| hash[arr_element] += 1; hash }

1.9.3p448 :082 > array = [1,2,4,5,4,7]
 => [1, 2, 4, 5, 4, 7] 
1.9.3p448 :083 > results = array.inject(Hash.new(0)) {|hash, arr_element| hash[arr_element] += 1; hash }
 => {1=>1, 2=>1, 4=>2, 5=>1, 7=>1} 

4
在我看来,使用each_with_object会更简洁:a.each_with_object(Hash.new(0)) { |e, h| h[e] += 1 }。该方法可以创建一个空的哈希表并遍历数组a中的每个元素 e,将其作为键,并将对应值加1,最后返回更新后的哈希表。 - mu is too short
1
各有所好,@muistooshort :) - CDub
3
在“迭代并反馈”情况下,我倾向于使用inject方法;在“迭代并收集”情况下,我则会使用each_with_object方法。我已经厌倦了使用; hash的方式。 - mu is too short

2
这里有一个使用哈希数组初始化器的简短选项。
Hash[arr.uniq.map {|v| [v, arr.count(v)] }]

1
这里的关键是当数组中第一次出现1时,hash[1]不存在(为nil)。
你需要想办法初始化它,hash = Hash.new(0) 是最简单的方法。在这种情况下,0是你想要的初始值。

0

或使用group by方法:

arr = [1,2,4,5,4,7]

Hash[arr.group_by{|x|x}.map{|num,arr| [num, arr.size] }]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接