## 7.哈希和符号 哈希是具有程序或用户而不是 Ruby 解释器定义的索引的数组。 让我们看一个程序,以了解哈希是如何工作的。 在文本编辑器中输入以下程序( [hash.rb](code/hash.rb) )并执行它。 ```rb #!/usr/bin/ruby # hash.rb mark = Hash.new mark['English'] = 50 mark['Math'] = 70 mark['Science'] = 75 print "Enter subject name:" sub = gets.chop puts "Mark in #{sub} is #{mark[sub]}" ``` 输出 1 ```rb Enter subject name:Math Mark in Math is 70 ``` 输出 2 ```rb Enter subject name:French Mark in French is ``` 看一下输出 1。程序要求用户输入主题名称。 当用户输入`Math`时,程序将给出数学标记。 让我们遍历代码。 一开始,我们有`mark = Hash.new`行,在这一行中,我们声明了一个称为 hash 类型的标记的变量。 看一下以下几行 ```rb mark['English'] = 50 mark['Math'] = 70 mark['Science'] = 75 ``` 与数组不同,哈希可以将对象作为索引。 在这种情况下,我们使用简单的字符串作为索引。 我们将在英语,数学和科学中获得的分数放在`mark['English']`,`mark['Math']`和`mark['Science']`中。 接下来的两个语句 ```rb print "Enter subject name:" sub = gets.chop ``` 提示用户输入标记,当他输入标记时,它将被存储到名为`sub`的变量中。 在最后一行`puts "Mark in #{sub} is #{mark[sub]}"`中,我们简单地使用`sub`作为键访问哈希值并打印出来。 看一下输出 2,在这种情况下,我输入了`French`,程序没有给出任何结果。 在下面的程序中,我们将学习如何处理它。 ### 7.1。 散列中的默认值 当我们将索引传递给散列并且如果其值确实存在时,则散列将忠实地返回该值。 如果索引没有定义值怎么办。 在前面的示例中,我们看到该程序未返回任何内容。 我们希望在此程序中对其进行修复。 看这段代码`mark = Hash.new 0`。 在其中,我们给出了`mark = Hash.new 0`,而不是像上一个那样给出`mark = Hash.new`,这里零是默认值。 现在让我们运行程序,看看会发生什么。 ```rb #!/usr/bin/ruby # hash_default_value.rb mark = Hash.new 0 # We specify default value of mark is zero mark['English'] = 50 mark['Math'] = 70 mark['Science'] = 75 print "Enter subject name:" sub = gets.chop puts "Mark in #{sub} is #{mark[sub]}" ``` 输出量 ```rb Enter subject name:Chemistry Mark in Chemistry is 0 ``` 查看输出,我们尚未为`mark['Chemistry']`定义值,但是当主题名称指定为“化学”时,结果为 0。 之所以如此,是因为我们将零设置为默认值。 因此,通过设置默认值,我们将获得尚未定义的那些索引的值。 ### 7.2。 循环散列 数组中的循环非常容易,我们通常使用数组中的每个函数来迭代数组中的对象。 以类似的方式,我们可以循环散列。 在文本编辑器中输入以下代码 hash_looping.rb 并执行它。 ```rb #!/usr/bin/ruby # hash_looping.rb mark = Hash.new 0 # We specify default value of mark is zero mark['English'] = 50 mark['Math'] = 70 mark['Science'] = 75 total = 0 mark.each { |key,value| total += value } puts "Total marks = "+total.to_s ``` Output ```rb Total marks = 195 ``` 在上面的程序中,我们计算了哈希`mark`中存储的所有标记的总数。 注意我们如何使用`each`循环。 请注意,我们在循环体中使用`|key,value|`获得了键值对。 `key`保留哈希的索引,`value`保留存储在该特定索引 <sup class="footnote">[ [25](#_footnotedef_25 "View footnote.") ]</sup> 处的值。 每次执行循环时,我们都将值加到 total,因此最后,变量 total 获得了散列中存储的值的总数。 最后,我们打印出总数。 下面是另一个将学生标记存储在哈希中的程序,我们使用 each 循环打印与`key`对应的`key`和`value`。 希望你已经足够了解,可以自行解释以下代码。 ```rb #!/usr/bin/ruby # hash_looping_1.rb mark = Hash.new 0 # We specify default value of mark is zero mark['English'] = 50 mark['Math'] = 70 mark['Science'] = 75 total = 0 puts "Key => Value" mark.each { |a,b| puts "#{a} => #{b}" } ``` Output ```rb Key => Value Science => 75 English => 50 Math => 70 ``` ### 7.3。 散列创建的更多方法 还有另一种创建哈希的方法,让我们来看一下。 参见下文,该程序的解释与先前程序 [hash_looping_1.rb](code/hash_looping_1.rb) 的解释相同,不同之处在于以下程序中的突出显示的行。 希望你能自己解释一下这些程序。 ```rb #!/usr/bin/ruby # hash_creation_1.rb marks = { 'English' => 50, 'Math' => 70, 'Science' => 75 } puts "Key => Value" marks.each { |a,b| puts "#{a} => #{b}" } ``` Output ```rb Key => Value Science => 75 English => 50 Math => 70 ``` ### 7.4。 使用符号 通常在哈希中,我们使用符号作为键而不是字符串。 这是因为与 String 相比,Symbol 占用的空间要少得多。 速度和空间要求的差异现在可能对你并不明显,但是如果你编写的程序会创建成千上万的哈希,则可能会付出巨大的代价。 因此,请尝试使用符号代替字符串。 那么什么是符号? 通过在终端中输入`irb --simple-prompt`来激活我们的 irb。 在其中键入以下内容 ```rb >> :x.class => Symbol ``` 注意,我们在`x`之前放置了一个冒号,从而使其成为`:x`。 符号以冒号开头。 当我们问`:x`是什么类时,它说它是`Symbol`。 符号是可以在哈希 <sup class="footnote">[ [26](#_footnotedef_26 "View footnote.") ]</sup> 中用作`key`的事物或对象。 以类似的方式,我们声明另一个称为`:name`的符号,并查看其所属的类。 ```rb >> :name => :name >> :name.class => Symbol ``` 变量可以在其中包含符号。 请注意,下面我们为变量`a`分配了值`:apple`,该变量只不过是一个符号。 当我们通过使用`a.class`询问它是`a`是什么类时,它说出它是一个符号。 ```rb >> a = :apple => :apple >> a.class => Symbol ``` 可以使用`to_s`方法将符号转换为字符串。 请看下面的 irb 示例,我们将符号转换为字符串。 ```rb >> :orange.to_s => "orange" ``` 要将字符串转换为符号,请使用`.to_sym`方法,如下所示 ```rb >> "hello".to_sym => :hello ``` 让我们编写一个程序,在该程序中,哈希不使用 String 而是使用 Symbols 作为键。 在文本编辑器中输入下面的程序 hash_symbol.rb 并执行它。 ```rb # hash_symbol_2.rb mark = Hash.new 0 # We specify default value of mark is zero mark[:English] = 50 mark[:Math] = 70 mark[:Science] = 75 print "Enter subject name:" sub = gets.chop.to_sym puts "Mark in #{sub} is #{mark[sub]}" ``` Output ```rb Enter subject name:Math Mark in Math is 70 ``` 程序运行时,提示输入主题名称,输入后显示相应的标记。 让我们遍历代码,看看它是如何工作的。 请注意,在标记哈希中,我们使用符号而不是字符串作为键,如图所示 ```rb mark[:English] = 50 mark[:Math] = 70 mark[:Science] = 75 ``` 接下来,我们提示用户使用打印“输入主题名称:”输入标记,然后输入主题名称。 现在看下一行,首先我们使用`gets.chop`将主题名称放入变量`sub`中。 `chop`会删除你在按下键盘上的 Enter 键时输入的输入字符`\n`。 `to_sym`将你输入的任何内容转换为符号,所有这些最终存储在`sub`中。 ```rb sub = gets.chop.to_sym ``` 接下来,我们要做的就是访问具有键`sub`的哈希值,并使用以下语句进行打印 ```rb puts "Mark in #{sub} is #{mark[sub]}" ``` 因此,你已经看到了该程序 [hash_creation_1.rb](code/hash_creation_1.rb) ,我们现在已经了解符号,因此可以将其编写如下: ```rb #!/usr/bin/ruby # hash_creation_1_a.rb marks = { :English => 50, :Math => 70, :Science => 75 } puts "Key => Value" marks.each { |a,b| puts "#{a} => #{b}" } ``` Output ```rb Key => Value English => 50 Math => 70 Science => 75 ``` 参见`marks = { :English ⇒ 50, :Math ⇒ 70, :Science ⇒ 75 }`行,我们在这里使用符号而不是字符串作为哈希键。 与字符串相比,哈希具有一些优势,因为与字符串相比,它们占用的内存更少(在程序运行期间)。 在 ruby 1.9 及更高版本中,有更好的写 [hash_creation_1_a.rb](code/hash_creation_1_a.rb) 的方法,你可以在下面提到的 [hash_creation_2.rb](code/hash_creation_2.rb) 中看到。 只需在下面的程序中查看此`marks = { English: 50, Math: 70, Science: 75 }`行 ```rb #!/usr/bin/ruby # hash_creation_2.rb marks = { English: 50, Math: 70, Science: 75 } puts "Key => Value" marks.each { |a,b| puts "#{a} => #{b}" } ``` 该程序与上一个程序完全一样,但是行`marks = { English: 50, Math: 70, Science: 75 }`被翻译(假定已翻译)为以下代码`marks = { :English ⇒ 50, :Math ⇒ 70, :Science ⇒ 75 }`,因此它是一种新的简短形式,用于声明以符号为键的哈希,这是在 ruby 中新引入的。 1.9 ### 7.5。 字符串,冻结的字符串&符号,它们的存储足迹 字符串占用大量内存,让我们看一个例子,启动你的 irb 并输入以下代码 ```rb >> c = "able was i ere i saw elba" => "able was i ere i saw elba" >> d = "able was i ere i saw elba" => "able was i ere i saw elba" >> c.object_id => 21472860 >> d.object_id => 21441620 ``` 在上面的示例中,我们看到了两个变量`c`和`d`,它们都分配给相同的字符串“ able is ire ere elba”,但是如果我通过调用`c.object_id`和`d.object_id`看到对象 ID 是 ,两者是不同的。 这意味着这两个“可能是我看见了厄尔巴岛”存储在不同的位置。 它们被复制并复制。 这意味着,假设你在程序中的多个位置声明了相同的字符串,并且所有字符串都将占用新的内存,因此这将导致计算机 RAM 上的大量负载(对于大型程序)。 现在让我们看看如果我们使用一种称为冻结字符串的新事物会发生什么。 在 irb 中输入以下代码 ```rb >> a = "able was i ere i saw elba".freeze => "able was i ere i saw elba" >> b = "able was i ere i saw elba".freeze => "able was i ere i saw elba" >> a.object_id => 21633340 >> b.object_id => 21633340 ``` 现在,在上面的示例中,我们在字符串上调用了一个名为`freeze`的方法。 现在,当我检查`a`和`b'的对象 ID 时,两者是相同的。 这意味着它们都指向同一字符串。 他们只是占据一个空间。 就是这样 在前面的示例中,每次都会创建新的东西,但是当我们为变量分配冻结字符串时,它将检查天气是否已经(冻结)声明了该字符串,如果是,则仅指向同一位置。 现在让我们看看符号如何占据空间,以及它们是否一次又一次地复制。 ```rb >> e = :some_symbol => :some_symbol >> f = :some_symbol => :some_symbol >> e.object_id => 1097628 >> f.object_id => 1097628 ``` 因此,在上面的示例中,我们将`e`和`f`分配给符号`:some_symbol`,当我们检查它们的对象 ID 时,它们都是相同的,或者它们都指向相同的位置。 这意味着,如果我们一次又一次地声明符号,它们将不会占用额外的空间。 冻结的字符串和符号对于存储很有用。 普通的字符串是不好的。 那我为什么要在哈希部分说这个呢? 假设你看到了这段代码 ```rb >> person = {"name" => "frank"} => {"name"=>"frank"} >> person2 = {"name" => "bob"} => {"name"=>"bob"} and you have this one >> person = {"name".freeze => "frank"} => {"name"=>"frank"} >> person2 = {"name".freeze => "bob"} => {"name"=>"bob"} ``` 试想一下哪一个将占用最少的内存? 现在考虑在数十行代码中使用相同哈希结构的大型程序……。 ### 7.6。 压实哈希 就像可以压缩数组一样,此新功能从 Ruby 2.4.0 引入到哈希中,使程序员可以对其进行压缩。 让我们看一个 irb 或 pry 的例子 ```rb >> hash = {a: 1, b: nil, c: 2, d: nil, e: 3, f:nil} => {:a=>1, :b=>nil, :c=>2, :d=>nil, :e=>3, :f=>nil} >> hash.compact => {:a=>1, :c=>2, :e=>3} >> hash => {:a=>1, :b=>nil, :c=>2, :d=>nil, :e=>3, :f=>nil} >> hash.compact! => {:a=>1, :c=>2, :e=>3} >> hash => {:a=>1, :c=>2, :e=>3} ``` 该解释与关于数组压缩的解释非常相似。 请参考。 ### 7.7。 转换哈希值 假设你有一个 Hash 对象,并且想要转换其值。 假设你有一个哈希,其值是数字,并且想要将其转换为它们的平方,然后在该 Hash 实例上使用 transform_values 函数。 让我们来看看代码,看看它是如何工作的。 点火或撬动 首先定义一个哈希,如下所示 ```rb >> hash = {a: 1, b: 2, c: 3} => {:a=>1, :b=>2, :c=>3} ``` 现在我们要转换值,所以我们在哈希表上调用`transform_values`,如下所示 ```rb >> hash.transform_values{ |value| value * value } => {:a=>1, :b=>4, :c=>9} ``` 如果你注意到,它的功能与在 Array 中收集非常相似,但适用于 Hash 中的值。 现在让我们看看哈希是否已更改 ```rb >> hash => {:a=>1, :b=>2, :c=>3} ``` 从上面的代码片段可以看出,答案是否定的。 如果要更改在其上调用`transform_values`的 Hash 实例,请如下所示用一个爆炸声来调用它。 它将更改调用它的哈希对象。 ```rb >> hash.transform_values!{ |value| value * value } => {:a=>1, :b=>4, :c=>9} >> hash => {:a=>1, :b=>4, :c=>9} ``` ### 7.8。 阅读 rubydoc 再次,这本书无法解释哈希中的所有内容。 要了解更多信息,请参考 rubydocs 以获取哈希值 [https://ruby-doc.org/core-2.5.1/Hash.html](https://ruby-doc.org/core-2.5.1/Hash.html)