16.在改变作为参数的集合之前复制它们
17.使用Array方法将nil及标量对象转化成数组
18.考虑使用集合高效检查元素的包含性
19.了解使用通过reduce方法折叠集合
20.考虑使用默认hash值
21.对集合优先使用委托而非继承
16.在改变作为参数的集合之前复制它们
多数对象传递是通过引用而非实际值来进行传递的(Integer是通过值来进行传递),如果使用引用来进行传递,任何一个拥有该对象的引用的代码都可以完成对这个对象的修改。
class Tuner
def initialize(presents)
@presents = presents
clean
end
private
def clean
@presents.delete_if{|f| f[-1].to_i.even?}
end
end
presents = %w(90.1 106.2 88.5)
tuner = Tuner.new(presents)
p presents #["90.1", "88.5"]
注意上面的代码,delete_if会改变原始值,还包括类似的方法,reject!方法,因此可以使reject方法不改变原始值,但是返回改变后的值。
#delete_if,reject!方法会改变原始值,reject不会改变原始值,但是返回改变后的值
presents = %w(90.1 106.2 88.5)
presents.reject{|c| c[-1].to_i.even? } #["90.1", "88.5"]
presents #["90.1", "106.2", "88.5"]
presents = %w(90.1 106.2 88.5)
presents.reject!{|c| c[-1].to_i.even? } #["90.1", "88.5"]
presents #["90.1", "88.5"]
presents = %w(90.1 106.2 88.5)
presents.delete_if!{|c| c[-1].to_i.even? } #["90.1", "88.5"]
presents #["90.1", "88.5"]
使用reject方法对上面的代码进行改进
class Tuner
def initialize(presents)
@presents = clean(presents)
end
private
def clean(presnets)
presents.delete_if{|f| f[-1].to_i.even?}
end
end
presents = %w(90.1 106.2 88.5)
tuner = Tuner.new(presents)
p presents #["90.1", "106.2", "88.5"],没有改变传递进来的变量
实际上,如果用参数的副本来传递变量更为合理
class Tuner
def initialize(presents)
@presents = presents.dup
clean
end
private
def clean
@presents.delete_if{|f| f[-1].to_i.even?}
end
end
presents = %w(90.1 106.2 88.5)
tuner = Tuner.new(presents)
p presents #["90.1", "106.2", "88.5"]
上述代码也是存在问题,因为如果对于Array元素进行复制,仅仅只是复制了容器本身,而不会复制里面的元素,即是对元素的增减是不会互相影响的,如果是改变元素的本身,则会互相影响,详细见赋值和拷贝中内容叙述。
17.使用Array方法将nil及标量对象转化成数组
这个章节的内容是使用Array()方法将其他一些对象转化成数组
#代码示例
class Pizza
#在下面的方法中,是预期toppings按照数组的对象来进行操作,这里可能是nil对象,所以会出错
def initialize(toppings)
toppings.each do |topping|
add_some_method(topping)
end
end
end
下面的是一些其他对象通过Array()方法转化成数组
#数组返回本身
Array(["hello", "world"]) #["hello", "world"]
#nil返回空数组
Array(nil) #[]
#hash返回二维数组
demo = Array({hello: "world", thank: "you"}) #[[:hello, "world"], [:thank, "you"]]
#通过Hash[二维数组]的形式生成hash
Hash[demo] #{hello: "world", thank: "you"}
#range会展开为数组
Array(1..5) #[1,2,3,4,5]
#string对象返回单个数组对象
Array("demo") #["demo"]
18.考虑使用集合高效检查元素的包含性
使用Array的include?方法来计算时间复杂度,其是O(n), 使用Hash来计算时间复杂度是O(log n),对比下面的计算式
#使用Array#include?
class Role
def initialize(name, permissions)
@name, @permissions = name, permissions
end
def can?(permission)
@permissions.include?(permission)
end
end
#使用Hash的include?
class Role
def initialize(name, permissions)
@name = name
#其中value值是true, 是一个不可变的全局变量
@permissions = Hash[permissions.map{ |p| [p, true]}]
end
def can?(permission)
@permissions.include?(permission)
end
end
Set可以由任何集合对象或枚举器来构建,实现也是基于hash,使用include的时间复杂度是O(log n),使用方式如下
#使用Set的include?
class Role
def initialize(name, permissions)
@name,@permissions = name, Set.new(permissions)
end
def can?(permission)
@permissions.include?(permission)
end
end
Set基于hash实现,如果将对象放置在Set中,需要和hash一样,实现eql?方法和hash方法。
#代码示例
require 'set'
require 'csv'
class AnnualWeather
Reading = Struct.new(:date, :high, :low) do
def eql? other
date.eql? other.date
end
def hash
date.hash
end
end
def initialize(file_name)
@readings = Set.new
CSV.foreach(file_name, headers: true) do |row|
@readings << Reading.new(Date.parse(row[2]), row[10].to_f, row[11].to_f)
end
end
end
19.了解使用通过reduce方法折叠集合
reduce方法
20.考虑使用默认hash值
如果hash没有默认值的情况下,如果访问不存在key时,返回nil,如果添加了hash的默认值,则返回默认值
#没有默认值
a = {}
a["demo"] #nil
a = Hash.new(0)
a["demo"] #0
#没有默认值的情况下
array = [1,2,3]
def frequency(array)
array.reduce({}) do |hash, element|
hash[element] ||= 0
hash[element] += 1
hash
end
end
p frequency(array) #{1=>1, 2=>1, 3=>1}
#使用默认值的情况下使用
array = [1,2,3]
def frequency(array)
array.reduce(Hash.new(0)) do |hash, element|
hash[element] += 1
hash
end
end
p frequency(array) #{1=>1, 2=>1, 3=>1}
上述的代码中出现“+=”,如果在hash存在默认值的情况下,使用的时候会出现多余的hash值,+=的扩展如下所示
#+=的扩展
h[:missing_key] += 1
h[:missing_key] = h[:missing_key] + 1
h = Hash.new(42)
h[:missing_key] #42
h.keys #[]
h[:missing_key] += h[:missing_key] #43
h.keys #[:missing_key]
1.上面的是使用Integer值作为hash的默认值
#hash使用Integer作为默认值,Integer是不可变的
a = Hash.new(0)
a["demo"] #0
2.使用数组[]作为hash的默认值
#使用[]作为默认值,[]值是可以改变的
a = Hash.new([])
a["demo"] #[]
#Hash.new使用空数组作为默认值,这其实是对可变数组的引用,a["demo"]返回的是对这个可变数组的引用
#如果默认值被修改了,当访问不存在的键时,返回的是修改后的值
a = Hash.new([])
a["demo"] << "hello"
a["cc"] #["hello"]
通过"<<"方式获得的hash会共享hash值
h = Hash.new([])
h[:weekdays] = h[:weekdays] << "monday"
h[:months] = h[:months] << "january"
h.keys #[:weekdays, :months]
h[:weekdays] #["monday", "january"]
h[:months] #["monday", "january"]
h.default ##["monday", "january"]
上面的代码示例中,通过<<方式产生的key, weekdays和months共享一个默认数组。
3.使用块作为hash的默认值
希望访问一个不存在的键时能返回一个全新的数组,那么选择默认值为块
h = Hash.new{[]}
h[:weekdays] = h[:weekdays] << "monday"
h[:months] = h[:months] << "january"
h[:weekdays] #["monday"]
h[:months] #["months"]
注意点:
1.如果设置hash生成的默认值,则hash[key]返回不是nil或者false
2.fetch用法
h = { "a" => 100, "b" => 200 }
#1.fetch有两个参数,如果第一参数没有值,则返回第二个参数的内容
h.fetch("a") #=> 100
h.fetch("z", "go fish") #=> "go fish"
#2.如果fetch的第二个参数为块,如果第一参数没有值,则返回块中内容,块参数为key值
h.fetch("z") { |el| "go fish, #{el}"} #=> "go fish, z"
#3.如果只有第一个参数,那么没有相应的key,则返回error
h.fetch("z") #key error
21.对集合优先使用委托而非继承
使用继承的最大的缺点是耦合,同时又有一个副作用调用reverse方法之后返回的不再是该子类的对象
class ListArray < Array
end
demo = ListArray.new([1,2,3])
demo.class #ListArray
obj = demo.reverse
obj.class #Array
使用代理的好处是对需要的方法开放,对不需要的对象不必使用undef method消除
class Demo
extend Forwardable
include Enumerable
def_delegators :@hash, :[], :[]=, :keys, :values, :length
def initialize
@hash = Hash.new do |hash, key|
raise KeyError, "invalid key #{key}"
end
end
def invert
other = self.class.new
#使用protected方法可以改变实例变量的值
other.replace!(@hash.invert)
other
end
def initialize_copy(other)
@hash = @hash.dup
end
#freeze, taint, untaint方法先传递消息给委托目标,之后调用super方法
def freeze
@hash.freeze
super
end
def taint
@hash.taint
super
end
def untaint
@hash.untaint
super
end
protected
def replace!(hash)
#保存default_proc到revert之后的对象中
hash.default_proc = @hash.default_proc
@hash = hash
end
end
demo = Demo.new
demo[:hello] = "world"
p demo[:hello]
p demo.invert
p demo.invert.class #Demo
使用回调方法来优化上面的代码
#定义SuperForwardable模块
module SuperForwardable
def self.extended(klass)
require 'forwardable'
klass.extend Forwardable
end
def def_delegator_with_super(target, *methods)
methods.each do |method|
target_method = "#{method}_without_super".to_sym
def_delegator(target, method, target_method)
define_method(method) do |*args, &block|
send(target_method, *args, &block)
super(*args, &block)
end
end
end
end
#重新打开RaisingHash类
class RaisingHash
extend SuperForwardable
def_delegators(:@hash, :[], :[]=)
def_delegators_with_super(:@hash, :freeze, :taint, :untaint)
def initialize
..
end
end