让我们逐行查看您的代码。
line = ("Ignore punctuation, please :)")
s = line.strip.downcase
#=> "ignore punctuation, please :)"
在这里strip 没有什么特别的原因,因为无论如何你都会删除空格和标点符号。
string = s.split(//)
#=> ["i", "g", "n", "o", "r", "e", " ", "p", "u", "n", "c", "t",
# "u", "a", "t", "i", "o", "n", ",", " ", "p", "l", "e", "a",
# "s", "e", " ", ":", ")"]
您已选择将句子拆分为字符,这很好,但正如我将在最后提到的,您可以只使用 String 方法。无论如何,
string = s.chars
做同样的事情,可以说更清楚。您现在拥有的是一个名为string 的数组。这不是有点混乱吗?让我们称之为arr:
arr = s.chars
(人们经常看到s 和str 用于字符串名称,a 和arr 用于数组名称,h 和hash 用于哈希名称,等等。)
arr.select! {|x| /[a-z]/.match(x) }
#=> ["i", "g", "n", "o", "r", "e", "p", "u", "n", "c", "t", "u",
# "a", "t", "i", "o", "n", "p", "l", "e", "a", "s", "e"]
现在您已经消除了除小写字母之外的所有字母。你也可以这样写:
arr.select! {|x| s =~ /[a-z]/ }
或
arr.select! {|x| s[/[a-z]/] }
您现在可以进行排序了。
arr.sort_by!{ |x| arr.count(x) }
#=> ["l", "g", "s", "c", "r", "i", "p", "u", "a", "o", "t", "p",
# "a", "t", "i", "o", "u", "n", "n", "e", "e", "n", "e"]
这没问题,但对数组进行排序并同时计算其元素的频率并不是一个好习惯。更好的是:
arr1 = arr.sort_by{ |x| arr.count(x) }
给出相同的顺序。生成的排序数组是否正确?让我们计算每个字母在字符串中出现的次数。
我将创建一个散列,其键是arr 的唯一元素,其值是相关键在arr 中出现的次数。有几种方法可以做到这一点。一个简单但不是很有效的方法如下:
h = {}
a = arr.uniq
#=> ["l", "g", "s", "c", "r", "i", "p", "u", "a", "o", "t", "n", "e"]
a.each { |c| h[c] = arr.count(c) }
h #=> {"l"=>1, "g"=>1, "s"=>1, "c"=>1, "r"=>1, "i"=>2, "p"=>2,
# "u"=>2, "a"=>2, "o"=>2, "t"=>2, "n"=>3, "e"=>3}
通常会这样写:
h = arr.uniq.each_with_object({}) { |c,h| h[c] = arr.count(c) }
h 的元素按价值递增的顺序排列,但这只是巧合。为了确保它们按那个顺序排列(以便更容易查看顺序),我们需要构造一个数组,对其进行排序,然后将其转换为哈希:
a = arr.uniq.map { |c| [c, arr.count(c)] }
#=> [["l", 1], ["g", 1], ["s", 1], ["c", 1], ["r", 1], ["a", 2], ["p", 2],
# ["u", 2], ["i", 2], ["o", 2], ["t", 2], ["n", 3], ["e", 3]]
a = a.sort_by { |_,count| count }
#=> [["l", 1], ["g", 1], ["s", 1], ["c", 1], ["r", 1], ["a", 2], ["t", 2],
# ["u", 2], ["i", 2], ["o", 2], ["p", 2], ["n", 3], ["e", 3]]
h = Hash[a]
#=> {"l"=>1, "g"=>1, "s"=>1, "c"=>1, "r"=>1, "i"=>2, "t"=>2,
# "u"=>2, "a"=>2, "o"=>2, "p"=>2, "n"=>3, "e"=>3}
人们通常会看到这样写:
h = Hash[arr.uniq.map { |c| [c, arr.count(c)] }.sort_by(&:last)]
或者,在 Ruby v2.0+ 中:
h = arr.uniq.map { |c| [c, arr.count(c)] }.sort_by(&:last).to_h
请注意,在 Ruby 1.9 之前,哈希中没有键排序的概念。
h 的键值对的值表明您的排序是正确的。然而,它的效率不是很高。那是因为在:
arr.sort_by { |x| arr.count(x) }
你反复遍历arr,计算元素的频率。最好在上面构造散列:
h = arr.uniq.each_with_object({}) { |c,h| h[c] = arr.count(c) }
在执行排序之前,然后:
arr.sort_by { |x| h[x] }
顺便说一句,让我提一个更有效的方法来构造哈希h,它只需要一次通过arr:
h = Hash.new(0)
arr.each { |x| h[x] += 1 }
h #=> {"l"=>1, "g"=>1, "s"=>1, "c"=>1, "r"=>1, "a"=>2, "p"=>2,
# "u"=>2, "i"=>2, "o"=>2, "t"=>2, "n"=>3, "e"=>3}
或者,更简洁:
h = arr.each_with_object(Hash.new(0)) { |x,h| h[x] += 1 }
这里h被称为计数哈希:
h = Hash.new(0)
创建一个默认值为零的空散列。这意味着如果h 没有密钥k,h[k] 将返回零。 abbreviated assignment h[c] += 1 扩展为:
h[c] = h[c] + 1
如果h没有键c,则默认值分配给右侧的h[c]:
h[c] = 0 + 1 #=> 1
但是下次遇到c:
h[c] = h[c] + 1
#=> 1 + 1 => 2
最后,让我们重新开始,尽可能多地使用String 方法:
line = ("Ignore punctuation, please :)")
s = line.strip.downcase.gsub(/./) { |c| (c =~ /[a-z]/) ? c : '' }
#=> "ignorepunctuationplease"
h = s.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
#=> {"i"=>2, "g"=>1, "n"=>3, "o"=>2, "r"=>1, "e"=>3, "p"=>2,
# "u"=>2, "c"=>1, "t"=>2, "a"=>2, "l"=>1, "s"=>1}
s.each_char.sort_by { |c| h[c] }
#=> ["l", "g", "s", "c", "r", "i", "p", "u", "a", "o", "t", "p",
# "a", "t", "i", "o", "u", "n", "n", "e", "e", "n", "e"]