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: