Containers,Blocks,and Iterators
arrays与hashes是Ruby的关键内置类,块语法用于对集合分层(pair with collections)等操作。
--4.1 Arrays--------
定义数组:a = [3.14, 'pie', 99],或者b = Array.new,然后就可以:a[x],b[i] = xx的读取或设值了,没有数组越界,操作的返回nil
[]是Ruby中的数组操作符,其实是Array类的方法,可接收负数,下图不错,描述了正负的内容情况:
注意:a[1..3]是从1开始,取三个值,a[1...3]从1到3,不包含3
还可以获取子数组:
a = [ 1, 3, 5, 7, 9 ]
a[1, 3] # => [3, 5, 7]
a[3, 1] # => [7]
a[-3, 2] # => [5, 7]
与 []方法对应的有一个[]=用于给数组设值
a = [ 1, 3, 5, 7, 9 ] #=> [1, 3, 5, 7, 9]
a[1] = 'bat' #=> [1, "bat", 5, 7, 9]
a[-3] = 'cat' #=> [1, "bat", "cat", 7, 9]
a[3] = [ 9, 8 ] #=> [1, "bat", "cat", [9, 8], 9]
a[6] = 99 #=> [1, "bat", "cat", [9, 8], 9, nil, 99]
注意第三个,3位置放的一个数组,第四个,如果下标超过了,超过部分都是nil填充。
如果[]=有两个参数,则表示一个开始与一个长度,用于将右面的替换到数组的指定位置,及占的长度,如果长度为0,则不替换,只从开始位置插入。
a = [ 1, 3, 5, 7, 9 ] #=> [1, 3, 5, 7, 9]
a[2, 2] = 'cat' #=> [1, 3, "cat", 9] 从2开始,占两个
a[2, 0] = 'dog' #=> [1, 3, "dog", "cat", 9]
a[1, 1] = [ 9, 8, 7 ] #=> [1, 9, 8, 7, "dog", "cat", 9],如果右面是数组,则插入多个,而不是整个数组。
a[0..3] = [] #=> ["dog", "cat", 9] 从0到3替换为空
a[5..6] = 99, 98 #=> ["dog", "cat", 9, nil, nil, 99, 98] 自动填充nil
数组有许多好用的方法,可用来把数组当成stacks,sets,queues,dequeues,FIFO queues
stack = []
stack.push "red"
stack # => ["red"]
stack.pop # => "blue"
stack # => []
unshift与shift方法用于添加和删除头元素,与push(向尾添加)结合使用,可实现FIFO
queue = []
queue.push "red"
queue.push "green"
queue.shift =>red
queue.shift =>green
还有first与last方法返回头或尾的n个元素,但是不删除
array = [ 1, 2, 3, 4, 5, 6, 7 ]
array.first(4) # => [1, 2, 3, 4]
array.last(4) # => [4, 5, 6, 7]
-----4.2 Hashes------------
hash的索引即key可以是任何类型:符号,字符串,正则表达式。
h = {'dog' => 'canine', 'cat' => 'feline'}
h['dd'] = 'bbbb' #添加元素
h[12] = 'sdfsf' #添加元素
#from Ruby 1.9以后,如果key是符合,可写成
h = {:dog => 'sdfsdf', :cat => 'sdfsfd'}
或
h = {dog : 'sdfsdf'}
相对数组,hash的优点是可使用任何类型作为下标索引,但数组只有是整数。另外,Ruby会记录下添加到hash的顺序,当迭代它时,会按照添加的顺序。
下面的示例用于从输入的句子中找出每个单词的出现次数
def words_from_string(string)
string.downcase.scan(/[\w']+/) #返回单词与'的数组
end
p words_from_string("But I didn't inhale, he said (emphatically)")
produces:
["but", "i", "didn't", "inhale", "he", "said", "emphatically"]
def count_frequency(word_list)
counts = Hash.new(0)
for word in word_list
counts[word] += 1
end
counts
end
p count_frequency(["sparky", "the", "cat", "sat", "on", "the", "mat"])
produces:
{"sparky"=>1, "the"=>2, "cat"=>1, "sat"=>1, "on"=>1, "mat"=>1}
require_relative "words_from_string.rb"
require_relative "count_frequency.rb"
raw_text = %{The problem breaks down into two parts. First, given some text
as a string, return a list of words. That sounds like an array. Then, build
a count for each distinct word. That sounds like a use for a hash---we can
index it with the word and use the corresponding entry to keep a count.}
word_list = words_from_string(raw_text)
counts = count_frequency(word_list)
sorted = counts.sort_by {|word, count| count}
top_five = sorted.last(5)
for i in 0...5 # (this is ugly code--read on
word = top_five[i][0] # for a better version)
count = top_five[i][1]
puts "#{word}: #{count}"
end
produces:
that: 2
sounds: 2
like: 2
the: 3
a: 6
#sort_by: The result of the sort is an array containing a set of two-element arrays, with
each subarray corresponding to a key/entry pair in the original hash。
排序后:{[word, count], {word, count}}
---------Test::Unit-----------
Ruby的标准包中已经包含。与java一样,以test为前缀的方法都会被认为测试方法。
----------4.3 Blocks and Iterators-----------------
上面的:
for i in 0..4
word = top_five[i][0]
count = top_five[i][1]
puts "#{word} : #{count}"
end
使用块与迭代器
top_five.each do |word, count|
puts "#{word} : #{count}"
end
或者
top_five.map {|word, count| puts "#{word} : #{count}"} #注意上面是each,这里是map
-------------Blocks------
sum
[1, 2, 3, 4].each do |value|
square = value * value
sum += square
end
puts sum
块内可直接访问块外的变量。对于变量名冲突的问题:解决办法有两个:
1、使用{}的块方法;2、do..end样式中在do |value;square|这种方式(square与块外冲突),这表示定义一个本地变量
--------Implementing Iterators实现迭代器--------
def two_times
yield
yield
end
two_times {puts "invoking"}
#传入参数
def fib_up_to(max)
i1, i2 = 1, 1
while i1 <= max
yield i1
i1, i2 = i2, i1+i2
end
end
fib_up_to(1000) {|f| print f, " "}
puts
Ruby的集合类型有多个迭代器:each, collect, find。each每次一个元素,当然我们自己定义的类也可以实现这些迭代器。
class Array
def find
each do |value|
return value if yield(value)
end
nil
end
end
p [1,3,5,7,9].find{|v| v*v>30}
collect同样是迭代一个数组,只是它会把代码块的返回结果用来构造一个新的数组。比如:
["H", "A", "L"].collect {|x| x.succ } # => ["I", "B", "M"]
迭代器不只是用来访问集合中存在的数据。下面的示例展示读取文件
f = File.open("testFile")
f.each do |line|
puts "the line #{line}"
end
f.close
如果需要记录调用块的次数,可使用each.with_inde,比如:
f = File.open("testFile")
f.eac.with_index do |line, index|
puts "#{index}, #{line}"
end
f.close
------来点儿干货,inject method(在Enumerable模块中定义)---
[1,3,5,7].inject(0) {|sum, element| sum+element} # => 16
[1,3,5,7].inject(1) {|product, element| product*element} # => 105
inject的参数值先设给sum或product,element是集合中的元素,第一次调用以后,sum值将变化为块代码的执行结果。这样块的执行结果设给sum。最后返回的也是这样值。如果inject没有参数,则使用集合的第一个元素为初始值(sum),然后元素从第二个开始。因为不可能再从1开始了,已经加了上吗!
[1,3,5,7].inject(:+) # => 16
[1,3,5,7].inject(:*) # => 105
--------------Enumerators-External Iterators-------------
a = [ 1, 3, "cat" ]
h = { dog: "canine", fox: "vulpine"
# Create Enumerators
enum_a = a.to_enum
enum_h = h.to_enum
enum_a.next # => 1
enum_h.next # => [:dog, "canine"]
enum_a.next # => 3
enum_h.next # => [:fox, "vulpine"]
调用each如果没有跟一个代码块,也是返回一个Enumerator类
a = [ 1, 3, "cat" ]
enum_a = a.each # create an Enumerator using an internal iterator
enum_a.next # => 1
enum_a.next # => 3
Enumerator与loop方法结合可以完成遍历所有元素并结。loop方法本来只是重复调用代码块,什么事情不做,但是当loop发现块中的变量是Enumerator对象时,会在遍历完成后自动结束(当Enumerator对象运行时迭代出了它的范围值,则loop会自动结束)。
short_enum = [1, 2, 3].to_enum
long_enum = ('a'..'z').to_enum
loop do
puts "#{short_enum.next} - #{long_enum.next}"
end
#当到4时,short_enum就会超出范围,loop自动结束。
produces:
1 - a
2 - b
3 - c
----------Enumerators Are Objects-------------
class File
def self.open_and_process(*args)
f = File.open(*args)
yield f
f.close()
end
end
File.open_and_process("testFile", "r") do |file|
while line = file.gets
puts line
end
end
#*args用于方法不关心参数,只是把它原样传给下面的方法
block_given?用于判断是否方法传入了块。
class ProcExample
def pass_block(&action)
@stored_proc = action
end
def exec_action(parameter)
@stored_proc.call(parameter)
end
end
example = ProcExample.new
example.pass_block {|parameter| puts "call parameter:#{parameter}"}
example.exec_action("abc")
代码块如果被方法直接返回给调用者,也将会以对象的形式可被调用,如下:
def create_block_object(&action)
action #直接返回块代码的对象
end
bo = create_block_object {|parameter| puts "#{parameter}"}
bo.call 99
bo.call "ltt"
Ruby为了提供这种主动转换的方式,还提供了两个方法:lambda与Proc.new
bo = lambda { |param| puts "You called me with #{param}"}
bo.call 99
-----代码块可以是闭包-----
def n_times(thing)
lambda {|n| thing*n}
end
p1 = n_times(23)
p1.call(3) #=>69
p2 = n_times("Hello ")
p2.call(3) #=>"Hello Hello Hello "
#lambda 这行返回的是Proc对象,它引用了thing。闭包:参数的生命周期与引用它的块的生命周期一致。
#下面的代码演示局部变量的保存
def power_proc_generator
value = 1
lambda { value += value }
end
power_proc = power_proc_generator
puts power_proc.call
puts power_proc.call
puts power_proc.call
produces:
2
4
8
----------------块代码的另外一种写法-----
lambda {|params| ...}
#可写dn
-> params {...}
e.g
proc1 = -> arg { puts "In proc1 with #{arg}" }
proc2 = -> arg1, arg2 { puts "In proc2 with #{arg1} and #{arg2}" }
proc3 = ->(arg1, arg2) { puts "In proc3 with #{arg1} and #{arg2}" }
proc1.call "ant"
proc2.call "bee", "cat"
proc3.call "dog", "elk"
#这种写法更适合将创建的Proc对象传入方法:
def my_if(condition, then_clause, else_clause)
if condition
then_clause.call
else
else_clause.call
end
5.times do |val|
my_if val < 2,
-> { puts "#{val} is small"},
-> { puts "#{val} is big"}
end
produces:
0 is small
1 is small
2 is big
3 is big
4 is big
把块传入到方法的原因之一是方法可以执行块在任何时候,任何次数。
def my_while(cond, &body) # &前缀表示接收块 cond接收的是Proc对象
while cond.call
body.call
end
end
a = 0
my_while -> { a < 3 } do
puts a
a += 1
end
produces:
0
1
2
#注意 -> params{...}这个用来创建Proc对象,而块的定义还是do..end或一行形式.
---------块的参数列表----
proc1 = lambda do |a, *b, &block|
puts "a = #{a.inspect}"
puts "b = #{b.inspect}"
block.call
end
proc1.call(1, 2, 3, 4) { puts "in block1" } #注意*b表示2,3,4
produces:
a = 1
b = [2, 3, 4]
in block1
使用->的写法
proc1 = -> (a, *b, &block) do
puts "a = #{a.inspect}"
puts "b = #{b.inspect}"
block.call
end
proc2.call(1, 2, 3, 4) { puts "in block2" }
produces:
a = 1
b = [2, 3, 4]
in block2
---------------------