LJZN

每天更新Rails练习项目到Github~

» Home
» Category
» About Me
» Github

(Why的Ruby书)第四章-一叶漂浮着的代码

18 Jan 2016 » why

Why the Lucky Stiff

今天我们跟着小精灵和他宠物金华火腿一起,去到他们生活的地方————安不螺丝洞穴。在那个世界里,通用的货币是蓝水晶。不过小精灵刚刚告诉我,因为蓝水晶太重不方便携带,所以货币已经改成树叶了。

1.安不螺丝世界里的货币

为了方便大家兑换新的货币,小精灵颁布了一项规定:一片树叶等价与五块蓝水晶。

blue_crystal = 1
leaf_tender = 5

在用Ruby编写程序时,完成对变量的赋值相当与完成了一半的工作。

小精灵说要给你介绍一下他最新的研究————动物补完计划,不过我认为你可能会感觉不适。

事情还要从一次意外说起。我曾有一匹很棒的赛马,她在比赛中获奖无数。有一天我骑着她出行,路过一条铁轨时她变得犹豫不决,在铁轨上徘徊着。无论我做什么都不能让她挪动一步。这时火车呜呜叫着飞速驶来,我只好在最后一秒钟跳下来。可怜的小马被斩断了四条腿,我只好把她拖回家。

小精灵和我开始在网上搜索治疗她的方法。最后,我们终于找到了答案。为马儿安上了闪亮的铁皮四肢。

她在手术完成之后立刻急匆匆地走了,后来在小精灵的洞穴世界里生活了许多年才死去。

动物补完计划已经成为了制造新动物和改造现有动物的最佳方法。但它还有很长的路要走。在这个计划刚开始的时候,效果是很差的。比如一只成年狗熊走进了实验室,出来时变成了一只带墨镜的成年狗熊。

四处看看你会发现一只带火箭背包的螃蟹。那是2004款的火箭螃蟹。

现如今情况已经大大改善了,你会发现这里的环境干净得出奇。所有东西的镀上了铬。并且都装备了武器。它们随时能够向入侵者射击,如果子弹用完了,他们还会用手枪抽打入侵者。

下面由小精灵展示一下星星猴的制造过程。

pipe.catch_a_star

这段代码可以解释为,告诉管道:捉住一颗星星。

告诉你在前半部分工作中定义的东西要做什么,这就是Ruby编程的后半部分工作。

1.定义东西

2.让这些东西运作起来

所以之后呢,被捉住的星星放哪?

captive_star = pipe.catch_a_star

这完全取决于你,如果你不把它们收集起来便会立刻消失不见。当你执行一个方法时,你总会收到一些返回值,你可以使用也可以忽略。

学会如何使用这些返回值,能帮助你支配一切。

接下来

starmonkey = ratchet.attach( captive_monkey, captive_star )

齿轮得到了一个命令:贴合。贴合什么?方法参数:被捉的猴子和被捉的星星。我们得到了一只星星猴!另外,小精灵还在它们的右手上加了一只青蛙。

starmonkey = ratchet.attach( captive_monkey, pipe.catch_a_star ) + deco_hand_frog

想看看管道是如何捉住星星的吗?跟我来。

2.简直毫无作用

安不螺丝的旅馆环境不太好。床上满是疙瘩,电梯小得可怜。我看到一个男人把行李放进电梯,就没有空间留给他自己了。他只好按下按钮再去爬楼梯。

这里的肥皂和电梯一样小得过分,所以我完全不能把手洗干净。我开始思考:种种迹象表明安不螺丝很可能是一个魔法世界。所以我闭上眼睛,口中念起咒语,幻想一股水流从我手中流过,当我睁开眼时手会变得干干净净。

惊人的是什么也没有发生。完全是nil。

nil

在Ruby中,nil代表没有。它没有值。它不是零。零是一个数字。

而它在Ruby世界中是如此流行。

plastic_cup = nil

塑料杯是空的。如果你学习过其它编程语言,你可能会说塑料杯是未定义的。在Ruby中一个变量未定义意味着它根本不存在。但在这里Ruby感觉到了塑料杯,它是空的但不是未定义的。

错的

在Ruby的世界中,每样东西都是具有正能量,除了两个关键词。nil和false,它们存在于黑暗的负空间中,让我们来做个实验证实一下:

if plastic_cup
  print "Plastic cup is on the up 'n' up!"
end

如果塑料杯里装的是nil或false,那么什么都不会打印在屏幕上。它们两不是if的客人。

这时nil和false觉得很沮丧,它们觉得自己是问题少年。但是unless接纳了它们。并且只接受它们两。

unless plastic_cup
  print "Plastic cup is on the down low."
end

