哈希表(Hash)

虽然数组提供了一种好的方式来通过数字索引集合中的元素,但有时候以其它方式进行索引会更方便。例如,如果你正在创建一个食谱集合,那么按名称索引每个食谱会更有意义,例如 “Rich Chocolate Cake” 和 “Coq au Vin”,而不是数字 23、87 等等。

Ruby 有一个类可以让你做到这一点。它被称为哈希(Hash)。在其它语言中被称为字典(Dictionary)。就像真正的字典一样,条目由一些唯一键(在字典中,这将是一个单词)索引,该键与一个值相关联(在字典中,这将是单词的定义)。

创建哈希表

hash1.rb

就像数组一样,您可以通过创建 Hash 类的新实例来创建一个哈希表:

  1. h1 = Hash.new
  2. h2 = Hash.new("Some kind of ring")

上面的两个例子都创建了一个空的哈希表。Hash 对象始终具有一个默认值,即在给定索引处未找到特定值时返回的值。在这些例子中,h2 用 “Some kind of ring” 作为初始化的默认值,h1 没有指定初始化的默认值,因此其默认值为 nil

创建 Hash 对象后,可以使用类似数组的语法向其添加元素,也就是说将索引放在方括号中,使用 = 号来赋值。这里明显的区别是,对于数组来说索引必须是整数; 而对于哈希表,它可以是任何唯一的数据项:

  1. h2['treasure1'] = 'Silver ring'
  2. h2['treasure2'] = 'Gold ring'
  3. h2['treasure3'] = 'Ruby ring'
  4. h2['treasure4'] = 'Sapphire ring'

通常,键(key)可以是数字,或者如上面的代码中那样是字符串。 但是,原则上键可以是任何类型的对象。

唯一键(keys)?

给哈希表分配键(key)时要小心。如果你在一个 Hash 中使用了两次同样的键(key),你最终将覆盖原来的值。这就像为数组中的同一索引赋值两次一样。思考这个例子: h2[‘treasure1’] = ‘Silver ring’ h2[‘treasure2’] = ‘Gold ring’ h2[‘treasure3’] = ‘Ruby ring’ h2[‘treasure1’] = ‘Sapphire ring’在这里 “treasure1” 键使用了两次。因此,原来的值 “Silver ring” 被 “Sapphire ring” 替换,该 Hash 为: {“treasure1” => “Sapphire ring”, “treasure2” => “Gold ring”, “treasure3” => “Ruby ring”}

给定一些类 X,以下的赋值是合法的:

  1. x1 = X.new('my Xobject')
  2. h2[x1] = 'Diamond ring'

有一种创建哈希表并初始化一些键值对的简写方式。只需要在键(key)后跟 => 和关联的值(value),每个键值对使用逗号分割,整体放在一对花括号中:

  1. h1 = { 'room1'=>'The Treasure Room',
  2. 'room2'=>'The Throne Room',
  3. 'loc1'=>'A Forest Glade',
  4. 'loc2'=>'A Mountain Stream' }

哈希表索引

要访问值,将键(key)放在方括号中:

  1. puts(h1['room2']) #=> "The Throne Room"

如果指定不存在的键,则返回默认值。回想一下,之前我们没有为 h1 指定默认值,但为 h2 指定了默认值:

  1. p(h1['unknown_room']) #=> nil
  2. p(h2['unknown_treasure']) #=> 'Some kind of ring'

使用 default 方法获取默认值,并用 default= 方法可以设置默认值(有关 getset 存取器方法的详细信息,请参见第 2 章):

  1. p(h1.default)
  2. h1.default = 'A mysterious place'

哈希表拷贝

hash2.rb

与数组一样,可以将一个 Hash 变量赋值给另一个变量,在这种情况下两个变量将引用相同的 Hash,使用任何一个变量的修改都会影响到该 Hash:

  1. h4 = h1
  2. h4['room1'] = 'A new Room'
  3. puts(h1['room1']) #=> 'A new Room'

如果你希望两个变量引用拥有相同元素的不同的 Hash 对象,使用 clone 方法创建一个新副本:

  1. h5 = h1.clone
  2. h5['room1'] = 'An even newer Room'
  3. puts(h1['room1']) #=> 'A new room' (i.e. its value is unchanged)

哈希表排序

hash_sort.rb

和数组一样,你可能会发现 Hash 的 sort 方法也存在一些问题。该方法期望处理的键(keys)具有相同的数据类型,因此,你将一个使用数字索引和另一个使用字符串索引的数组合并后,你无法对该 Hash 进行正确排序。和数组一样,解决该问题的方法是通过编写一些代码来进行自定义类型的比较并将其传递给 sort 方法。你可能会定义一个方法,如下所示:

  1. def sorted_hash( aHash )
  2. return aHash.sort {
  3. |a,b|
  4. a.to_s <=> b.to_s
  5. }
  6. end

这将会根据 Hash 中每个键(key)的字符串(to_s)形式进行排序。实际上,Hash 的 sort 方法是将 Hash 转换为嵌套数组 [key,value] 后使用 Array 的 sort 方法对它们进行排序。

哈希表方法

hash_methods.rb

Hash 类有许多内置方法。例如,哈希表 aHash 通过元素的键来删除元素使用 aHash.delete( someKey )。测试一个键(key)或者(value)是否存在则使用 aHash.has_key?( someKey )aHash.has_value?( someValue )。要返回一个使用原始哈希表的值(values)作为键(keys)创建的新哈希表,并将其键作为值使用则通过 aHash.invert 方法;返回一个填充了哈希键(keys)或其值(values)的数组使用 aHash.keysaHash.values,等等。

hash_methods.rb 中有许多这些方法的示例。