Ruby 集合指南(2):Hashes、Sets 和 Ranges

来源:互联网 发布:打电话l录音软件 编辑:程序博客网 时间:2024/06/18 13:36

The first article in this series focused on Array and the basics of idiomatic Ruby iteration. Array is a pretty flexible class, but there are better solutions for particular situations. This article covers some of the other collection types that ship with Ruby.

Hashes

Sometimes you need to map one value to another. For example, you might want to map a product ID to an array containing information about that product. If the product IDs were all integers, you could do this with Array, but at the risk of wasting a lot of space in between IDs. Ruby hashes function as associative arrays where keys are not limited to integers. They are similar to Python’s dictionaries.

译者信息

第一篇文章中都是关注数组和Ruby中通用的迭代方式。数组是一个漂亮和优雅的类,但对于一些特殊情况就要更好的解决方案。这篇文章包含了Ruby中一些其它的集合类型。

哈希表

有时你需要将一个数映射到另一个上。比如,你可能想要映射一个产品ID到一个包含产品信息的数组中。如果产品ID都是整形,你可以用数组来处理,但是这样可能在ID中要浪费更多的空间。Ruby哈希表函数可以关联一个键值不一定是整形的数组。

Creation

Like arrays, hashes have both literal and constructor initialization syntax.

>> colors = {}>> colors['red'] = 0xff0000>> colors = Hash.new>> colors['red'] = 0xff0000

As with arrays, it’s possible to create a hash with starting values. This is where we get to see the idiomatic => (“hash rocket”) operator.

>> colors = {>>   'red' => 0xff0000,>>   'blue' => 0x0000ff>> } => {"red"=>16711680, "blue"=>255}

If you try to access a hash key with no value, it will return nil. You can change this default value by passing an argument to the constructor.

>> h = Hash.new>> h[:blah]=> nil>> h = Hash.new(0)>> h[:blah]=> 0

Note that accessing a non-existent key does not create it.

>> h = {}>> h.size=> 0>> h[:blah]>> h.size=> 0

Deletion

If you want to remove a key-pair from a hash, you can use#delete. It might be tempting to simply set the value at the key to nil like in Lua tables, but the key will still be part of the hash and thus included in iteration.

>> colors['red'] = nil>> colors.size=> 2>> colors.delete('red')>> colors.size => 1

Iteration

Hashes are iterated like Arrays, except two values are passed to blocks instead of one.

>> hash = {"Juan" => 24, "Isidora" => 35}>> hash.each { |name, age| puts "#{name}: #{age}" }Juan: 24Isidora: 35

Block variables likenameandagein the previous example are just placeholders. They are arbitrary and could be anything, though it is good practice to make them descriptive.

译者信息

创建

类似于数组,Hash既具有文本初始化语法,也具有构造器初始化语法。

>> colors = {}>> colors['red'] = 0xff0000>> colors = Hash.new>> colors['red'] = 0xff0000

