Successfully reported this slideshow.
Your SlideShare is downloading. ×

第四章

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
第四章

数组和哈希
到现在,我们通常都是一次性使用一个对象。这一章,我们将讨论如何来创建一组对象。我
们将从最常用的列表结构——数组开始。


数组
                                              ...
array2.rb



在数组中存放不同的数据类型,甚至可以包含一个可以计算其值的表达式。让我们假设你已
经创建了这个方法:

   def hello

       return “hello world”

   end

现在你可以这...
a = Array.new                             #an empty array

  a = Array.new(2)                          #[nil,nil]

  a = A...
Advertisement
Advertisement
Advertisement
Upcoming SlideShare
Scala+RDD
Scala+RDD
Loading in …3
×

Check these out next

1 of 15 Ad

More Related Content

Advertisement

第四章

  1. 1. 第四章 数组和哈希 到现在,我们通常都是一次性使用一个对象。这一章,我们将讨论如何来创建一组对象。我 们将从最常用的列表结构——数组开始。 数组 array0.rb 什么是数组? 一个数组就是一个连续的集合,在该集合中的每一项都是可以被索引的。在 Ruby 中 ( 不像 其他很多的语言 ) ,一个数组中可以包含不同数据类型的项,例如字符串,整型数和浮点 数,甚至是一个有返回值的方法: a1 = [1,'two',3.0,array_length(a0)] 数组中的第一项索引值为 0 ,这就意味着最后一项的索引值为该数组中项总数减去 1 。在上 面给出的数组 a1 中,可以这样来获得第一和最后一项的值: a1[0] # 返回第一项 ( 索引值为 0) a1[3] # 返回第四项 ( 索引值为 3) 我们已经使用过了几次数组,例如在第二章的 2adventure.rb 中,我们使用一个数组来存储 房间的地图: mymap = Map.new([room1,room2,room3]) 创建数组 与很多其他的编程语言相同,Ruby 使用方括号来界定一个数组。你可以很容易地通过在其 中填充一些用都好分隔的值来创建一个数组: arr = ['a','b','c'] puts(arr[0]) #打印出'a' puts(arr[1]) #打印出'b' puts(arr[2]) #打印出'c' puts(arr[3]) #nil
  2. 2. array2.rb 在数组中存放不同的数据类型,甚至可以包含一个可以计算其值的表达式。让我们假设你已 经创建了这个方法: def hello return “hello world” end 现在你可以这么声明数组了: x = [1+2,hello,`dir`] 在该数组中,第一个元素是整数 3,第二个是字符串”hello world”(方法 hello 返回的)。 如果你在 Windows 上运行该程序,第三个元素将会是一个路径名的字符串。这取决于`dir` 是一个反引号字符串,该字符串将会由操作系统执行(详见第三章)。该数组最后的'槽'由 dir 命令行的返回值填充,该值为一个文件名的字符串表示。如果你在一个其他的操作系统运行 的话,你需要将其替换为一个相应命令。 dir_array.rb 创建一个文件名的数组 大部分的 Ruby 内置类的方法返回数组。例如, Dir 类,该类用于对磁盘目录进行操作,有 一个方法 entries 。传入一个路径名给该方法,它以数组形式返回所有文件的列表: Dir.entries('C:') #returns an array of files in C: 如果你想创建一个单引号字符串的数组,但是又不想频繁地输入所有的引号符,一个捷径就 是将所有文本输入,不使用引号但是使用空格来分隔它们,然后将这些文本放置于一个 由%w 开头后接的一个括号中: array2.rb y = %w(this is an array of strings) 你也可以通过对象的构造方法 new 来创建一个数组。你可以传入(也可不传)一个整数给 new 方法来创建一个指定大小的空数组(每个对象都设置为 nil),或者你可以传入两个参数 ——第一个参数用于设置数组大小,第二个参数用于指定数组中所有项的初始值,如下:
  3. 3. a = Array.new #an empty array a = Array.new(2) #[nil,nil] a = Array.new(2,”hello world”) #[“hello world”,”hello world”] 多维数组 创建一个多维数组,你可以创建一个数组,然后添加其他数组到它的每一个'槽'内。例如, 创建了一个包含两个元素的数组,每一个数组自身又包含两个元素: a = Array.new(2) a[0] = Array.new(2,'hello') a[1] = Array.new(2,'world') 你也可以通过传递一个数组作为参数给 new 方法创建一个数组。注意,这是 Ruby 的一个古 怪,传递一个数组参数时有没有使用内附圆括号都是合法的,但是如果你在没有使用括号时, 在 new 方法和 '[' 之间不留一个空格符的话, Ruby 认为这是一个语法错误——另一个关于在 传递参数时使用括号习惯的重要性的原因。译者注: y = Array.new [1,'two'] 是正确的 同样也可以通过使用方括号内嵌数组到另一个数组中来创建数组。下面的语句创建了一个包 含四个整型数组的数组: a=[ [1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16] ] 在上面的代码中,我将四个子数组分别写在单独的行。这并不是强制性的,但是这样将每个 子数组写在单独行能帮助我们更为清晰地了解多维数组的结构,像电子表格一般地将每行展 开。当讨论数组的数组时,很方便地将每一个内嵌数组当作”外部”数组的“行”。 multi_array.rb 如果需要更多关于多维数组的例子,可以参考 multi_array.rb 程序。该程序从创建一个数组 multiarr 开始,它包含两个不同的数组。第一个数组在 multiarr 索引 0 处,第二个数组在索 引 1 处: multiarr = [['one','two','three','four'],[1,2,3,4]]
  4. 4. 全面索引数组 你可以通过使用 for 循环来索引数组中的每一个元素来迭代数组中所有的元素。这个循环将 迭代两个元素:即在索引 0 和 1 的两个子数组: for i in multiar puts(i.inspect) end 这将打印出: ["one", "two", "three", "four"] [1, 2, 3, 4] 那么,你如何来索引这两个子数组中的所有项(字符串和整型值)呢?如果有项目数是确定的, 你可以为每一个子数组声明一个迭代变量,在不同的情况下,每一个变量将被赋值为相应索 引处的子数组。 如果有四个子数组槽,你可以如下使用四个变量来获取每一个子数组: for (a,b,c,d) in multiarr print("a=#{a}, b=#{b}, c=#{c}, d=#{d}n" ) end 迭代和循环 在一个 for 循环中的代码将执行某表达式中的每一个元素。语法可以概括为: for<one or more variables> in <expression> do <code to run> end 当你提供多个变量时,这些变量都将传递到 for..end 块中的代码中,就像你传递参数给一 个方法一般。例如,你可以假设 (a,b,c,d) 是参数,每次循环使用 multiarr 中的一行初始 化其中一个元素 : for(a,b,c,d) in multiarr print(“a=#{a},b=#{b},c=#{c},d=#{d}n”) end 我们将在下一章中再详细讨论 for 循环。 你也可以使用一个 for 循环来迭代每一个子数组中的所有项: for s in multiarr[0] multi_array2.rb
  5. 5. puts(s) end for s in multiarr[1] puts(s) end 上面的两种方式(复合迭代或者复合循环)都有两个要求:a)那就是你需要知道数组网格的行 数和列数;b)那就是每一个子数组中的包含的项目数是相等的。 为了能更灵活地迭代多维数组,你可以使用内嵌 for 循环。一个外部循环迭代每行(也就是子 数组),内循环迭代当前行中每一项。这种方式即使是在子数组拥有可变项数时也能很好的工 作: for row in multiarr for item in row puts(item) end end 数组索引 和数组一样(详见第三章),你可以使用负数从一个数组的尾端开始进行索引,-1 是最后一 个元素,同样你也可以使用区间符: array_index.rb arr = ['h','e','l','l','o',' ','w','o','r','l','d'] print( arr[0,5] ) #=> „hello‟ print( arr[-5,5 ] ) #=> „world‟ print( arr[0..4] ) #=> „hello‟ print( arr[-5..-1] ) #=> „world‟ 注意,和字符串一样,当你提供两个整型数来返回数组中一组邻近的项时,第一个整型数是 索引的起始值,第二个值是需要索引的项目总数(而不是索引值): arr[0,5] # returns 5 chars - ["h", "e", "l", "l", "o"] array_assign.rb
  6. 6. 你也可以通过数组索引来给数组赋值。例如,我先创建了一个空数组,然后分别给索引值 0,1,3 处赋值。在索引 2 处的空槽将填充为 nil 值: arr = [] arr[0] = [0] arr[1] = ["one"] arr[3] = ["a", "b", "c"] # arr now contains: # [[0], ["one"], nil, ["a", "b", "c"]] 你也可以使用起始索引值,区间符和负索引等等: arr2 = ['h','e','l','l','o',' ','w','o','r','l','d'] arr2[0] = 'H' arr2[2,2] = 'L', 'L' arr2[4..6] = 'O','-','W' arr2[-4,4] = 'a','l','d','o' # arr2 now contains: # ["H", "e", "L", "L", "O", "-", "W", "a", "l", "d", "o"] 数组拷贝 array_copy.rb 注意当你使用赋值操作符=给一个数组变量赋值为另一个数组时,你实际上是将其引用赋值 给了另一个数组,你并没有进行拷贝。你可以似乎用 clone 方法来创建一个数组的拷贝: arr1=['h','e','l','l','o',' ','w','o','r','l','d'] arr2=arr1 # arr2 现在和 arr1 是一模一样的,修变 arr1 的值也会同时修改 arr2 arr3=arr1.clone # arr3 是 arr1 的一个拷贝,修改 arr1 和 arr2 的值并不会影响它的值
  7. 7. 判断数组相等 array_compare.rb 关于比较符<=>不需要多说。它会比较两个数组,假设是 arr1 和 arr2,如果 arr1 比 arr2 小,则返回-1,如果 arr1 等于 arr2 返回 0,如果 arr1 大于 arr2 返回 1。但是 Ruby 怎么 判断一个数组大于还是小于另一个数组呢?它是将数组中每一项依序和另一个数组中对应索 引处的值进行比较。当两项值不一样的时候,返回他们比较的结果。也就是说,如果是下面 的这个比较: [0,10,20]<=>[0,20,20] 该比较将返回-1(数组 1 小于数组 2),因为在数组 1 在索引值 1 处的值较小(10)而在数组 2 同样的索引处数组 2 的值为 20。 如果你需要比较字符串数组的大小,那么比较是针对 ASCII 码进行的。如果一个数组比另一 个数组长,在两个数组中的值都是相等的,那么长的数组将被认为是较大的。然而,如果两 个数组中,短数组中的一个元素大于常数组中相应索引处的值,那么短数组将被认定是较大 的。 数组排序 array_sort.rb sort 方式使用比较符<=>比较相邻元素的大小。Ruby 中大部分内置类都定义了该操作符, 包括 Array,String,Float,Date 和 Fixnum。然而 sort 操作并没有被所有的类定义(也就是说, 所有类都继承的 Object 类没有定义该方法)。所以有一个不幸后果就是,含有 nil 值的数组 不能使用 sort 方法。但是,你可以通过定义自己的 sort 规则来绕过这个限制。通过在 sort 方法中加入一段代码来实现,我们将在第十章详细讨论该方法块。现在,你只需要知道这一 大块是 sort 方法用来判断比较值的代码就足够了。 这是我定义的 sort 规则: arr.sort{ |a,b| a.to_s<=>b.to_s } 这里的 arr 是一个数组对象,变量 a 和 b 代表了两个相邻的数组元素。我在程序中使用了 to_s 方法将所有的变量值都转化为字符串了,其中 nil 会被转化为空字符串,它将会被排在 最低位。注意,虽然我的排序块中定义了数组项的排列顺序,但是这不会影响数组中实际存
  8. 8. 放的项。所以 nil 依然是 nil 值,整型数也将依然是整型数.字符串转化只是用来做比较的, 而不会改变任何数组项。 值比较 比较符<=>(实际上是一个方法)定义在 Ruby 的 Comparable 模块中。现在,你可以想象一 个模块就是一种可重用的“代码库”。我们将在第十二章中再深入讨论模块。 你可以在你自己的类中包含这个 Comparable 模块。当你包含之后,你就可以重载<=>操 作符来定义两个指定类型的对象之间的比较方式了。例如,你可能想改变数组比较的方式, 你通过对两个数组的长度进行比较而不是对数组中的每一项进行比较(这是默认的,和之前 我们了解的一样)。现在我们就可以这么做: comparisons.rb class MyArray < Array include Comparable def <=> ( anotherArray ) self.length <=> anotherArray.length end end 现在你可以创建两个这样的数组: myarr1 = MyArray.new([0,1,2,3]) myarr2 = MyArray.new([1,2,3,4]) 然后你可以使用在 MyArray 中定义的<=>方法来作比较: myarr1 <=> myarr2 #returns 0 返回值为 0 也就意味着这两个数组是相等的(因为我们的<=>方法只是单独对长度是否相等 进行了比较)。如果,另一方面,我们使用同样的值和方式创建两个标准的 Array 对象, Array 类的<=>方法将执行下面的比较: arr1 <=> arr2 #returns -1 返回值为-1 表示第一个数组小于第二个数组,因为标准 Array 类的<=>方式是对每一个项 的数值进行比较的,而数组 arr1 中的项的值与 arr2 中同样索引处的值相比要小。 如果你想将小于,等于和大于比较符用传统程序语言中的标志来表示的话:
  9. 9. < #less than == #equal to > #greater than 在 myArray 类中,我们可以不新增任何代码,使用这种比较方法。这个取决于 MyArray 类 导入的这个 Comparable 模块,这个模块自动提供了这三种比较方法,每一种比较方法都 是基于其<=>方法的定义。因为我们的<=>符号是基于数组项的值进行比较的,<方法返 回 true 当第一个数组长度小于第二个数组,==返回 true 当第一个数组长度等于第二个数 组,>返回 true 当第一个数组长度大于第二个数组。 p(myarr1 < myarr2) #=>false p(myarr1 == myarr2) #=>true 标准 Array 类,并没有包含 Comparable 模块,所以如果你想使用<,==,或者>来比较两个 一般数组,Ruby 将会打印错误信息提示你该方法没有定义。 不过我们可以很容易地将这三个方法添加到一个 Array 的子类中。你所要做的就是在类定义 中包含 Comparable 模块就好了,如下: class Array2 < Array Include Comparable end Array2 类现在可以基于 Array 类的<=>方法来执行它的比较,那就是,通过比较数组中各 项值而不再只是对数组的长度进行比较。假设有两个 Array2 对象,arr1 和 arr2,使用之前分 别在 myarr1 和 myarr2 中使用的值进行初始化,我们将看到这样的结果: p(arr1 < arr2) #=>true p(arr1 > arr2) #=>false 数组方法 array_methods.rb 标准 Array 类的一些方法是修改其自身对象,而不是返回一个对象修改后的拷贝。这不仅包 含那些以感叹号结尾的方法如 flatten!和 compact!,还有就是<<方法,该方法将其后面的数 据添加到左边的数组右面,clear 方法将删除数组中所有的元素,delete 和 delete_at 方法 删除选中的元素。
  10. 10. 哈希 虽然数组提供了一种很好的通过数字来索引一个集合中的项,但是有的时候使用一些其他的 方式可能会更为方便一些。例如,你将要创建一个菜谱的集合,这样的话,通过像”Rich Chocolate Cake”和”Coq au Vin”这样的名字来索引会显得更有意义,而不应该通过像 23,87 等这样的数字来索引。 Ruby 有一个类可以让你这样做。类名是 Hash。这和其他语言中的 Dictionary 是一样的。 和真实的字典一样,实体是通过一些唯一的键来索引的(在一个字典中,这些可能是一个单 词),这些键都关联着一个值(在字典中,这可能是这个单词的定义)。 创建哈希 和数组一样,你可以通过新建一个 Hash 类的实例来创建一个对象: hash1.rb h1 = Hash.new h2 = Hash.new(“Some kind of ring”) 上面的两个例子都创建了一个空的 Hash 对象。一个 Hash 对象总是会有一个默认值,就是 当指定索引处没有值的时候返回的值。在这个例子中,h2 就是通过默认值初始化的, ”Some kind of ring”;h1 没有使用值来初始化,所以它的默认值为 nil。 既然已经创建好了一个 Hash 对象,你可以像使用数组一样的来向其中添加项,通过在方括 号中的索引值来索引,使用=来赋值。这里最明显的差别就是,数组的索引(键)必须是整型 数而哈希的键必须是一个唯一的数据项: h2['treasure1'] = 'Silver ring' h2['treasure2'] = 'Gold ring' h2['treasure3'] = 'Ruby ring' h2['treasure4'] = 'Sapphire ring' 唯一的键值? 通常,键值可能会是一个数字或者跟上面代码中的一样,是一个字符串。原则上来说,一个 当你在给 Hash 赋值的时候一定要注意键值。如果你在一个哈希中两次使用了同一个键 键值可以是任何类型的对象。 值,最后你会重写原来的值。这就像你给数组中的同一索引处赋两次值一样。看看下面 这个例子: h2['treasure1'] = 'Silver ring' h2['treasure2'] = 'Gold ring' h2['treasure3'] = 'Ruby ring' h2['treasure1'] = 'Sapphire ring' 在这里 'treasure1' 使用了两次。结果,原来的值 'Silver ring' 将会被 'Sapphire ring' 替代,最后这个哈希的结构如下: {"treasure1"=>"Sapphire ring", "treasure2"=>"Gold ring", "treasure3"=>"Ruby ring"}
  11. 11. 假设有一个类 X,下面的赋值完全是合法的: x1 = X.new('my Xobject') h2[x1] = 'Diamond ring' 还有一中创建并使用值对初始化哈希的快捷方式。那就是在花括号中,使用键值后跟=>然 后是其关联的数据这样的键值对,每个键值对间使用逗号分隔: h1 = { 'room1'=>'The Treasure Room', 'room2'=>'The Throne Room', 'loc1'=>'A Forest Glade', 'loc2'=>'A Mountain Stream' } 哈希的索引 通过在中括号中放置键值来访问一个值: puts(h1['room2']) #=>'The Throne Room' 如果你指定了一个不存在的键值的话,将会返回默认值。再次强调,我们没有为 h1 指定默 认值,但是我们你给 h2 指定了: p(h1['unknown_room']) #=> nil p(h2['unknown_treasure']) #=> 'Some kind of ring' 使用 default 方法可以获得哈希的默认值,使用 default=方法可以设置默认值(详见第二章 的 get 和 set 访问器方法): p(h1.default) h1.default = 'A mysterious place' 哈希的拷贝 hash2.rb
  12. 12. 和数组一样,你可以将一个 Hash 变量赋值给另一个,这样的话,两个变量将指向同一个 Hash 对象,改变其中任何一个将影响到另一个的值: h4 = h1 h4['room1']='A new Room‘ puts(h1['room1'] #=>'A new Room' 如果你想这两个变量指向不同 Hash 对象中的相同的项,可以使用 clone 方法创建一个新的 拷贝: h5 = h1.clone h5['room1'] = 'An even newer Room' puts(h1['room1']) #=> „A new room' (i.e. its value is unchanged) 哈希的排序 hash_sort.rb 和 array 类一样,你会发现 Hash 的 sort 方法也有一点小问题。当键值的类型相同时没有问 题,但是如果你现在需要合并两个哈希,一个使用整型数作为键值,而另一个使用字符串, 你将无法对这个合并的哈希进行排序。这个问题的解决方法和数组一样,写一个自定义的比 较传递给 sort 方法。你可以给它传递一个方法,如下: def sorted_hash( aHash ) return aHash.sort{ |a,b| a.to_s <=> b.to_s } end 这个排序的执行是基于该哈希每个键值的字符串表示进行的。实际上,哈希的 sort 方法将 哈希转化为了一个[keys, value]数组的内嵌数组,然后使用 Array 类的 sort 方法进行排序。 哈希方法 hash_methods.rb Hash 类拥有非常多的内置方法。例如通过 aHash.delete(somekey)使用它的键值 (somekey)来删除哈希中的某个项。通过 aHash.has_key?(somekey)和 aHash.has_value?
  13. 13. (somevalue)来测试一个哈希中是否包含一个键值或者值。通过 aHash.invert 来创建一个使 用原有哈希键值作为值,原有值作为键值的反转哈希对象。使用 aHash.keys 和 aHash.values 返回一个哈希键值或者值的数组,等等。 hash_methods.rb 程序中演示了很多这样的方法。 深入探讨 hash_ops.rb Hash 类的 keys 和 values 方法均返回一个数组,所以你可以使用很多的 Array 方法来操纵 它们。这里有一些简单的例子: h1 = {'key1'=>'val1','key2'=>'val2','key3'=>'val3','key4'=>'val4'} h2 = {'key1'=>'val1','KEY_TWO'=>'val2','key3'=>'VALUE_3', 'key4'=>'val4'} p( h1.keys & h2.keys ) # set intersection (keys) #=> ["key1", "key3", "key4"] p( h1.values & h2.values ) # set intersection (values) #=> ["val1", "val2", "val4"] p( h1.keys+h2.keys ) # concatenation #=> [ "key1", "key2", "key3", "key4", "key1", "key3", "key4", "KEY_TWO"] p( h1.values-h2.values ) # difference #=> ["val3"] p( (h1.keys << h2.keys) ) # append #=> ["key1", "key2", "key3", "key4", ["key1", "key3", "key4", "KEY_TWO"]] p( (h1.keys << h2.keys).flatten.reverse ) # „un-nest‟ arrays and reverse
  14. 14. #=> ["KEY_TWO", "key4", "key3", "key1", "key4", "key3", "key2", "key1" 追加和连接 一定要注意使用+号将第二个数组中的值加到第一个数组中,和使用<<追加第二个数组到 第一个数组中最后面的区别: append_concat.rb a =[1,2,3] b =[4,5,6] c=a+b #=> c=[1, 2, 3, 4, 5, 6] a=[1, 2, 3] a << b #=> a=[1, 2, 3, [4, 5, 6]] 另外<<修改了第一个数组(接收器),而+返回一个新的数组,而保持原有数组不变。 接收器,消息和方法 在面向对象的术语中,方法从属于的对象称为接收器。这个是用来代替过程化语言中”方法 调用”的,”消息”将传递给对象。例如,消息 +1 应该被发送给一个整型对象,而 reverse 应该发送给一个字符串对象。收到消息的对象将尝试去找到与该消息相对应的方法。一个字 符串对象,例如,有一个 reverse 方法,所以它能对 reverse 消息作出响应,而整型对象没 有这样的方法,它将无法响应该消息。 如果需要,在使用<<将一个数组追加到一个接收器后,你可以使用 flatten 方法来将其追加 数组的项加入接收器数组中,而不是任其默认将追加数组作为一个内嵌数组追加到接收器中: a=[1, 2, 3, [4, 5, 6]] a.flatten #=> [1, 2, 3, 4, 5, 6] 矩阵和向量 Ruby 提供了一个 Matrix 类,该类中可以包含行和列,每一个行和列可以看做是一个向量 (vector,Ruby 也提供了一个 Vector 类)。Matrices 允许你执行矩阵计算。例如,有两个 matrix 对象 m1 和 m2,你可以使用+号来将两个对象对应处单元的值进行相加: matrix.rb m3 = m1 + m2
  15. 15. 集合 Set 类实现了一个无重复无序值的集合。你可以使用一个数组来初始化一个 Set(集合),但是 将忽略其中的重复元素: 示例: s1 = Set.new( [1,2,3, 4,5,2] ) s2 = Set.new( [1,1,2,3,4,4,5,1] ) s3 = Set.new( [1,2,100] ) weekdays = Set.new( %w( Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday ) ) 你可以使用 add 方法来添加新值: s1.add(1000) merge 方法可以用于合并两个 set: s1.merge(s2) 你可以使用==来判断两个 set 是否相等。两个包含相同值的 set(记住当 set 被创建的时候会 删除重复的元素)被认为是相等的: p(s1 == s2) #=>true

×