你也可以在一句代码的结尾使用if或unless。

print "Yeah, plastic cup is up again!" if plastic_cup
print "Hardly. It's down." unless plastic_cup

这里有一个小技巧:把if和unless连着用。

print "We're using plastic 'cause we don't have glass." if plastic_cup unless glass_cup

意思是只在a条件为真且b条件为假时执行。

真的

approaching_guy = true

我今天在旅馆的桌子上遇到了true。他戴着贝壳项链,脸上是自信的表情。说实话,我不能忍受一个总是正确的人,true一直在说“A-OK.”

就像你猜测的那样,他的背包里有if的招待名单上所有的东西。 print “Hugo Boss” if true acts like print “Hugo Boss”.

if会对true说一些特别的话。

if approaching_guy == true
 print "That necklace is classic."
end

两个等号就像是一种身份验证。

以这种方式,你可以控制if接受的东西。如果你像我一样和true在一起那么久,你会打心里思念false。

if approaching_guy == false
  print "Get in here, you conniving devil."
end

一切都由你来决定。

你来统治

事实上,两个等号是一个方法。

approaching_guy.==( true )

让我们来试试

if nil.==( true )
  print "This will never see realization."
end

当等号两边不匹配时,它会返回false,而if不接受false,所以print不会被执行。

at_hotel = true
email = if at_hotel
          "why@hotelambrose.com"
        else
          "why@drnhowardcham.com"
        end

上面的例子说明即便if不是一个方法,它也会有返回值。

如果你在if或unless中有许多行代码,那么只有最后一行的值会被返回。

email = if at_hotel
          address = "why"
          address << "@hotelambrose"
          address << ".com"
        end

两个小于号代表关联操作符。能够在结尾加上一些东西。和==一样,«也是一种方法。所以if中的第三行代码返回了我的邮箱地址。

这里有个问题:如果if失败了,会返回什么?

是的,什么也没有。我是说,返回了nil。nil经常是个很有用的答案。

print( if at_hotel.nil?
         "No clue if he's in the hotel."
       elsif at_hotel == true
         "Definitely in."
       elsif at_hotel == false
         "He's out."
       else
         "The system is on the freee-itz."
       end )

你可以对Ruby中的任何值使用nil?方法。就像在说:“你是nil吗?你是空的吗?”

如果at_hotel是空的,Ruby不会知道我到底在不在旅馆里。elsif代表更深入一步的if。

现在你感觉还不错对吗,后面会难得多。

3.链接妄想

刚接到消息,小精灵制造的55,000只星星猴全跑到地面上了。我们得想想办法,解决这五万五千只星星猴。

print "Type and be diabolical: "
idea_backwards = gets.reverse

gets方法是Ruby的核心方法之一,它让Ruby暂停运行等待你的输入。在你按下回车时,它会返回一个包含你所有输入的字符串给Ruby。

reverse方法是字符串的特有方法。许多方法只适用于特定类型的值。

或许我们应该先把所有字母都换成大写的。

idea_backwards = gets.upcase.reverse

密码

为了防止我们的计划泄漏,我决定将文件中所有敏感词都替换掉,用一个hash,也许你会觉得它很危险。

CODE_WORDS = {
  'starmonkeys' => 'Phil and Pete, those prickly chancellors of the New Reich',
  'catapult' => 'chucky go-go', 'firebomb' => 'Heat-Assisted Living',
  'Nigeria' => "Ny and Jerry's Dry Cleaning (with Donuts)",
  'Put the kabosh on' => 'Put the cable box on'
}

箭头前面的词被称作键,箭头后面的解释被称为值。

我们一般用单引号表示字符串,如果字符串里有单引号,我们就在外面用双引号。

那么我们如何使用这个密码表呢?

CODE_WORDS['catapult']

它会返回字符串’chucky go-go’。方括号里的字符被称为坐标,当你给了一个坐标,叉车便会开到仓库里,从对应的货架上取来你需要的东西。

和你见过的其他操作符一样,方括号也是一个方法。

CODE_WORDS.[]( 'catapult' ) 

开始替换

我把之前的hash保存为wordlist.rb。

require_relative 'wordlist'

# Get evil idea and swap in code words
print "Enter your new idea: " 
idea = gets
CODE_WORDS.each do |real, code| 
  idea.gsub!( real, code )
end

# Save the jibberish to a new file
print "File encoded.  Please enter a name for this idea: " 
idea_name = gets.strip
File::open( "idea-" + idea_name + ".txt", "w" ) do |f|
  f << idea
end

第一行我们用require_relative这一核心方法引入了我们的wordlist文件。

写在井字符后面的是注释,我喜欢用注释概括一小段代码的作用。

