假设我的每行都有一个带数字的文件:
0101
1010
1311
0101
1311
431
1010
431
420
最后,我会得到一个散列,其中包含每个数字的出现次数,在这种情况下:
{0101 => 2, 1010 => 2, 1311 => 2, 431 => 2, 420 => 1}
我怎样才能做到这一点?
答案 0 :(得分:11)
简单的单行,给定数组items
:
items.inject(Hash.new(0)) {|hash, item| hash[item] += 1; hash}
工作原理:
Hash.new(0)
创建一个新的Hash,访问未定义的键返回0。
inject(foo)
遍历具有给定块的数组。对于第一次迭代,它传递foo
,并且在进一步的迭代中,它传递最后一次迭代的返回值。
另一种写作方式是:
hash = Hash.new(0)
items.each {|item| hash[item] += 1}
答案 1 :(得分:4)
这与Chuck的基本相同,但是当你创建一个数组或散列时,'each_with_object'会使它比'inject'稍微简单一些,因为你不必在块中写入最后的数组或散列。
items.each_with_object(Hash.new(0)) {|item, hash| hash[item] += 1}
答案 2 :(得分:2)
ID = -> x { x } # Why is the identity function not in the core lib?
f = <<-HERE
0101
1010
1311
0101
1311
431
1010
431
420
HERE
Hash[f.lines.map(&:to_i).group_by(&ID).map {|n, ns| [n, ns.size] }]
# { 101 => 2, 1010 => 2, 1311 => 2, 431 => 2, 420 => 1 }
您只需使用Enumerable#group_by
自行对数字进行分组,这样就可以使用
{ 101 => [101, 101], 420 => [420] }
然后你Enumerable#map
将值数组加到它们的长度上,即[101, 101]
变为2
。然后使用Hash
将其转换回Hash::[]
。
但是,如果您愿意使用第三方库,它会变得更加微不足道,因为如果您使用MultiSet
数据结构,答案自然就会消失。 (MultiSet
与Set
类似,不同之处在于可以多次添加项目,MultiSet
将统计项目的添加频率 - 这正是您想要的。 )
require 'multiset' # Google for it, it's so old that it isn't available as a Gem
Multiset[*f.lines.map(&:to_i)]
# => #<Multiset:#2 101, #2 1010, #2 1311, #2 431, #1 420>
是的,就是这样。
使用正确的数据结构是一个美好的事情:您的算法变得更加简单。或者,在这种特殊情况下,算法只是消失。
我已经写了更多关于使用MultiSet
来解决这个问题的详细信息
group_by
示例相同。)