同数组一样,用初始值创建Hash也是可行的。这时你将看到常用的=>("hash火箭“)操作符。

>> colors = {>>   'red' => 0xff0000,>>   'blue' => 0x0000ff>> } => {"red"=>16711680, "blue"=>255}

如果你试图对一个没有值的Hash键进行访问的话,将返回为nil(无值)。你可以通过给构造器传递一个参数更改默认的这个值。

>> h = Hash.new>> h[:blah]=> nil>> h = Hash.new(0)>> h[:blah]=> 0

注意:对一个根本不存在的键的访问不能创建这个键对应的值。

>> h = {}>> h.size=> 0>> h[:blah]>> h.size=> 0

删除

如果你打算删除hash里的键值对的话,你可以使用#delete。类似于Lua表,仅仅设置要删除键对应的值为nil可能是很吸引人的,不过这个键仍然是这个Hash的一部分,因此还包含在循环的迭代里。

>> colors['red'] = nil>> colors.size=> 2>> colors.delete('red')>> colors.size => 1

迭代

像数组一样,Hash也可以循环迭代,只不过它传递给处理块的是两个值而不是一个值。

>> hash = {"Juan" => 24, "Isidora" => 35}>> hash.each { |name, age| puts "#{name}: #{age}" }Juan: 24Isidora: 35

上面例子中的块变量name和age只是占位符。它们是随意的,而且可以是任意值,不过给它们赋予意义是很好的做法。

To Hash Rocket or Not to Hash Rocket

It’s popular to use symbols as the Hash keys because they are descriptive like strings but fast like integers.

>> farm_counts = {>>   :cow => 8,>>   :chicken => 23,>>   :pig => 11,>> }=> {:cow=>8, :chicken=>23, :pig=>11}

Starting with Ruby 1.9, hashes whose keys are symbols can be built sans hash rocket (=>), looking more like JavaScript or Python.

>> farm_counts = {>>   cow: 8,>>   chicken: 23,>>   pig: 11>> }=> {:cow=>8, :chicken=>23, :pig=>11}

Both styles are common, but one thing to keep in mind is that all other key types still use a hash rocket, so using a colon instead in one part of your code might throw newcomers off.

译者信息

用哈希表火箭(=>)或者不用哈希火箭

用一些标识来作为哈希表的键值很流行,因为它们被描述为字串但是和整形一样快。

>> farm_counts = {>>   :cow => 8,>>   :chicken => 23,>>   :pig => 11,>> }=> {:cow=>8, :chicken=>23, :pig=>11}

从Ruby1.9开始,哈希表的标识键值可以用哈希火箭(=>)来扫描,这个和JavaScript或Python比较相似。

>> farm_counts = {>>   cow: 8,>>   chicken: 23,>>   pig: 11>> }=> {:cow=>8, :chicken=>23, :pig=>11}

这两种风格都很常见,但是要记住其它类型的键值也可以用哈希火箭来用,所以用一个分号来代替一部分代码会让新人头大的。

Keyword Arguments With Hashes

Python provides the ability to call functions using keywords arguments With keywords, it isn’t necessary to pass arguments in a specific order or pass any particular arguments at all. Although Ruby technically does not provide keyword arguments, a hash can be used to simulate them. If a hash is the last argument in a method call, the curly braces can be left off.

>> class Person>>   attr_accessor :first, :last, :weight, :height>>   def initialize(params = {})>>     @first = params[:first]>>     @last = params[:last]>>     @weight = params[:weight]>>     @height = params[:height]   >>   end>> end>> p = Person.new(>>   height: 170cm,>>   weight: 72,>>   last: 'Doe',>>   first: 'John'>> )

Note thatparams = {}isn’t strictly necessary, but it protects your code from throwing anArgumentErrorif no argument is passed, and it makes the intended argument type clearer.

译者信息

使用Hash实现关键字参数传递

Python具有 使用关键字传递关键字参数的函数调用的能力,这时以特定的顺序传递参数或者传递特定的参数根本就不是必须的。虽然Ruby技术上不提供关键字参数,但是可以使用Hash模拟出这样的效果。如果Hash是一个方法调用的最后一个参数,那么花括号都可以 省略。
>> class Person>>   attr_accessor :first, :last, :weight, :height>>   def initialize(params = {})>>     @first = params[:first]>>     @last = params[:last]>>     @weight = params[:weight]>>     @height = params[:height]   >>   end>> end>> p = Person.new(>>   height: 170cm,>>   weight: 72,>>   last: 'Doe',>>   first: 'John'>> )
注意:Params={}严格的来说是不需要的,不过在没有参数传递的情况下,这样做可以保护代码,以免抛出参数错误这样的错误,而且 这也可以使所期望的参数类型更加的清晰。

Smaller Hashes with Array Fields

Someone got the bright idea of making a lighter hash out of the Array class.

$ gem install arrayfields>> require 'arrayfields'>> h = ArrayFields.new>> h[:lunes] = "Monday">> h[:martes] = "Tuesday">> h.fields=> [:lunes, :martes]>> h.values=> ["Monday", "Tuesday"]

I’m not ultra-familiar with the arrayfields gem or how it applies across different Ruby implementations, but it’s very popular on Ruby Toolbox, and if you’re going to be serializing a lot of Hash data, it’s probably worth checking out.

Sets

If you need a collection where the order does not matter, and the elements are guaranteed to be unique, then you probably want a set.

Unlike the other collection types, you must add a require statement to make use of the Set class.

>> require 'set'

Also, unlike Array and Hash, Set does not have any kind of special literal syntax. However, you can pass an Array toSet#new.

>> s = Set.new([1,2,3])=> #<Set: {1, 2, 3}>

Alternatively, you can useArray#to_set.

>> [1,2,3,3].to_set=> #<Set: {1, 2, 3}>

Set uses the<<operator like Array, but#addis used instead of#push.

>> s = Set.new>> s << 1>> s.add 2

To remove an element from a set, use the#deletemethod.

>> s.delete(1)=> #<Set: {2}>

As with Array,#include?can be used for membership testing.

>> s.include? 1=> false>> s.include? 2=> true

One of the useful features of Set is that it will not add elements that it already includes.

>> s = Set.new [1,2]=> #<Set: {1, 2}> >> s.add 2=> #<Set: {1, 2}>

Earlier I pointed out that Array can perform boolean operations. Naturally, Set can do these as well.

>> s1 = [1,2,3].to_set>> s2 = [2,3,4].to_set>> s1 & s2=> #<Set: {2, 3}>>> s1 | s2=> #<Set: {1, 2, 3, 4}>

It also can do exclusive-or operations with the^operator, unlike Array.

>> [1,2,3] ^ [2,3,4]=> NoMethodError: undefined method `^' for [1, 2, 3]:Array>> s1 ^ s2=> #<Set: {4, 1}>

译者信息

使用数组域创建更轻型的Hash

有人提出使用数组类创建更轻型的Hash这个特别明智的想法。

$ gem install arrayfields>> require 'arrayfields'>> h = ArrayFields.new>> h[:lunes] = "Monday">> h[:martes] = "Tuesday">> h.fields=> [:lunes, :martes]>> h.values=> ["Monday", "Tuesday"]
我不是非常熟悉数组域gem,或者不是非常熟悉数组域在不同的Ruby实现中如何使用的,不过在Ruby工具中,这么做非常流行, 如果你打算对许多Hash数据进行序列化,那么数组域就可能值得看看了。

Set

如果你需要一个不关心顺序的集合,而且要保证每个元素都是唯一,那么你可能需要set。

不像其他集合类型,你在使用Set类的时候必须添加require语句。

>> require 'set'

另外,也不同于数组和Hash,Set没有任何特别的文本初始化语法。不过,你可以给Set的new方法传递数组。

>> s = Set.new([1,2,3])=> #<Set: {1, 2, 3}>

另外,你还可以使用数组的to_set方法。

>> [1,2,3,3].to_set=> #<Set: {1, 2, 3}>

类似于数组,Set也可以使用<<操作符,不过这个时候不能使用push方法了,而要使用add方法。

>> s = Set.new>> s << 1>> s.add 2

要删除Set的一个元素,请使用delete方法。

>> s.delete(1)=> #<Set: {2}>

同数组一样,也可以使用#include?方法测试是否是Set的成员。

>> s.include? 1=> false>> s.include? 2=> true

Set有一个很有用的功能是它不能添加已经包含的元素。

>> s = Set.new [1,2]=> #<Set: {1, 2}> >> s.add 2=> #<Set: {1, 2}>

以前我已经提到数组可以执行布尔操作,很自然,Set也可以这么做。

>> s1 = [1,2,3].to_set>> s2 = [2,3,4].to_set>> s1 & s2=> #<Set: {2, 3}>>> s1 | s2=> #<Set: {1, 2, 3, 4}>

与数组不同的是,它还可以使用^操作符执行异或操作。

>> [1,2,3] ^ [2,3,4]=> NoMethodError: undefined method `^' for [1, 2, 3]:Array>> s1 ^ s2=> #<Set: {4, 1}>

Ranges

I pointed out Ranges before in part I. The Range class is a sort of quasi-collection. It can be iterated like the other collections that use Enumerable, but it’s not a container for arbitrary elements.

>> r = Range.new('a', 'c')=> 'a'..'c'>> r.each { |i| puts i }abc

Before, I showed that Ranges can slice arrays or produce indices for iterating through them.

>> letters = [:a,:b,:c,:d,:e]>> letters[1..3]=> [:b, :c, :d]>> (1..3).map { |i| letters[i].upcase }=> [:B, :C, :D]

In addition to slicing arrays, Ranges can simplify case statement logic.

>> def theme(year)>>   case year>>     when 1970..1979 then "War Bad, Black People Not Bad">>     when 1980..1992 then "Cocaine, Money, and The Future">>     when 1993..2000 then "Gillian Anderson, Sitcoms in The FriendZone, and AOL">>     when 2000..2013 then "RIP, Music">>   end>> end>> theme(1987)=> "Cocaine, Money, and The Future"

There is also this stackoverflow question about generating random strings which got some answers that put ranges to good use.

>> (0...10).map{ ('a'..'z').to_a[rand(26)] }.join=> "vphkjxysly"

Conclusion

That covers Hashes, Sets, and Ranges. It the next post, I’ll discuss Enumerable, Enumerator, and the neat things you can do with such tools.

译者信息

Range

在第一部分我曾经提到过Range。Range类是准集合的一种。它可以像其他使用Enumerable的集合那样进行迭代,然而它不是可以存储任意元素的容器。

>> r = Range.new('a', 'c')=> 'a'..'c'>> r.each { |i| puts i }abc

以前,我展示了Range可以用来对数组分片或者生成用于迭代的索引。

>> letters = [:a,:b,:c,:d,:e]>> letters[1..3]=> [:b, :c, :d]>> (1..3).map { |i| letters[i].upcase }=> [:B, :C, :D]

除了对数组进行分片之外,Range还可以简化case语句的逻辑。

>> def theme(year)>>   case year>>     when 1970..1979 then "War Bad, Black People Not Bad">>     when 1980..1992 then "Cocaine, Money, and The Future">>     when 1993..2000 then "Gillian Anderson, Sitcoms in The FriendZone, and AOL">>     when 2000..2013 then "RIP, Music">>   end>> end>> theme(1987)=> "Cocaine, Money, and The Future"
为了得到如何充分利用Range的答案,可以看看stackoverflow上的这个自动生成随机字符串的问题。
>> (0...10).map{ ('a'..'z').to_a[rand(26)] }.join=> "vphkjxysly"

总结

这篇文章概述了Hash、Set和Range。在下一篇博客日志里,我将讨论Enumberable,Enumberator和使用这样的工具你可以做的出色的

原创粉丝点击