有人可以解释一下inject(Hash.new(0)) { |total, bigram| total[bigram]

  • 本文关键字:total bigram new inject 解释 一下 Hash ruby
  • 更新时间 :
  • 英文 :

alphabet = ["A","B","C","D","E","F","G","H","I","J",
"K","L","M","N","O","P","Q","R","S","T",
"U","V","W","X","Y","Z"," ",".",",",";",
"-","'"
]
file = File.read("vt_00.txt")
i = 0
while i < alphabet.count do
single_char = alphabet[i]
single_char_count = file.count(single_char)
print "#{alphabet[i]} = #{single_char_count} "
j = 0
while j < alphabet.count do
two_chars = alphabet[i] + alphabet[j]  
two_chars_count = file.scan(two_chars).count
if two_chars_count > 10 && two_chars_count < 15
print "#{two_chars} = #{two_chars_count} "
end
k = 0
while k < alphabet.count do
three_chars = alphabet[i] + alphabet[j] + alphabet[k]
three_chars_count = file.scan(three_chars).count
if three_chars_count > 10 && three_chars_count < 15
print "#{three_chars} = #{three_chars_count} "
end
k += 1
end
j += 1
end
i += 1
end 

我有像上层代码这样的代码。但是后来我通过each_cons找到了解决方案,你能解释一下它是如何工作的吗? 我不明白.inject...part。

count = string.each_cons(1).inject(Hash.new(0)) { |total, bigram| total[bigram] += 1; total }.sort_by {  |_key, value| value }.reverse.to_h

更精细的编写方法是:

total = Hash.new(0)
string.each_cons(1).each{|bigram| total[bigram] += 1} 

inject允许注入一些起始值(Hash.new(0)-->我们使用默认的 0,这样我们就可以安全地使用+=运算符),并且块返回的任何内容都会在下一次迭代中注入。所以在这种情况下,我们必须显式返回哈希(total)才能在下一步中操作它。

一个简单的例子是将数组的所有值相加:

[1,4,5,23,2,66,123].inject(0){|sum, value| sum += value}

我们从 0 开始,这是我们执行0 + 1的第一次迭代,然后其结果将在下一次迭代中注入。

注意:在原始代码中,您可以更轻松地迭代数组,而不是使用while循环和维护计数器,如下所示:

alphabet.each do |single_char|
single_char_count = file.count(single_char)
print "#{alphabet[i]} = #{single_char_count} "
alphabet.each do |second_char| 
two_chars = single_char + second_char  
# do something with two_chars 
alphabet.each do |third_char|
three_chars = single_char + second-char + third_char
# do something with three_chars
end 
end
end  

我猜这取决于file的大小,迭代所有each_cons(1-2-3)还是使用file.scan会更有效。

问题

您想知道以下内容是如何工作的:

g = Hash.new(0)
count = str.each_char.inject(g) do |h, s|
h[s] += 1
h
end.sort_by { |_key, value| value }.reverse.to_h

str.each_cons(1)不起作用,因为类String(str是一个实例)没有实例方法each_cons。有一个方法 Enumerable#each_cons,但类Stringinclude该模块,因此字符串不响应该方法:

String.included_modules
#=> [Comparable, Kernel]

String#each_char 在这里确实有意义,因为它返回一个生成字符串每个字符的枚举器。因此,我认为each_char的意思是写each_cons(1)的地方。

我已将变量名称更改为更通用的名称,并已移动

g = Hash.new(0)

到单独的行。

一个例子

假设str如下所示:

str = "The Cat and the Hat"

检查执行的步骤

让我们将计算分解为多个部分:

g = Hash.new(0)
#=> {}
h = str.each_char.inject(g) do |h,s|
h[s] += 1
h
end
#=> {"T"=>1, "h"=>2, "e"=>2, " "=>4, "C"=>1,
#    "a"=>3, "t"=>3, "n"=>1, "d"=>1, "H"=>1}
a = h.sort_by { |_key, value| value }
#=> [["T", 1], ["C", 1], ["n", 1], ["d", 1], ["H", 1],
#    ["h", 2], ["e", 2], ["a", 3], ["t", 3], [" ", 4]] 
b = a.reverse
#=> [[" ", 4], ["t", 3], ["a", 3], ["e", 2], ["h", 2],
#    ["H", 1], ["d", 1], ["n", 1], ["C", 1], ["T", 1]] 
count = b.to_h
#=> {" "=>4, "t"=>3, "a"=>3, "e"=>2, "h"=>2,
#    "H"=>1, "d"=>1, "n"=>1, "C"=>1, "T"=>1} 

abcount的计算很简单,所以让我们先考虑一下。

a的计算

与所有Enumerable方法一样,Enumerable#sort_by 要求其接收方响应方法each。这里sort_by的接收者是一个哈希,所以h必须响应 Hash#each。事实上,sort_by的第一个操作是通过向枚举器发送方法Hash#eachh转换为枚举器:

enum = h.each
#=> #<Enumerator: {"T"=>1, "h"=>2, "e"=>2, " "=>4, "C"=>1, "a"=>3,
#                  "t"=>3, "n"=>1, "d"=>1, "H"=>1}:each>

我们可以通过重复向枚举器#next方法发送方法来查看此枚举器生成的值:

enum.next  #=> ["T", 1] 
enum.next  #=> ["h", 2] 
enum.next  #=> ["e", 2] 
...
enum.next  #=> ["H", 1] 
enum.next  #=> StopIteration (iteration reached an end)

可以看出,enum生成了哈希的键值对序列。因此

h.sort_by { |_key, value| value } 

相当于

[["T", 1], ["h", 2], ["e", 2],..., ["H", 1]].sort_by { |_key, value| value }

这就解释了为什么a等于上面显示的数组。

b的计算

这个计算再简单不过了。请注意,我们可以通过将b = h.sort_by { |_key, value| value }.reverse替换为

b = h.sort_by { |_key, value| -value }
#=> [[" ", 4], ["a", 3], ["t", 3], ["h", 2], ["e", 2],
#    ["T", 1], ["C", 1], ["n", 1], ["d", 1], ["H", 1]]

这将像以前一样按值的降序对h的键值对进行排序,尽管连接的顺序略有不同。

count的计算

这是方法 Array#to_h 的简单应用。

h的计算

此计算的第一步是使用 Hash::new 方法创建一个默认值为零的空哈希:

h = Hash.new(0)
#=> {}

这只会导致h[k]在没有键k时返回默认值零h。例如,由于h现在没有键:

h['cat']
#=> 0

如果我们现在设置

h['cat'] = 3

然后

h['cat']
#=> 3

因为默认值不再适用。以这种方式创建的哈希h通常称为计数哈希。Ruby 解析表达式h[s] += 1的第一步是将其扩展为:

h[s] = h[s] + 1

如果h没有键s则表达式简化为

h[s] = 0 + 1

因为等号右侧的h[s](方法 Hash#[],与左侧的方法 Hash#[]= 相反)返回默认值零。如果字符串被"aaa",将进行以下计算:

h['a'] = h['a'] + 1 => 0 + 1 => 1
h['a'] = h['a'] + 1 => 1 + 1 => 2
h['a'] = h['a'] + 1 => 2 + 1 => 3

右侧的h['a']在第一步中返回默认值零,但由于h在第二步和第三步中具有键'a',因此在第一步之后返回h['a']的当前值。

Enumerable#inject(又名reduce)可以在这里使用,但h的计算更常写如下:

h = str.each_char.each_with_object(Hash.new(0)) { |s,h| h[s] += 1 }
#=> {"T"=>1, "h"=>2, "e"=>2, " "=>4, "C"=>1,
#    "a"=>3, "t"=>3, "n"=>1, "d"=>1, "H"=>1}

请参阅枚举#each_with_object。

最新更新