Programming Ruby(读书笔记)-4章

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实现迭代器--------
Ruby的迭代器就是一方法,它可调用块代码。
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每次一个元素,当然我们自己定义的类也可以实现这些迭代器。
代码块也可以有返回值,即最后一行的执行结果。这个值会返回给yield。见下面的示例
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-------------
 Enumerator类是Ruby内置的实现了扩展的迭代器,可以通过在array中hash上调用to_enum或enum_for来生成迭代器。
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-------------
有待补充
------Enumerators Are Generators and Filters-----------
(This is more advanced material that can be skipped on first reading.)
有待补充
------Lazy Enumerators in Ruby 2 -----------
有待补充
------Blocks for Transactions ------
 
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?用于判断是否方法传入了块。
----------Blocks can be Objects---------
 块代码可作为对象使用,约定:如果方法的最后一个参数是&开始如(&action),则在运行这个方法时,ruby会把传入的块代码通过&action传入进来,然后我们可以通过一个实例变量保存它,这个实例变量的class类型是ruby内置的Proc类。然后,这个实例变量就和其它对象的引用一样了,可以在其它方法中调用(通过对象的call方法)。如下面代码:
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
 ---------------------
Containers,blocks,iterators是三个Ruby的核心概念。
 
 
 
 
 
 
 
 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值