接下来提示你输入你的idea,我们将要把它转换成密码。

CODE_WORDS.each do |real, code|
  idea.gsub!( real, code )
end

each方法出现在Ruby的各个地方,在这里意味着检查hash中的每个键。如果有和idea中匹配的,就对idea使用gusb方法。

在Ruby里,gusb是对全局替换的缩写,使用它时,第一个参数代表原来的值,第二个代表替换后的值。

我们如何保存替换后的文件呢?你可能会这样做。

safe_idea = idea.gsub( real, code )

不,我们不需要新建一个文件来保存了,在gsub后面加上!代表直接修改原来的文件。我们把这称为破坏方法。

保存疯言疯语

# Save the jibberish to a new file
print "File encoded.  Please enter a name for this idea: " 
idea_name = gets.strip
File::open( "idea-" + idea_name + ".txt", "w" ) do |f|
  f << idea
end

首先要求你输入一个名字用于给idea文件命名。

strip方法用于将字符串开头和结尾的空格(包括回车)去掉。但不会去掉中间的空格。

之后我们打开一个新的空白文件,并按括号里的公式命名。我们用类方法File::open创造了新文件。告诉你一个关于核心方法的秘密,print之类的核心方法其实是类方法。

Kernel::print( "55,000 Starmonkey Salute!" )

这意味着kernel是Ruby宇宙的中心,你不需要再拼写它。

大多数方法是更特殊的,比如File类所独有的方法:

File::read( "idea-mustard-plus-codeine.txt" ) 

会返回一个包含文件所有内容的字符串。

File::rename( "old_file.txt", "new_file.txt" ) 

重命名

File::delete( "new_file.txt" )

摧毁

这些方法存储在File类里,如果你没有写出全名,Ruby不会自动检查File类。

File::open( 'idea-' + idea_name + '.txt', 'w' ) do |f|
  f << idea
end

我们打开了文件,‘w’代表写一个全新的文件,‘r’代表读一个文件,‘a’代表在文件末尾添加。

我们把文件返交给变量f,你看到那个双竖线的滑梯了吗?它把f引入块里。在块里,我们写好了文件,随着块关闭,文件也关闭了。

注意到我们用«来写文件,因为file也具有«方法。

解码

require_relative 'wordlist'

# Print each idea out with the words fixed
Dir['idea-*.txt'].each do |file_name|
  idea = File.read( file_name )
  CODE_WORDS.each do |real, code|
    idea.gsub!( code, real )
  end
  puts idea
end

Dir[]是一个类方法Dir::[]。它的功能是在地址表中选找符合条件的文件。返回值会以包含所有文件名字符串的数组的形式表现,

p Dir['idea-*.txt']
#=> ['idea-mustard-plus-codeine.txt'] - an Array of file names!

print方法用于打印字符串,p方法可以打印任何东西。

p File::methods
#=> ["send", "display", "name", "exist?", "split", ...]

4.神奇的块

我想先给你,我的朋友,讲讲我的猫的故事。

记得我教我的宠物猫学习Ruby的时候,我给他这样一个例子:

kitty_toys =
  [:shape => 'sock', :fabric => 'cashmere'] +
  [:shape => 'mouse', :fabric => 'calico'] +
  [:shape => 'eggroll', :fabric => 'chenille']
kitty_toys.sort_by { |toy| toy[:fabric] }

“我搞不懂这是什么东西?”他说,“既有数组的方括号,又有hash的箭头。”

“事实上,这样写意味着一个包含了hash的数组。我用加号把三个数组合成了一个大数组。这里有另外一种写法。”

kitty_toys = [
  {:shape => 'sock', :fabric => 'cashmere'},
  {:shape => 'mouse', :fabric => 'calico'},
  {:shape => 'eggroll', :fabric => 'chenille'}
]

一个数组相当于玩具箱,里面的hash描述了每个玩具。

挽救生命的迭代

kitty_toys.sort_by { |toy| toy[:shape] }.each do |toy|
  puts "Blixy has a #{ toy[:shape] } made of #{ toy[:fabric] }"
end

我们调用了sort_by方法,和each一样,它也会一个一个地检查。然后返回一个按照shape的字母表顺序排好的新的数组。

还没完

“今天就到这里吧,能给我倒点牛奶吗?”猫说。

倒好牛奶,我忽然想到还有什么可以教的。那就是next。

non_eggroll = 0
kitty_toys.each do |toy|
  next if toy[:shape] == 'eggroll'
  non_eggroll = non_eggroll + 1
end

这段代码有点像“把它从迭代循环中踢出去。”

kitty_toys.each do |toy|
  break if toy[:fabric] == 'chenille'
  p toy
end


Refference: