只在此山中,雲深不知處


聽首歌



© 2018 by Shawn Huang
Last Updated: 2018.5.27

Ruby


設定環境

使用之前先設定環境,首先到官網(https://www.ruby-lang.org/en/downloads/)會看到Ways of Installing Ruby,選擇你的作業系統所需要的檔案,若是與此處相同使用Windows,點擊連結到此下載網頁。此處選擇其所建議的Ruby+Devkit 2.5.5-1 (x64),下載後雙擊檔案執行,一路確定到底即可。
安裝完成後,確認一下是否安裝完成,打開DOS介面(在Windows開始處輸入cmd),輸入ruby -v指令,出現版本號即是安裝成功,如下。

接下來選擇使用的編輯軟體。此處選擇編譯方式是在DOS內輸入ruby filename.rb方式,所以可以任選文書編輯器,例如Nodepad++(better), TextPad, 甚至記事本,後兩者原則上不推薦。因為要使用DOS terminal,此處採用另一選擇:Visual Studio Code,進入官網即可看見下載,你不可能錯過它。下載後依然一路確定到底即可。接下來即可開始使用了。
此下載內含Devkit,到開始工作表中找到Interactive Ruby如下: 點擊打開,可以直接在其中輸入Ruby的指令,例如: 這個方式在練習時或是要確認某函數的運作相當方便快速,可多加利用。

基本操作

開啟Visual Studio Code如下: 在區塊1中是編寫程式碼的地方,下方區塊2選擇TERMINAL,就是DOS了。接下來輸入
puts "Hello,World."
然後在TERMINAL輸入ruby ruby1.rb,即可看到執行結果。第一個程式完成,puts函數就是在螢幕上印出內容,Ruby語法與英文類似,所以在之後空一格加上字串即可,事實上也可像其他語言一樣使用小括號,如下:
puts("Hello,World.")
再者puts其實等同其他語言的println(),也就是說印完後換行,其實還是有點不同,其中字串可以使用逗點分開,逗點處便會換行,例如:
puts "Hello","World"
那麼若是不想要換行呢?此時可以使用print函數,例如:
print "You are ", 18, " years old."
請注意兩個字串可以使用+號連結,若不是兩字串則不可。
逸出字
跟其他語言類似,沒甚麼好說的,列出如下:
Notation Character represented
\n Newline (0x0a)
\r Carriage return (0x0d)
\f Formfeed (0x0c)
\b Backspace (0x08)
\a Bell (0x07)
\e Escape (0x1b)
\s Space (0x20)
\nnn Octal notation (n being 0-7)
\xnn Hexadecimal notation (n being 0-9, a-f, or A-F)
\cx, \C-x Control-x
\M-x Meta-x (c | 0x80)
\M-\C-x Meta-Control-x
\x Character x
註解
Ruby的註解方式,單行註解使用#字號,多行註解使用=begin與=end符號,例如:
=begin 
puts "Hello, World."
print "You are ", 18, " years old." 
=end

變數

Ruby的變數型態原則上不外乎整數(Interger)、實數(Float)、字串(String)、布林(Boolean)等,在宣告時並不需要先指定變數型態,所以可以直接定義如下:
yourAge = 18
print "You are ", yourAge, " years old." 
因為Ruby中所有東西都是物件,所以可以使用.class方法來取得其物件型態:
puts yourAge.class
要將變數嵌入到字串中,除了使用上述的逗點分隔之外,還可以使用#{varname}符號,例如:
yourAge = 18
print "You are #{yourAge} years old." 
命名
命名的慣例在各個語言中大多類似,在此不再贅述。不過Ruby的變數依其作用範圍(或稱Local variable)有不同之命名規則,原則上可分為以下四種: 在之後使用時自會明瞭。

運算子

Ruby的運算子與其他許多語言多有類似,在此列出所有並解釋部分較可能混淆者:
運算優先順序
Operator Description
:: Constant resolution operator
[ ] [ ]= Element reference, element set
** Exponentiation (raise to the power)
! ~ + - Not, complement, unary plus and minus (method names for the last two are +@ and -@)
* / % Multiply, divide, and modulo
+ - Addition and subtraction
>> << Right and left bitwise shift
& Bitwise 'AND'
^ | Bitwise exclusive `OR' and regular `OR'
<= < > >= Comparison operators
<=> == === != =~ !~ Equality and pattern match operators (!= and !~ may not be defined as methods)
&& Logical 'AND'
|| Logical 'OR'
.. ... Range (inclusive and exclusive)
? : Ternary if-then-else
= %= { /= -= += |= &= >>= <<= *= &&= ||= **= Assignment
defined? Check if specified symbol defined
not Logical negation
or and Logical composition
永遠記得控制執行先後的好幫手:小括號()。

Control Flow

if...else...
沒甚麼好介紹的,看例子學語法:
score=61

if score < 60
    puts "Failed."
else
    puts "Passed."
end
不像Java使用括號,Python使用Indentation,Ruby使用end關鍵字來表示完結,記得最後要加上end即可。若是超過一個狀況,可以使用elsif,使用常用的BMI例子:
weight = 72
height = 1.65
bmi = weight/height/height

if bmi < 18.5
    puts "太瘦了,要多吃一點"
elsif 18.5 <= bmi && bmi < 23.9
    puts "標準身材,請好好保持"
elsif 23.9 <= bmi && bmi < 27.9
    puts "喔喔!得控制一下飲食了,請加油!"
else
    puts "肥胖容易引起疾病,得要多多注意自己的健康囉!"
end
此處要注意不要寫成elif或是elseif應該就沒問題了。之前在運算子介紹的?:符號,正是if...else...的簡寫法,用法如下:
a = 5
b = (a%2==0)?"偶數":"奇數"
puts b
判斷?之前的Boolean,若是true則傳回:左邊的值,false則傳回:右邊的值。或說執行:左右邊的程式碼,如下:
a = 5
(a%2==0)?(puts "偶數"):(puts "奇數")
unless...else...
Ruby提供一個跟if相反意思的關鍵字unless,也就是當其後的條件為false時執行,若為true則執行else,例如:
a = 6
unless a<5
    puts "a>=5"
else
    puts "a<5"
end
Well,原則上有if...else...就能解決的話,這個指令顯得有點多餘。順帶一提,if與unless也可以寫在欲直行的程式碼之後(此時可以不用使用end關鍵字):
a = 10
print a, " is an Even number" if a%2==0
print a, " is an Odd number" unless a%2==0
case...when...
跟Java中的switch類似,列出可能的情況以及對應情況的反應:
a = 1
case a
when 0
    puts "case 0"
when 1
    puts "case 1"
when 2
    puts "case 2"
else
    puts "other cases"
end
再試一下奇偶數的例子:
a = 10
case a%2
when 0
    print a, " is an even number"
else
    print a, " is an odd number"
end
要注意case與when只能接受整數狀況。
while loop
不囉嗦,直接看例子學語法:
a = 0
while a < 5 do
    puts a
    a+=1
end
一樣要使用end做結尾,須謹記。此外其中的do可以省略。在Java中有所謂的do...while()語法讓程式碼可以至少執行一次,在Ruby中的寫法如下:
a = 1
begin
    print "a = ", a
    a+=1
end while a > 10
puts
print "a = ", a
在begin...end中的指令會先執行一次然後再確認while的條件。
until loop
跟unless類似,Ruby提供until指令來對應while,其中當條件為false時執行程式碼:
a = 1
until a>10 do
    print "a = ", a, "\n"
    a += 1
end
for loop
Ruby的for loop語法比較接近Python,例如:
for i in 0..5
    puts i
end
其中的0..5就是指range,若是三個點0...5表示不包含最後一數(5)。這是一個iterator,也可以使用each來traverse:
sum = 0
(1..50).each do |i|
    sum = sum + i
end
puts sum
times loop
可以使用times關鍵字來指定某一區塊的程式碼的執行次數,例如:
5.times {puts "Hello"}
這真是太神奇了。直接說做5次吧就可以了。剛才的累加改成如下寫法:
sum = 0
50.times {|i| sum+=i}
puts sum
要注意的是此次的結果與剛才不同。原因是我們要求其執行50次,|i|表示產生的enumerator,計數的方式是從0開始,也就是i的內容是(0..49),所以會少了50。除此之外,我們也可以使用以下三種方式來讓程式執行某區塊多次:
1.upto(5)  {|i| puts i} 
10.downto(5)  {|i| puts i} 
0.step(10,2)  {|i| puts i} 
第一個由小到大,第二個由大到小,第三個根據特定的間隔。當然間隔也可以是負的,如下:
10.step(0,-2)  {|i| puts i}
break、next & redo
Well,break跟next就是break與continue,如下例不解釋:
for i in (0..10)
    if i%2==0
        next
    end
    if i>7
        break
    end
    puts i
end
而redo的意思類似next,就是跳回重做,但是enumerator還是同一個index(也就是i沒有+1),不像next跳回後i會+1,所以如果執行以下的程式碼,會產生無窮迴圈且顯示的值一直都是3。你可以按Ctrl+c來終止迴圈執行。
for i in 0..5
    if i > 2 then
        puts "i = #{i}"
        redo
    end
 end
將上面程式碼修改如下,增加一個count變數,讓每個i都印兩次。
count = 0
for i in 0..5
    if count >= 2
        count = 0
        next
    end
    if i > 0 then
        puts "i = #{i}"
        count += 1
        redo
    end
end
練習使用redo印出99乘法,可以不使用兩個loop。
j = 1
for i in 1..9
    if j > 9
        j = 1
        puts
        next
    end
    if i > 0 then
        puts "#{i}*#{j} = #{i*j}"
        j += 1
        redo
    end
end

方法(method)

就是函數,要注意的是其命名須用小寫英文字母開始,以避免被誤解為常數。先來個簡單例子:
def hello
    puts "Hello, World!"
end
hello
因為沒有輸入參數,所以連小括號都省了。若是有輸入參數的話:
def squareArea(w, h)
    puts "Area = #{w*h}"
end
squareArea(10,20)
Ruby的方法輸入參數是可以給初始值的,如下:
def squareArea(w=10, h=10)
    puts "Area = #{w*h}"
end
squareArea(10,20)
squareArea
這應該沒有疑義,重點是如果不是每個參數都有初始值的話會如何?
def squareVolume(w, h=50, z)
    puts "Volume = #{w*h*z}"
end
squareVolume(10,20,30)
squareVolume(10,10)
直接給三個參數顯然沒有問題,若是只給兩個,Ruby非常神奇的會自動指派給沒有初始值的參數,所以有初始值的參數在哪個位置都可以,跟其他語言(e.g. Python)不同。若是想要將結果傳回,這倒是跟其他語言類似,使用return關鍵字:
def squareArea(w=10, h=10)
    return w*h
end
puts squareArea(10,20)+squareArea
事實上我們可以省略return,函數的最後一個數值就是預設的傳回值,例如:
def squareArea(w=10, h=10)
    w*h
end
puts squareArea(10,20)+squareArea
在Ruby中可以使用未定長度參數,只需在之前加上*號,例如:
def sum(*numbers)
    s = 0
    for i in numbers
        s += i
    end
    return s
end
puts sum(1,2,3,4,5)
如果方法的輸入參數中有一般參數與未定長度參數:
def sum(*scores, classnumber)
    s = 0
    for i in scores
        s += i
    end
    puts "#{classnumber}'s average score is #{s.to_f/scores.length}"
end
sum(100,92,93,64,85, 102)
首先classnumber可以放在scores之前或之後,程式會直接保留一個參數給它,所以即使也是整數,不會將其誤認為是成績。(若是classnumber放在scores之後,不可以給初始值。若是放在scores之前,雖然可以給初始值,不過這是無效的,還是會將第一個參數視為班級編號。)在取得平均數的計算時,為了計算到小數後,使用s.to_f來將整數轉換成實數,而scores.length表示的是其長度(因為scores是一個Array,可以使用scores.class來確認)。
給初始值的參數也可以是未定長度,我們須在參數前加上**符號,例如:
def info(theclass, *students, **scores)
    [theclass, students, scores]
end
p info 101, "Tom", "Mary", tom: 90, mary: 92 # [101, ["Tom", "Mary"], {:tom=>90, :mary=>92, :John=>100}]
可見*所收集的參數形成一個array,而**所收集的資料形成一個hash。
除了使用def來定義函數之外,還可以使用lambda來建立,例如:
func = lambda { puts "Hi, there" }
puts "lambda會建立\"#{func.class}\"物件"
func.call
我們可以使用一個變數來代表lambda所產生的物件,此物件屬於Proc,因為屬於Proc物件,所以須使用.call方法來呼叫。事實上我們還可以省略lambda關鍵字,直接使用->符號代替:
func = -> { puts "Hi, there" }
puts "lambda會建立\"#{func.class}\"物件"
func.call
使用lambda可以產生Proc物件,也可以乾脆直接建立Proc物件,例如:
func = Proc.new {puts "hi, there"}
puts func.class
func.call
這也是一樣當作函數使用。

陣列(Array)

就是Array,先看如何宣告:
arr = Array.new(5)
puts arr.size, arr.length
其中5指的是array的長度,而.size與.length都會傳回長度,但是此時的陣列沒有內容,給初始值的方式:
arr = Array.new(5, "Hello")
puts arr
這個寫法是說長度5,每個內容都是Hello,所以列印的內容是5個Hello。不過我們想要的不只是都一樣的內容:
arr1 = Array.[](1,2,3,4,5)
arr2 = Array[1,2,3,4,5]
arr3 = [1,2,3,4,5]
puts arr1, arr2, arr3
這三種方式都可以宣告有初始值的Array,事實上也可以使用如下的方式達到相同的效果:
arr4 = Array(1..5)
再來利用區塊的方式:
arr5 = Array.new(5){|i| i=i+1}
puts arr5
因為長度為5,|i|表示產生的enumerator,計數的方式是從0開始,所以要顯示1到5須讓i為i+1。
Array Iteration
意思就是歷遍Array中的所有元素,例如逐一印出,因為array是iterable,所以可以
使用for loop:
arr = Array(1..5)
for i in arr
    puts i
end
使用each:
arr = Array(1..5)
arr.each {|i| puts i*2}
使用collect:collect跟each的分別是collect會傳回一個新的array。
arr = Array(1..5)
newarr = arr.collect {|i| i**2}
puts newarr
使用map:跟collect類似,逐一處理完每個元素然後傳回。
arr = Array(1..5)
newarr = arr.map {|i| i**3}
puts newarr
Array Methods
Array內建的方法有許多,請見以下表格:
No. Methods & Description
1 array & other_array: 兩陣列之交集
a1 = [1,2,3,4,5]
a2 = Array(3..9)
puts a1&a2
2 array * int [or] array * str: 乘以數字表示內容乘以該數字倍數然後傳回新的array,乘以字串表示等同於self.join(str),使用字串連結元素,傳回字串
a1 = [1,2,3,4,5]
puts a1*"." #傳回字串
a2 = a1*2 #傳回Array
print a2
3 array + other_array: 兩陣列疊加
a1 = [1,2,3,4,5]
a2 = Array(3..9)
puts a1+a2
4 array - other_array: 去除原Array與另一Array之相同者,傳回新的array
a1 = [1,2,3,4,5]
a2 = Array(3..9)
print a1-a2
5 array <=> other_array: 比較兩陣列是否相等,相等傳回0,左大於右傳回1,否則傳回-1。
a1 = [1,2,3,4,5]
a2 = Array(3..9)
print a1<=>a2
6 array | other_array: 兩陣列之聯集
a1 = [1,2,3,4,5]
a2 = Array(3..9)
print a1|a2
7 array << obj: append元素在最後,傳回陣列本身
a1 = [1,2,3,4,5]
a1 << 10 << 11 << 12 #chainable
print a1
8 array == other_array: 比較兩個Array是否相等,傳回Boolean
a1 = [1,2,3,4,5]
a2 = Array(1..5)
print a1==a2
9 array[index] [or] array[start, length] [or] array[range] [or] array.slice(index) [or] array.slice(start, length) [or] array.slice(range): 切片,-1表示最後一個元素,超出範圍傳回nil
a1 = Array(1..10)
print a1[2], "\n", a1[2,6], "\n", a1[2..6], "\n", a1[-1], "\n"
print a1.slice(2), "\n", a1.slice(2,6), "\n", a1.slice(2..6)
10 array[index] = obj [or] array[start, length] = obj or an_array or nil [or] array[range] = obj or an_array or nil: 指派值給Array
a1 = Array(1..10)
a1[0] = 100
a1[1,3] = [20,30,40]
a1[7..10] = ['a','b','c','d']
print a1
11 array.abbrev(pattern = nil): 算出字串的縮寫。需先導入abbrev模組(require "abbrev"),pattern可為nil或是regular expression
require "abbrev"
arr1 = ["java", "python", "ruby"].abbrev
puts arr1
arr2 = ["rabbit", "rubber", "ruby"].abbrev(/^.ub/)
puts arr2
12 array.assoc(obj): 尋找一個元素也是array的array,傳回第一個元素等同欲搜尋元素的array,此處的%W代表將之後的每個元素使用""包括並傳回array
arr1 = %W{a b c}
arr2 = %W{red green blue}
arr3 = %W{apple banana pear}
a = [arr1, arr2, arr3]
print a.assoc("apple")
13 array.at(index): 等同於array[index]
arr1 = %w[a b c]
puts arr1.at(1), arr1.at(-1)
14 array.clear: 清空array
arr1 = %w[a b c]
print arr1.clear
15 array.collect { |item| block } [or] array.map { |item| block }: 如前所述
16 array.collect! { |item| block } [or] array.map! { |item| block }: 與上類似,只是此處直接作用在原array上
arr1 = %w[a b c]
arr1.collect!{|i| i+"z"}
puts arr1
arr2 = Array(1..5)
arr2.map!{|i| i**2}
puts arr2
17 array.compact: 去除array內的nil然後傳回array
arr1 = [1,nil,2,3,nil,4,nil,5]
print arr1, "\n", arr1.compact
18 array.compact!: 與上同,只是直接作用在原array上
arr1 = [1,nil,2,3,nil,4,nil,5]
print arr1, "\n"
print arr1.compact!, "\n", arr1
19 array.concat(other_array): 將other_array內元素附加到array內,與+略不同(+會產生新的array)
arr1 = Array(1..5)
arr2 = Array(10..15)
print arr1.concat(arr2)
20 array.delete(obj) [or] array.delete(obj) { block }: 刪去array中的某元素,若該元素不存在,傳回nil(若有{block},傳回block內程式碼)。
arr1 = Array(1..5)
puts arr1.delete(2)
puts arr1.delete(2){"not found"} #puts arr1.delete(2){-1}
21 array.delete_at(index): 刪除某位置元素且傳回該元素,超過範圍傳回nil
arr1 = Array(1..5)
puts arr1.delete_at(2)
puts arr1.delete_at(5)==nil
22 array.delete_if { |item| block }: 刪除array內某條件的元素
arr1 = Array(1..10)
puts arr1.delete_if{|i| i%2==0}
23 array.each { |item| block }: 如前,對array中每個元素進行某程序
arr1 = Array(1..10)
arr1.each{|i| puts i*2}
24 array.each_index { |index| block }: 與前同,只是此次是針對每個index
arr1 = Array(1..10)
arr1.each_index{|i| puts i*2}
25 array.empty?: 判斷array內容是否是空(沒有元素)
arr1 = Array.new(2,nil)
puts arr1.empty?
26 array.eql?(other): 判斷兩array是否相等
puts Array.new.eql?([])
27 array.fetch(index) [or] array.fetch(index, default) [or] array.fetch(index) { |index| block }: 取得index位置內容,若超過範圍進行處理(沒有設計處理傳回IndexError)
arr = Array(1..10)
puts arr.fetch(10, "Out of bound")
puts arr.fetch(11){|i| print "out of bound: ", i}
puts arr.fetch(11)
28 array.fill(obj) [or] array.fill(obj, start [, length]) [or] array.fill(obj, range) [or] array.fill { |index| block } [or] array.fill(start [, length] ) { |index| block } [or] array.fill(range) { |index| block }: 填滿
arr = Array.new(10)
print arr.fill(1), "\n"
print arr.fill(2,2,6), "\n"
print arr.fill(3, 5..7), "\n"
print arr.fill{|i| i}, "\n"
print arr.fill(3,3){|i| i**2}, "\n"
print arr.fill(6..8){|i| -i}, "\n"
print arr.fill(-1){|i| i*10}, "\n"
29 array.first [or] array.first(n): 傳回第一個元素或是前n幾個元素組成之array,如果原array為空,傳回nil或[]
arr = Array(1..10)
puts arr.first
print arr.first(3)
30 array.flatten: 將多維陣列轉換成一維陣列並傳回
arr = [Array(1..5), Array(10..15), [100,200,Array(300..305)]]
puts arr.flatten
31 array.flatten!: 將多維陣列轉換成一維陣列並傳回,作用在原array(in place)。但若原array就是一維陣列(表示內容沒改變)則傳回nil
arr = [Array(1..5), Array(10..15), [100,200,Array(300..305)]]
arr.flatten!
print arr
puts [1,2,3].flatten!==nil
32 array.frozen?: 判斷array是否是frozen
a = %W{a b c}
a.freeze
#a << "d" ## frozen array 不能再改變,傳回FrozenError
puts a.frozen? # return true
33 array.hash: 傳回array的hash-code,相同內容將傳回相同的hash code
a = %W{a b c}
b = ['a','b','c']
print a.hash, "\t", b.hash
34 array.include?(obj): 判斷元素obj是否存在於array內,傳回Boolean
a = %W{a b c}
print a.include?('b')
35 array.index(obj): 傳回array中第一個等於obj元素的index,不存在則傳回nil
arr = %W{a b c d e b f g}
print arr.index('b'), "\t", arr.index("k")==nil
36 array.insert(index, obj...): insert元素
arr = %W{a b c d e}
print arr.insert(1, 'w')
37 array.inspect: 建立一可印之array字串
arr = %W{a b c d e}
puts arr.inspect
puts arr
38 array.join(sep = $,): 將array中元素使用sep連結,傳回字串
arr = %W{a b c d e}
puts arr.join('-')
puts arr
39 array.last [or] array.last(n): 與.first對應,此處自最後起計
arr = %W{a b c d e}
puts arr.last
puts arr.last(2).inspect
40 array.length: 傳回array長度
arr = %W{a b c d e}
puts arr.length
41 array.pack(aTemplateString): 把array內的元素變成binary sequence,其中A, a, and Z可能需要加上數字來表示其寬度
arr = %W{a b c}
puts arr.pack("A5A5A5") # arbitrary binary string (space padded, count is width)
puts arr.pack("a3a3a3") # arbitrary binary string (null padded, count is width)
num = [65,66,67]
puts num.pack("ccc") # 8-bit unsigned (unsigned char)
unum = [165,266,367]
puts unum.pack("UUU") # UTF-8 character
42 array.pop: 刪除array中最後一個元素並傳回該元素,空的array傳回nil
arr = %W{a b c}
puts arr.pop
print arr
43 array.push(obj, ...): append元素在最後(chainable),傳回陣列本身,等同於<<
arr = %W{a b c}
puts arr.push('d','e','f')
print arr << 'g' << 'h'
44 array.rassoc(key): 與assoc()同,只是比對的是第二個元素
arr1 = %W{a b c}
arr2 = %W{red green blue}
arr3 = %W{apple banana pear}
a = [arr1, arr2, arr3]
print a.rassoc("green")
45 array.reject { |item| block }: 去除array中符合條件的元素並傳回新array
arr = Array(1..10)
puts arr.reject{|i| i < 5}
46 array.reject! { |item| block }: 與上同,只是in place(等同delete_if),若沒改變傳回nil
arr = Array(1..10)
arr.reject!{|i| i < 5}
print arr
arr = arr.reject!{|i| i < 5}
print arr==nil
47 array.replace(other_array): 使用other_array內容替代array內容,此時內容相同但非指向相同記憶體區塊,與array=other_array不同
arr1 = Array(1..10)
arr2 = %W{a b c d e}
arr1.replace(arr2)
print arr1
puts
print arr1.equal?arr2
48 array.reverse: 傳回新的array in reverse order
arr1 = Array(1..10)
print arr1.reverse
49 array.reverse!: reverse array in place
arr1 = Array(1..10)
arr1.reverse!
print arr1
50 array.reverse_each {|item| block }: 與.each類似,只是in reverse order
arr = Array(1..10)
arr.reverse_each{|item| print item," "}
51 array.rindex(obj): 與.index同,只是in reverse order
arr = %W{a b c d e b f g}
print arr.rindex('b'), "\t", arr.index("k")==nil
52 array.select {|item| block }: 選擇符合條件的元素並傳回新array
arr = %W{a b c d e b f g}
newarr = arr.select{|item| item < 'e'}
print newarr
53 array.shift: 移除array中第一個元素並傳回該元素,其餘元素皆往前一位平移。若array為空傳回nil。
arr = %W{a b c d e b f g}
puts arr.shift
print arr
54 array.size: 等同array.length
arr = %W{a b c d e b f g}
puts arr.size
55 array.slice!(index) [or] array.slice!(start, length) [or] array.slice!(range): 就是slice in place
a1 = Array(1..10)
print a1, "\n"
a1.slice!(2)
a1.slice!(2..5)
a1.slice!(1,3)
print a1
56 array.sort [or] array.sort { | a,b | block }: 排序
a1 = ['d','a','z','k','w']
print a1.sort
a2 = [[1,2,3,4,5], Array(2..6), [1,5,10]]
def sum(ar)
    s = 0
    ar.each{|item| s+=item}
    return s
end
print a2.sort{|a,b| sum(a)<=>sum(b)}
57 array.sort! [or] array.sort! { | a,b | block }: sort in place
a1 = ['d','a','z','k','w'].sort!
print a1
a2 = [[1,2,3,4,5], Array(2..6), [1,5,10]]
def sum(ar)
    s = 0
    ar.each{|item| s+=item}
    return s
end
a2.sort!{|a,b| sum(a)<=>sum(b)}
print a2
58 array.to_a: 傳回array本身,若是array的subclass,傳回array物件
a1 = Array(1..5).to_a
print a1
59 array.to_ary: 也是傳回array本身
60 array.to_s: 傳回array本身的字串
a1 = Array(1..5).to_s
print a1
61 array.transpose: 傳回轉置矩陣
a = [Array(1..3), Array(4..6), Array(7..9)]
print a.transpose
62 array.uniq: 傳回去除重複元素的array
a = Array(1..10)
b = Array(5..15)
print (a+b).uniq
63 array.uniq!: uniq in place
a = Array(1..10)
b = Array(5..15)
c = (a+b)
c.uniq!
print c
64 array.unshift(obj, ...): 在array前端插入元素,其他元素往後移位
a = Array(1..10)
a.unshift(11)
a.unshift(12,13)
print a
65 array.values_at(selector,...): 傳回特定index位置元素組合成的array
a = Array(1..10)
print a.values_at(1)
print a.values_at(1,3,5,7,9)
print a.values_at(2..6)
66 array.zip(arg, ...) [or] array.zip(arg, ...){ | arr | block }: zip
a = Array(1..5)
b = %W(a b c d e)
c = Array["Do", "Re", "Mi"]
d = ["u", "v"]
z1 = a.zip(b)
z2 = c.zip(a,b)
z3 = d.zip(a,b)
z4 = []
b.zip(c,d){|x,y,z| z4 << "#{x}#{y}#{z}"}
print z1, "\n", z2, "\n", z3, "\n", z4

字串(String)

字串表示法:
s1 = "String 1"
s2 = 'String 2'
puts s1+s2
單雙引號都可以,且兩字串可以使用+號連結。若是很長的字串需要跨行呢?
s1 = "String 1
line2..."
s2 = 'String 2
another line2...'
puts s1+s2
直接換行看起來沒問題,也可以這樣做:
s3 = %q{
If we are going to have a long string,
we can have a string span several lines.
This is what I am talking about.}
s4 = <<ENDSYMBLE
If we are going to have a long string,
we can have a string span several lines.
This is what I am talking about.
ENDSYMBLE

puts s3
puts s4
String Methods
字串也有許多內建方法,請見以下表格:
No. Methods & Description
1 str % arg: 格式化字串,更多請參照sprintf under "Kernel Module."
puts "%.2f" % 3.14159
puts "%-5s: %08d" % ["hashcode", Array(1..10).hash]
puts "%08x" % 10000
puts "a = %{a}" % {:a => 'abc'}
2 str * integer: 傳回integer倍數的字串
for i in 1..10
    puts "*"*i
end
3 str + other_str: 字串相加,如前述
4 str << obj: 跟array類似自後累加,若是數字會轉換成字元
puts "abc" << 'de' << 66 << 15101
5 str <=> other_str: 比較兩字串大小,returning -1 (less than), 0 (equal), or 1 (greater than). The comparison is case-sensitive.
puts "abc" <=> String('abc')
6 str == obj: 比較兩字串是否相等,若obj不是字串傳回false。
puts "abc" == String('abc')
7 str =~ obj: obj是regular expression,傳回字串中符合obj的位置,若無符合傳回false。
puts "You are 18 years old." =~ /\d/
8 str.capitalize: 第一個字母大寫
puts "ruby".capitalize
9 str.capitalize!: 第一個字母大寫in place
r = "ruby"
r.capitalize!
puts r
10 str.casecmp: 等同<=>但無視大小寫
puts "abc".casecmp("AbC")
11 str.center: 置中
for i in 1..10
    puts ("* "*i).center(20)
end
12 str.chomp: 移除字串最後的separator
puts "hello\n".chomp # hello
puts "hello\r\n".chomp # hello
puts "hello".chomp("lo") # hel
puts "hello\r\n\r\n".chomp('') #hello
puts "hello\r\n\r\r\n".chomp('') #hello\r\n\r
13 str.chomp!: 移除字串最後的separator in place
h = "hi\n"
h.chomp!
print h + " there"
14 str.chop: 移除最後一個字符
print "test\n".chop # test
puts "test".chop # tes
15 str.chop!: 移除最後一個字符in place
t = "test\n"
t.chop!
t.chop!
puts t
16 str.clear: 清除字串內容
s = "Time is money"
puts s.clear
17 str.concat(other_str): 與<<同
puts "a".concat("b").concat(66).concat(15101)
18 str.count(str, ...): 計數參數在str出現的次數
puts "'Time is money' is any old proverb.".count('is') # check number of 'i' & 's'
puts "'Time is money' is any old proverb.".count('ma-d') # m & a-d(included)
puts "Time".count("^ie") # ^ => negated
puts "'Time is money' is any old proverb.".count('time', 'fine') # intersection of "time" & "fine" => 'i' & 'e'
19 str.crypt(other_str): 根據other_str(should be two characters long)產生str的加密hashcode
puts "xyz".crypt("cr")
20 str.delete(other_str, ...): 刪除給定字元
puts "current".delete("re") # delete 'r' & 'e'
puts "current".delete("re","en") # delete 'e'=>intersection of re & en
puts "current".delete("aeiou", "^e") # ^e means not e
puts "current".delete("tm-r") # delete t & m-r
21 str.delete!(other_str, ...): 刪除給定字元in place
c = "current".delete!("re") # delete 'r' & 'e'
puts c
c = "current".delete!("re","en") # delete 'e'=>intersection of re & en
puts c
c = "current".delete!("aeiou", "^e") # ^e means not e
puts c
c = "current".delete!("tm-r") # delete t & m-r
puts c
22 str.downcase: 大寫變小寫
puts "Time Is Money".downcase
23 str.downcase!: 大寫變小寫in place
t = "Time Is Money".downcase!
puts t
24 str.dump: 傳回包含逸出字的字串
puts "\tTime\n Is \"Money\"".dump
25 str.each_byte { |fixnum| block }: 針對字串中每一byte
"Time is money.".each_byte {|i| puts "" << i}
s = ""
for i in "Time is money.".each_byte
    puts s << i
end
26 str.each_char { |cstr| block }: 針對每一個char,若沒給block(大括號),傳回enemerator
"Time is money.".each_char {|c| puts c}
或是
for i in "Time is money.".each_char
    puts i
end
27 str.each_codepoint{ |integer| block }: 針對每一個char傳回integer ordinal
"Time is money.\u0639".each_codepoint{|c| puts c}
28 str.each_line(separator=$/) { |substr| block }: 根據字元分拆為不同次字串,預設使用\n。
"Time\nis\nmoney.".each_line{|c| puts c}
"Time\nis\nmoney.".each_line(""){|c| puts c}
"Time is money.".each_line("m"){|c| puts c}
for i in "Time is money.".each_line(" ")
    print i+" "
end
29 str.empty?: 判斷str是否為空(長度為0)
puts "".empty?
30 str.end_with?([suffixes]+): 判斷是否字串以suffixes之一結尾
puts "Time is money".end_with?("ney", "Tim")
31 str.eql?(other): 判斷兩字串是否相等
puts "Time is money".eql?("Time is money")
32 str.getbyte(index): 取得某位置的字元的byte
puts "Time is money".getbyte(0)
33 str.gsub(pattern, replacement) [or] str.gsub(pattern) { |match| block }: 替換子字串
puts "Time is money".gsub("Time","Everything")
puts "Time is money".gsub(/[aeiou]/,'@')
puts "Time is money".gsub(/([aeiou])/,'<\1>')
puts "Time is money".gsub(/./){|s| s.ord.to_s+' '} #s.ord傳回codepoint
puts "Time is money".gsub(/([mn])/, "m"=>"M", 'n'=>"N")
34 str[fixnum] [or] str[fixnum,fixnum] [or] str[range] [or] str[regexp] [or] str[regexp, fixnum] [or] str[other_str]: 取得字串的部分內容
puts "Time is money"["is"]
puts "Time is money"[1]
puts "Time is money"[1..6]
puts "Time is money"[1,6]
puts "Time is money"[/[aeiou]n/]
35 str[fixnum] = fixnum [or] str[fixnum] = new_str [or] str[fixnum, fixnum] = new_str [or] str[range] = aString [or] str[regexp] = new_str [or] str[regexp, fixnum] = new_str [or] str[other_str] = new_str ]: 替換部分字串,類似slice
t = "Time is money"
t[1..6]= "123456"
t[/[aeiou]n/] = "ON"
puts t
36 str.gsub!(pattern, replacement) [or] str.gsub!(pattern) { |match|block }: 替換子字串in place,若無替換發生傳回nil
puts "Time is money".gsub!("m","M")
37 str.hash: 傳回hash value
puts "ruby".hash
38 str.hex: 傳回16進位的10進位值(錯誤時傳回0)
puts "ruby".hex
puts "ff".hex
puts "0x1a".hex
39 str.include? other_str [or] str.include? fixnum: 判斷字串是否包含某子字串
puts "Time is money".include? "is"
40 str.index(substring [, offset]) [or] str.index(fixnum [, offset]) [or] str.index(regexp [, offset]): 搜尋子字串在str中的位置,不存在傳回nil
puts "Time is money".index("is")
puts "Time is money".index(/[aeiou]/, -3) #第二個參數表示搜尋的起始位置
puts "Time is money".index(?m)
41 str.insert(index, other_str): 插入字串至str
puts "Time is money".insert(8, "more than ")
42 str.inspect: 傳回包含逸出字的可印字串
puts "Time\nis\nmoney".inspect
43 str.intern [or] str.to_sym: 傳回代表字串的symbol
t = "Time".intern
s = "money".to_sym
puts t==:Time, s==:money
44 str.length: 傳回字串長度
puts "Time is money".length
45 str.lines(separator=$/): 傳回根據separator分割字串組成的array
print "Time is money".lines(" ")
print "Time\nis\nmoney".lines()
46 str.ljust(integer, padstr = ' '): 如果integer大於str長度,字串長度變成integer,str靠左,其餘由padstr填滿,若是integer小於字串長度,傳回該字串
print "Time is money".ljust(20, "#")
47 str.lstrip: 移去左方的空白
print "\t   Time is money".lstrip
48 str.lstrip!:移去左方的空白in place,若無改變傳回nil
s = "Time is money".lstrip!
puts s==nil
49 str.match(pattern): 傳回match pattern的子字串
puts "Time is money".match(/[aeiou]/, 9)
puts "Time is money".match("is")
puts "Time is money".match("was")==nil
50 str.match?(pattern): 判斷是否有match pattern的子字串
puts "Time is money".match?(/[aeiou]/, 9)
puts "Time is money".match?("is")
puts "Time is money".match?("was")
51 str.oct: 傳回8進位的數值
puts "123".oct
puts "ruby".oct
puts "-0377ruby".oct
52 str.ord: 傳回char的integer ordinal
puts 'a'.ord
53 str.partition(sep or regexp): 根據sep將字串分為三部分,傳回array
print "Time is money".partition("m")
print "Time is money".partition(/m./)
54 str.prepend(other_str1, other_str2,...): 將other_str加到str前面
puts "Time is money".prepend "Yes,"
55 str.replace(other_str): 使用other_str替換str
t = "Time is money".replace("Time is more than money")
puts t
56 str.reverse: Reverse
puts "Time is money".reverse
57 str.reverse!: Reverse in place
puts "Time is money".reverse!
58 str.rindex(substring [, fixnum]) [or] str.rindex(fixnum [, fixnum]) [or] str.rindex(regexp [, fixnum]): 自右往左尋找substring的index,第二個參數為搜尋起始位置
puts "Time is money".rindex("is")
puts "Time is money".rindex(/[aeiou]/, -3) #第二個參數表示搜尋的起始位置
puts "Time is money".rindex(?m)
59 str.rjust(integer, padstr = ' '): 自右邊開始的just
puts "Time is money".rjust(20, "#")
60 str.rpartition(sep or regexp): 自右邊根據sep將字串分為三部分,傳回array
print "Time is money".rpartition("m")
print "Time is money".rpartition(/m./)
61 str.rstrip: 去除末端空白
puts "Time is money\t.\n     ".rstrip
62 str.rstrip!: 去除末端空白in place,沒改變傳回nil
puts "Time is money\t.\n     ".rstrip!
puts "Time is money".rstrip! == nil
63 str.scan(pattern) [or] str.scan(pattern) { |match, ...| block }: 找出符合描述的字串並傳回array
print "Time is money".scan(/.../)
print "Time is money".scan(/(...)/)
print "Time is money".scan(/(...)(...)/)
print "Time is money".scan(/m./)
print "Time is money".scan(/\w+/)
"Time is money".scan(/\w+/){|match| puts match.upcase}
64 str.scrub(repl) 若byte sequence錯誤,使用repl替換,否則傳回字串自身
puts "Time\u3059\x79".scrub
puts "Time\u3059\x80".scrub "*"
65 str.scrub!(repl) 若byte sequence錯誤,使用repl替換,否則傳回字串自身in place
puts "Time\u3059\x79".scrub!
puts "Time\u3059\x80".scrub! "*"
66 str.size 與.length同
puts "Time is money".size
67 str.slice(fixnum) [or] str.slice(fixnum, fixnum) [or] str.slice(range) [or] str.slice(regexp) [or] str.slice(regexp, fixnum) [or] str.slice(other_str) See str[fixnum], etc. str.slice!(fixnum) [or] str.slice!(fixnum, fixnum) [or] str.slice!(range) [or] str.slice!(regexp) [or] str.slice!(other_str): 切片,在原str刪去選取部分並傳回選取部分(! means in place)
puts "Time is money".slice(3)
puts "Time is money".slice(3,9)
puts "Time is money".slice(3..9)
puts "Time is money".slice(/[aeiou]./)
t = "Time is money"
t["is"] = "can be"
puts t
t = "Time is money"
puts t.slice!(3...9)
puts t
68 str.split(pattern = $, [limit]): split
print "Time is money".split
print "Time is money".split(' ')
print "Time is money".split('m')
print "Time is money".split(/[aeiou]/)
print "Time is money".split(//)
print "Time is money".split(//, 3)
print "Time is money".split(%r{\s*})
69 str.squeeze([other_str]*): 傳回新字串,其中相鄰且重複之字元僅留下一個,可設定範圍
puts "correct".squeeze
puts "It          is      correct".squeeze(" ")
puts "happy habbit".squeeze("a-c")
70 str.squeeze!([other_str]*): squeeze in place
t = "It          is      correct"
t.squeeze!(" ")
puts t
71 str.start_with?([prefixes]+): 判斷str是否起始於prefixes
puts "Time is money".start_with?("Time", "time")
puts "Time is money".start_with?(/.[aeiou]/)
72 str.strip: 去除str前後空白
puts "\tTime is money   ".strip
73 str.strip!: 去除str前後空白in place,無作用則傳回nil
s = "\tTime is money   "
s.strip!
puts s
t = "time is money"
puts (t.strip!)==nil
74 str.sub(pattern, replacement) [or] str.sub(pattern) { |match| block }: 替換子字串
puts "Time is money".sub(/[aeiou]/, "#")
puts "Time is money".sub(/([aeiou])/, '<\1>')
puts "Time is money".sub(/./){|s| s.ord.to_s+" "}
puts "Time is money".sub(/(?<whatever>[aeiou])/, '*\k<whatever>*')
75 str.sub!(pattern, replacement) [or] str.sub!(pattern) { |match| block }: 替換子字串in place
s = "Time is money"
s.sub!(/[aeiou]/, "#")
puts s
76 str.succ [or] str.next: 傳回str的successor
puts "Time is money.".succ
puts "Ver2.0".next
puts "<<Champion>>".succ
puts "100zzz".succ
puts "zzz999".succ
puts "+++".next
77 str.succ! [or] str.next!: 傳回str的successor in place
s = "Time is money"
s.succ!
puts s
t = "***"
t.next!
puts t
78 str.sum(n = 16): 傳回基本的n-bit checksum
puts "Time".sum(n=16)
79 str.swapcase: 大小寫互換
puts "Time is money".swapcase
80 str.swapcase!: 大小寫互換in place
s = "Time is money"
s.swapcase!
puts s
81 str.to_c: 轉換為complex
puts "9".to_c, 2.5.to_c, "2.5/1".to_c, '-i'.to_c, '-3e2-3e-2i'.to_c, "ruby".to_c
82 str.to_f: 轉換為float
puts "12.3456e3".to_f
puts "12.345 degrees".to_f
puts "version_1.01".to_f
83 str.to_i(base = 10): 轉換為int
puts "12.3456e3".to_i
puts "12.345 degrees".to_i
puts "version_1.01".to_i
puts "1010101".to_i(2)
puts "1010101".to_i(8)
puts "1010101".to_i(10)
puts "1010101".to_i(16)
84 str.to_r: 轉換為分數(rational)
puts "  2  ".to_r
puts "100/2".to_r
puts "-3.14".to_r
puts "2.e2".to_r
puts "1_234_567".to_r
puts "10/6/2019".to_r
85 str.to_s [or] str.to_str: 轉換為str(傳回本身)
puts "  2  ".to_s
86 str.tr(from_str, to_str): 將str中的from_str替換成to_str
puts "Time is money".tr("is", "#*")
puts "Time is money".tr("c-k", "#")
puts "Time is money".tr("[aeiou]", "")
puts "Time is money".tr("^[aeiou]", "#")
87 str.tr!(from_str, to_str): 將str中的from_str替換成to_str in place,無改變傳回nil
s = "Time is money"
s.tr!("is", "#*")
puts s
puts s.tr!("k", "@")==nil
88 str.tr_s(from_str, to_str): 使用to_str替換from_str,移除重複的characters
puts "Time is money".tr_s("i", "#")
puts "Time is money".tr_s("im", "#")
puts "Time is money".tr_s("mon", "Mm")
89 str.tr_s!(from_str, to_str): 使用to_str替換from_str,移除重複的characters in place
s = "Time is money"
s.tr_s!("mon", "#")
puts s
90 str.unpack(format): 根據format string來decode字串並傳回陣列,see more details here
print "Time Time".unpack("A5Z5"), "\n"
print "Time \0\0".unpack('a4a4'), "\n"
print "Time \0Time \0".unpack('Z*Z*'), "\n"
print "Time".unpack('b8B8b8B8'), "\n"
print "Time".unpack('h2H2cc'), "\n"
print "\xfe\xff\xfe\xff".unpack('sS'), "\n"
print "Time=20is=20money".unpack('M*'), "\n"
print "Time is money".unpack('xax2aX2aX1aX2a')
91 str.upcase: 小寫變大寫
puts "Time is money".upcase
92 str.upcase!: 小寫變大寫in place,若無改變傳回nil
s = "Time is money"
s.upcase!
puts s
t = s
puts (t.upcase!)==nil
93 str.upto(other_str) { |s| block }: 從str到other_str的iterator
"e8".upto("f6") {|s| print s, " "}
for s in "e8".."f6"
    print s, " "
end
94 str.valid_encoding?: 判斷是否為正確的coding
puts "\xc2\xa1".force_encoding("UTF-8").valid_encoding?
puts "\xc2".force_encoding("UTF-8").valid_encoding?
puts "\x80".force_encoding("UTF-8").valid_encoding?

Alternate Syntax

字串的正式語法使用單引號(')或雙引號(")作為括號,不過如前述在ruby中也定義可以使用成對符號來做為前後符號(尤其可用於多行字串),例如:
print 'single quote', "\t", 'single quote'.class, "\n"
print "double quote", "\t", "double quote".class, "\n"
print %(parenthesis), "\t", %(parenthesis).class, "\n"
print %[bracket], "\t", %[bracket].class, "\n"
print %{brace}, "\t", %{brace}.class, "\n"
print %|vertical bar|, "\t", %|vertical bar|.class, "\n"
# any other symbol -> *, !, @, #, $, ^, & ->試了好像都可以,不過似乎不需要
print %&any other symbol&, "\t", %&any other symbol&.class, "\n"
# 使用'<<文字'與'文字'作為前後符號
s = <<Whatever
This is a string that
corssing several lins
......
Whatever
puts s, s.class
甚至可以這樣做:
print <<"FRUIT", <<"BAR" # stack
first fruit......
Apple
Banana
Pear
FRUIT
second bar......
Horizontal bar
Vertical bar
BAR
以上各個方式都可以建立字串,有些之前提過,此處要提的是在%之後可以再加上字母來表達其他意思,例如前述的%q,可使用的符號如下:

Symbol

Symbol是一種物件,可用來代表某個狀態,而其宣告方式是在名稱前面加上:號,例如:
condition = :now
if :now
    puts "condition is now", :now.class
end
有點像是代表true。Symbol有些與字串類似的方法,例如length、upcase、downcase,不過不像字串可以修改,Symbol是不能被修改的。
puts :age.length
puts :age.upcase
puts :age[0]
Symbol跟字串的不同處還有即使內容相同,字串宣告會佔據不同塊記憶體,而相同內容Symbol一形成佔據同一塊記憶體,所以比較節省記憶體。
5.times do
    puts "age".object_id #皆不同
end
5.times do
    puts :age.object_id #皆相同
end
上述字串時提過to_sym方法便是將字串轉為symbol。也可以使用intern、%s(strname)等方式。而symbol因為不可修改,可用於部分變數名稱,或是常見用於hash的key。
在我們定義一個函數後,在Ruby空間中就會產生一個代表該函數的Symbol物件,我們可以使用send()方法來呼叫該物件以執行該函數,例如:
def hello
    puts "Hi, there"
end
hello
send(:hello)

Hash

Hash就是Java中的map或是Python中的dict,是另訂index(key)的array,宣告方式如下:
weeks = Hash.new
weeks = Hash.new("week")
weeks = Hash.new "week"
puts "#{weeks[0]}"
宣告空的或是初始值為week的hash。不過通常常用的做法是如下:
numbers = Hash["one", 1, "two", 2]
puts numbers["one"]
items = Hash[[["pen", 100],["pencil",200]]]
puts items["pen"]
weeks = Hash[1 => "Mon", 2 => "Tue"]
puts weeks[1]
months = {1 => "Jan", 2 => "Feb"}
puts months[2]
info = {name: "Tom", age: 18} # keys are symbol
puts info[:name]
以下為Hash的內建方法:
No. Methods & Description
1 hash ==(<,<=,>,>=,) other_hash: 比較是否相等或是是否互為子集合
numbers = Hash["one", 1, "two", 2, "three", 3]
others = Hash[[["two", 2],["one",1]]]
puts numbers > others
2 hash.[key]: 根據key取得hash內value
numbers = Hash["one", 1, "two", 2, "three", 3]
puts numbers["three"], numbers["five"]==nil
3 hash.[key] = value: 修改對應key之value或新增key及value
numbers = Hash["one", 1, "two", 2, "three", 3]
numbers["four"] = 4
numbers["three"] = 300
numbers.store("five", 5)
puts numbers
4 hash.any?: 若hash內非所有皆傳回false或nil則傳回true,若之後有block則針對hash內每一個元素
numbers = Hash["one", 1, "two", 2, "three", 3]
numbers.any?{|(key, value)| print key, "=>", value, "\n"}

for k,v in numbers
    print k, "=>", v, "\n"
end

puts numbers.any?
5 hash.assoc(obj): 傳回hash包含key, value的array,若無該key則傳回nil
numbers = Hash["one", 1, "two", 2, "three", 3, "four" , [1,2,3,4]]
print numbers.assoc("two")
print numbers.assoc("four")
print numbers.assoc("five") == nil
6 hash.clear: 清空hash內容
numbers = Hash["one", 1, "two", 2, "three", 3, "four" , [1,2,3,4]]
print numbers.clear
7 hash.compact: 傳回去除nil值的hash
numbers = Hash["one", 1, "two", 2, "three", nil, "four" , [1,2,3,4]]
print numbers.compact
print numbers
8 hash.compact: 傳回去除nil值的hash in place
numbers = Hash["one", 1, "two", 2, "three", nil, "four" , [1,2,3,4]]
numbers.compact!
print numbers
9 hash.compare_by_identity: 表示之後找key是根據其object_id(使用equal?),而非僅看其值(eql?)。compare_by_identity?表示判斷是否使用object_id來選擇key。下例中可看出兩個one的id並不相等(因為使用dup方法複製)。此方法可讓hash內有相同值的key(若有相同值的key可讓其value為一array 應較為適當)。
numbers = Hash["one" => 1, "two" => 2, :three => 3]
puts numbers.compare_by_identity
puts numbers.compare_by_identity?
puts numbers["one".dup] == nil
puts numbers[:three] == nil
numbers["one".dup] = 1
puts numbers
puts numbers.any?{|k,v| print k,"->",k.object_id,"\n"}
10 hash.default(key = nil): 如果key不存在,傳回預設值
h1 = Hash.new
puts h1.default==nil, "\n"
h2 = Hash.new(2)
puts h2.default, h2.default(2), h2["whatever"], "\n"
h2 = Hash.new {|h,k| h2[k] = k.to_i*10}
puts h2.default==nil, h2.default(2), h2[5]
puts h2
11 hash.default = obj: 設定hash的預設值
h1 = Hash.new
h1.default="The default value"
puts h1["1"], h1.default
12 hash.default_proc: 如果hash宣告時包含block,傳回block,否則傳回nil
h = Hash.new {|h,k| h[k] = k.to_i*k.to_i}
p = h.default_proc
a = {}
p.call(a,"2")
p.call(a,"3")
print a
13 hash.default_proc = proc_obj or nil: 設定hash的default_proc為一個proc物件(proc_obj)或nil
h = Hash.new 
h.default_proc = proc do |hash, key| 
    hash[key] = key+key
end # or h.default_proc = proc {|hash, key| hash[key] = key+key} 來建立一個小函數(proc物件)
h[3]
h["hi"]
print h
14 hash.delete(key) [or] hash.delete(key) { |key| block }: 根據某key刪除其key-value對,若key不存在則傳回nil或block
h = {one: 1, two: 2, three: 3}
puts h.delete(:three)
puts h.delete(:three) == nil
puts h.delete(:three) {|k| "#{k} not found"}
print h
15 hash.delete_if { |key,value| block }: 刪除hash中如果block為true的元素
h1 = {a: 100, b: 200, c: 300}
puts h1.delete_if {|k,v| k >= :b}
16 hash.dig(key...): 取出巢狀結構之key物件所表示的值,如果中間步驟是nil則傳回nil
h = {one: {two: {three: "The Value"}}}
puts h.dig(:one, :two, :three)
puts h.dig(:one, :two, :five) == nil
puts "------------------------------"
h1 = {arr: [1,2,{ar: [3,4,5]}]}
puts h1.dig(:arr, 1)
puts h1.dig(:arr, 2, :ar, 2)
puts h1.dig(:arr, 1, 0)
17 hash.each { |key,value| block } [or] hash.each_pair { |key,value| block } [or] hash.each [or] hash.each_pair: 針對每一個key, value對
h = {one: 1, two: 2, three: 3}
h.each {|key, value| print key,"->",value,"\n"}
a = []
h.each_pair{|key, value| a << key << value}
print a,"\n---------------\n"

for k,v in h.each # 傳回enumerator
    print k,"<->",v,"\n"
end
for k,v in h.each_pair # 傳回enumerator
    a << k << v
end
print a
18 hash.each_key { |key| block }: 針對每一個key
h = {one: 1, two: 2, three: 3}
h.each_key {|key| puts key}
for key in h.each_key
    print key,"->",h[key],"\n"
end
19 hash.each_value { |value| block }: 針對每一個value
h = {one: 1, two: 2, three: 3}
h.each_value {|value| puts value}
for value in h.each_value
    puts value
end
20 hash.empty?: 檢察hash是否內容為空,return boolean
h = {one: 1, two: 2, three: 3}
puts h.empty?
h1 = Hash.new # or h1={}
puts h1.empty?
21 hash.eql?(another_hash): 檢查hash與another_hash是否相等
h = {one: 1, two: 2, three: 3}
h1 = Hash.new
h2={}
puts h.eql?(h1), h1.eql?(h2)
22 hash.fetch(key [, default] ) [or] hash.fetch(key) { | key | block }: 取得hash內對應key之值,可處理key不存在情況
h = {one: 1, two: 2, three: 3}
puts h.fetch(:one)
puts h.fetch(:five, "200") # if key cannot be found
puts h.fetch(:five) {|el| "cannot find key = #{el}"} # if key cannot be found
print h
23 hash.fetch_values(key, ...) [or] hash.fetch_values(key,...) {|key| block}: 傳回包含對應key之value的陣列
h = {"one" => "uno", "two" => "dos", "three" => "tres"}
print h.fetch_values("one", "two") # 取得對應value之陣列
print h.fetch_values("one", "five") {|key| key.upcase} # key不存在執行block
puts h.fetch_values("one", "five") # key不存在傳回錯誤(KeyError)
24 hash.flatten [or] hash.flatten(level): flatten整個hash
h = {"one" => ["uno", "primero"], "two" => ["dos", "segundo"], "three" => ["tres","tercero"]}
print h.flatten, "\n"
print h.flatten(2) # flatten to 2nd level
25 hash.has_key?(key) [or] hash.include?(key) [or] hash.key?(key) [or] hash.member?(key): 檢查hash是否包含key
h = {"one" => "uno", "two" => "dos", "three" => "tres"}
puts h.has_key?("one"), h.has_key?("five")
puts h.key?("one"), h.key?("five")
puts h.include?("one"), h.include?("five")
puts h.member?("one"), h.member?("five")
26 hash.has_value?(value): 檢查hash是否包含value
h = {"one" => "uno", "two" => "dos", "three" => "tres"}
puts h.has_value?("uno"), h.has_value?("cinco")
27 hash.hash: 計算hash的hashcode
h = {"one" => "uno", "two" => "dos", "three" => "tres"}
puts h.hash
28 hash.inspect: 傳回能print的字串
h = {"one" => "uno", "two" => "dos", "three" => "tres"}
puts h.inspect
print h
29 hash.invert: 傳回一key, value對換的hash
h = {"one" => "uno", "two" => "dos", "three" => "tres"}
puts h.invert
h1 = {"1" => 1, "2" => 2, "3" => 1}
puts h1.invert, h1.invert.size==h1.size
30 hash.keep_if {|key, value| block}: 刪除不符合block條件的元素對
h = {"one" => 1, "two" => 2, "three" => 3}
puts h.keep_if {|key, value| value >= 2}
print h
31 hash.key(value): 傳回對應value的key
h = {"one" => 1, "two" => 2, "three" => 3}
puts h.key(2)
32 hash.keys: 傳回包含所有key的array
h = {"one" => 1, "two" => 2, "three" => 3}
print h.keys
33 hash.length: 傳回hash的長度
h = {"one" => 1, "two" => 2, "three" => 3}
print h.length
34 hash.merge(other_hash) [or] hash.merge(other_hash) { |key, oldval, newval| block }: 合併hash與other_hash,block處理重複key發生情況
h1 = {"one" => 1, "two" => 2, "three" => 3}
h2 = Hash["four", 4, "five", 5, "six", 6, "one", 100]
puts h1.merge(h2) #無block直接合併,有重複的key取h2
puts h1.merge(h2) {|key, oldval, newval| oldval+newval} #有重複的key執行block
35 hash.merge!(other_hash) [or] hash.merge!(other_hash) { |key, oldval, newval| block }: 合併hash與other_hash in place,block處理重複key發生情況
h1 = {"one" => 1, "two" => 2, "three" => 3}
h2 = Hash["four", 4, "five", 5, "six", 6, "one", 100]
h1.merge!(h2) #無block直接合併,有重複的key取h2
print h1,"\n" #此時h1已改變
puts h1.merge!(h2) {|key, oldval, newval| oldval+newval} #有重複的key執行block
36 hash.rassoc(obj): 傳回第一個match obj之value的元素對
h = {"one" => 1, "two" => 2, "three" => 3, "anothertwo" => 2}
print h.rassoc(2), h.rassoc(5)==nil #傳回第一個match的value之元素對,沒match傳回nil
37 hash.rehash: 如果hash的key物件被改變,重新使用新key建立hash
a = ["one", "two"]
b = ["three", "four"]
h = {a => 100, b => 200}
a[0] = "1"
puts h[a]==nil
puts h.rehash
puts h[a]
38 hash.reject { |key, value| block }: 刪除符合block條件的元素對
h = {"one" => 1, "two" => 2, "three" => 3}
puts h.reject {|key, value| value < 2 || key > "tw"} #對應keep_if方法
print h
39 hash.reject! { |key, value| block }: 刪除符合block條件的元素對in place
h = {"one" => 1, "two" => 2, "three" => 3}
h.reject! {|key, value| value < 2 || key > "tw"} #對應keep_if方法
print h
40 hash.replace(other_hash): 使用other_hash的內容替換hash的內容
h1 = {"one" => 1, "two" => 2, "three" => 3}
h2 = {uno: 1, dos: 2, tres: 3}
puts h1.replace(h2)
41 hash.select { |key, value| block }: 傳回一個符合block條件之new hash
h = {"one" => 1, "two" => 2, "three" => 3}
puts h.select {|key, value| value >= 2}
puts h.select {|key, value| key >= "t"}
puts h
42 hash.select! { |key, value| block }: 傳回一個符合block條件之new hash in place
h = {"one" => 1, "two" => 2, "three" => 3}
h.select! {|key, value| value >= 2}
puts h
43 hash.shift: 移除hash中一個元素對,並傳回一two-element array
h = {"one" => 1, "two" => 2, "three" => 3}
print h.shift
puts h
h1 = Hash.new("The default value")
print h1.shift
44 hash.size: 傳回hash的大小(length)
h = {"one" => 1, "two" => 2, "three" => 3}
puts h.size, h.length #等同.length
45 hash.slice(*keys): 傳回包含*keys的new hash
h = {"one" => 1, "two" => 2, "three" => 3}
puts h.slice("two", "three")
h1 = {one: 1, two: 2, three: 3}
puts h1.slice(:one, :two)
46 hash.sort: 傳回一個包含hash元素對的2-D array並排序
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
print h.sort
47 hash.store(key, value): 修改key對應的value並傳回新value
h = {"one" => 1, "two" => 2, "three" => 3}
puts h.store("one", 100) #傳回new value
puts h.store("four", 4) #加入新的元素對
h["two"] = 200
puts h
48 hash.to_a: 轉換成array
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
print h.to_a
49 hash.to_hash [or] hash.to_h: 傳回hash自身
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
print h.to_hash, "\n", h.to_h
50 hash.to_proc: 專換成函數(proc)
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
print h.to_proc.call("one") #專換成函數(proc)可使用call呼叫
print ["one", "two", "three"].map(&h) #加上&在h之前代表使用proc作為block,同下
print ["one", "two", "three"].map {|m| h[m]}
51 hash.to_s: 轉換成字串
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
puts h.to_s, h.inspect #等同.inspect
52 hash.transform_keys {|key| block} [or] hash.transform_keys.with_index {|key, index| block}: 轉換keys
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
g = h.transform_keys {|key| key.to_sym}
f = h.transform_keys.with_index {|key, index| "#{key}.#{index+1}"}
e = g.transform_keys(&:to_s) #等同 e = g.transform_keys {|key| key.to_s}
puts e, f, g, h
53 hash.transform_keys! {|key| block} [or] hash.transform_keys!.with_index {|key, index| block}: 轉換keys in place
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
h.transform_keys! {|key| key.to_sym}
puts h
h.transform_keys!(&:to_s)
puts h
h.transform_keys!.with_index {|key, index| "#{key}.#{index+1}"}
puts h
54 hash.transform_values {|value| block} [or] hash.transform_values.with_index {|value, index| block}: 轉換values
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
puts h.transform_values{|value| value*value}
puts h.transform_values(&:to_s)
puts h.transform_values.with_index{|value, index| "#{value}.#{index}"}
55 hash.transform_values! {|value| block} [or] hash.transform_values!.with_index {|value, index| block}: 轉換values in place
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
h.transform_values!{|value| value*value}
puts h
h.transform_values!(&:to_s)
puts h
h.transform_values!.with_index{|value, index| "#{value}.#{index}"}
puts h
56 hash.update(other_hash) [or] hash.update(other_hash) {|key, oldval, newval| block}: 使用other_hash來更新hash,原則上就是merge
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
h1 = Hash["one", "uno", "two", "dos", "five", "cinco"]
puts h.update(h1)
h2 = {"one" => "ichi", "six" => "roku"}
puts h.update(h2) {|key, v1, v2| "#{key}: #{v1+","+v2}"} #處理相同key的情況
57 hash.value?(value): 判定value是否存在於hash
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
puts h.value?(1), h.value?(5)
58 hash.values: 傳回所有value組成的array
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
print h.values, h.keys
59 hash.values_at(obj, ...): 傳回array包含所有給定key所對應的values
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
print h.values_at("one", "three", "five")

blocks & functions

在ruby中將一些程式區塊稱為block,在之前也使用過,這裡整理說明一下,例如之前提到array或hash有.each方法:
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
sum = 0
h.each do |key, value|
    sum += value
end
print "#{sum}"
其中的do...end之間的程式碼稱為一個block,因為.each是針對hash內的每一個元素對,所以block會執行四次。我們也可以使用大括弧{}來表示block,例如:
h = {"one" => 1, "two" => 2, "three" => 3, "four" => 4}
sum = 0
h.each {|key, value| sum += value}
print "#{sum}"
通常{}是內容比較簡短時的寫法,do...end則是程式碼較長時使用。像是
3.times {puts "Hello"}
不過也不是完全相同,例如:
print [*1..10].map {|i| i*i} #此相當於print([*1..10].map {|i| i*i})
print [*1..10].map do |i| i*i end #此相當於print([*1..10].map) do |i| i*i end
因為{}的執行優先於print,所以先執行完才印,而do的執行後於print,所以產生了一個enumerator。第二的方式可以修改如下即可:
k = [*1..10].map do |i| i*i end
print k
類似的用法也用於其他方法,例如map(或collect):
arr = Array(1..10)
puts arr.map {|value| value*value} # 使用map!(in place)來直接修改arr
index = 0
arr.map do |value| #針對每一個arr內的value執行此block
    arr[index] = value*value
    index += 1
end
print arr
我們可以在呼叫函數時給定一個block,此block中內容在函數中會被yield關鍵字呼叫並執行(有點像是函數,但並不相同,僅是分離出某片段程式碼,若此block會被多次使用最佳),例如:
def funcname #函數funcname
    yield #使用yield關鍵字呼叫block來執行block中的code
end
funcname { # 與函數同名的block
    for _ in 1..3
        puts "codes of block"
    end
}
也可以給block加上輸入參數:
def funcname #函數funcname
    yield("Tom", 18) #使用yield呼叫block來執行block中的code
end
funcname {|name, age| puts "#{name} is #{age} years old."}
或是
def funcname #函數funcname
    yield(10) #使用yield呼叫block來執行block中的code
    yield(20)
end
funcname do |n|
    sum = 0
    for i in 1..n
        sum += i
    end
    puts sum
end
跟函數類似,最後的結果可做為傳回值(不過不可以使用return),例如:
def prime(alist)
    result = []
    alist.each do |i|
        result << i if yield(i)
    end
    result
end
k = prime([*(2..100)]) do |x| #[*(2..100)] 等同於 (2..100).to_a 或 Array(2..100)
    isPrim = true
    for i in 2..(x-1)
        if x%i == 0
            isPrim = false
            break
        end
    end
    isPrim
end
print k
我們可以使用block_given?函數來判斷是否有給block再來決定如何進行,例如:
def funcname 
    if block_given?
        yield
    else
        puts "No block given"
    end
end
funcname #呼叫函數funcname
funcname {puts "Inside the block"}
至於block中的變數可以觀察以下兩個小例子:
n = 5
5.times {|n| puts "inside block #{n}"}
puts "outside block #{n}"

sum = 0
50.times {|n| sum += n}
puts "sum = #{sum}"
第一個例子可以看出block中的變數是local variable,而第二個例子中sum是外部變數,而block內的變數(n)與sum不同,所以可以在block中被改變。Ruby還可以使用內定的BEGIN與END block來建立程式的前後區塊,例如:
END {
    puts "End of program..."
}
BEGIN {
   puts "Initializing...";
}
puts "Main content"

block並不是物件,但是可以將其轉換成Proc物件,如前所述,可以使用lambda產生Proc物件或直接建立Proc物件,例如:
def hello
    lamfunc = lambda {puts "hi, three"}
    lamfunc.call
    proc = Proc.new {puts "hello again"}
    proc.call
end
hello
我們可以將Proc物件做為函數的傳入值,例如:
func = lambda do |n| # This is a Proc object
    sum = 0
    (1..n).each {|i| sum += i}
    return sum
end

def accumulation(n, proc) # Proc func as an argument
    puts "1+...+#{n} = #{proc.call(n)}" #puts proc.call(n)
end
accumulation(10, func) # Proc func as an argument
根據這個方式,我們可以將之前的質數函數改寫如下:
def primes(alist, proc)
    result = []
    alist.each do |i|
        result << i if proc.call(i)
    end
    result
end
k = lambda do |x| #check if x is a prime?
    isPrim = true
    for i in 2..(x-1)
        if x%i == 0
            isPrim = false
            break
        end
    end
    isPrim
end
print primes([*2..100], k)
不過事實上可以寫成如下:
def primes(alist, &proc)
    result = []
    alist.each do |i|
        result << i if proc[i] # 等同proc.call(i) or proc.call i
    end
    result
end
k = primes([*2..100]) do |x| #check if x is a prime?
    isPrim = true
    for i in 2..(x-1)
        if x%i == 0
            isPrim = false
            break
        end
    end
    isPrim
end

print k
也就是說我們將呼叫函數之後的block,使用&符號將其轉換成Proc物件,然後做為傳入參數,這是之前使用的函數block之改寫。這個例子可能有點長,再練習一個簡短的例子:
def hi(name)
    puts "hi, #{yield(name)}"
end
hi("World") {|name| name}
puts "---------分隔線---------"
def hello(name, &proc)
    puts "Greeting, #{proc.call name}"
end
hello("World") {|name| name}
在這個例子中,原來的寫法是hi,新的寫法是hello。要記得&block這個參數都是最後一個參數。
雖說lambda與Proc.new似乎都相同,不過也不盡相同,在Proc中,不可使用return來傳回,但是lambda卻是可以,如下:
def procE
    puts "Beginning..."
    proc = Proc.new {return 10} # but you can use: proc = Proc.new {10}
    puts proc.call
    puts "Complete..."
end
procE
def lambdaE
    func = -> {return 10}
    puts func.call
end
lambdaE
也就是說在Proc內return等同於在method中return。通常我們都是使用.call來執行,不過也可以使用其他方式,例如:
hello = Proc.new {|name| puts "Hello, #{name}"}
hello.call("World!")
hello.("World!")
hello["World!"]
hello === "World!"
hello.yield "World!"

Math, Date&Time

數學符號與函數使用Math模組(module),與其他語言相同,主要有兩個常數,E&PI:
puts Math::E, Math::PI
要注意使用::符號表示E&PI是Math這個模組內或是namespace中的常數。以下為包含之方法:
puts Math.sqrt(25)
1 acos(x)->Float, Domain:[-1,1]=>[0,PI]
puts Math.acos(-1) #3.141592653589793
2 acosh(x)->Float, Domain:[1,INFINITY)=>[0,INFINITY)
puts Math.acosh(Float::INFINITY) #NaN
3 asin(x)->Float, Domain:[-1,1]=>[-PI/2,PI/2]
4 asinh(x)->Float, Domain:(-INFINITY,INFINITY)=>(-INFINITY,INFINITY)
5 atan(x)->Float, Domain:(-INFINITY,INFINITY)=>(-PI/2,PI/2)
6 atan2(y,x)->Float, Domain:(-INFINITY,INFINITY)=>[-PI,PI]
7 atanh(x)->Float, Domain:(-1,1)=>(-INFINITY,INFINITY)
8 cbrt(x)->Float, Domain:(-INFINITY,INFINITY)=>(-INFINITY,INFINITY)
-9.upto(9) do |x| ##三次方根
    print [x, Math.cbrt(x), Math.cbrt(x)**3]
end
9 cos(x)->Float, Domain:(-INFINITY,INFINITY)=>[-1,1]
10 cosh(x)->Float, Domain:(-INFINITY,INFINITY)=>[1,INFINITY)
11 erf(x)->Float, Domain:(-INFINITY,INFINITY)=>(-1,1)
12 erfc(x)->Float, Domain:(-INFINITY, INFINITY)=>(0,2)
13 exp(x)->Float, Domain:(-INFINITY,INFINITY)=>(0,INFINITY)
puts Math.exp(2) #Math::E**2 => 7.38905609893065
14 frexp(x)->[fraction, exponent]
puts Math.frexp(100) #[0.78125, 7] => 0.78125*(2**7) = 100
15 gamma(x)->Float
16 hypot(x,y)->Float
puts Math.hypot(10,10) #至原點(0,0)的距離
17 ldexp(fraction, exponent)->Float
a, b = Math.frexp(100)
print Math.ldexp(a,b)
18 lgamma(x)->[Float, -1 or 1]
19 log(*args)
puts Math.log(Math::E)
20 log10(x)->Float
puts Math.log10(100)
21 log2(x)->Float
puts Math.log2(32)
22 sin(x)->Float, Domain:(-INFINITY,INFINITY)=>[-1,1]
puts Math.sin(Math::PI/6)
23 sinh(x)->Float, Domain:(-INFINITY,INFINITY)=>(-INFINITY,INFINITY)
24 sqrt(x)->Float, Domain:[0,INFINITY)=>[0,INFINITY)
25 tan(x)->Float, Domain:(-INFINITY,INFINITY)=>(-INFINITY,INFINITY)
26 tanh(x)->Float, Domain:(-INFINITY,INFINITY)=>(-1,1)
如果要產生隨機數,可以使用rand來產生0-1之間的實數,但若是要產生整數,可以在rand加上數值參數來產生0到該數值間的整數亂數,例如:
puts rand, rand(100)

Time
運算時間使用Time類別,若欲得到目前時間可以使用:
currentTime1 = Time.new # Time object
currentTime2 = Time.now # Time object
puts currentTime1, currentTime2.inspect #Time object & String
若欲取得時間的各部分,方式如下:
now = Time.now
puts now.year, now.month, now.day, now.hour, now.min, now.sec, now.usec, now.zone #usec means microseconds(999999)
puts now.wday, now.yday # day of week and day of year
若欲格式化時間,可以使用以下方法:
now = Time.now
puts Time.local(now.year, now.month, now.day) #2019-07-13 00:00:00 +0800
puts Time.local(now.year, now.month, now.day, now.hour, now.min) #2019-07-13 13:52:00 +0800
puts Time.utc(now.year, now.month, now.day, now.hour, now.min) #2019-07-13 13:52:00 UTC
puts Time.gm(now.year, now.month, now.day, now.hour, now.min) #2019-07-13 13:52:00 UTC
print now.to_a #[43, 54, 13, 13, 7, 2019, 6, 194, false, "台北標準時間"]
還可以如此顯示不同時間格式:
now = Time.now
puts now.to_s #2019-07-13 14:01:05 +0800
puts now.ctime #Sat Jul 13 14:01:05 2019
puts now.localtime #2019-07-13 14:01:05 +0800
puts now.strftime("%Y-%m-%d %H:%M:%S") #2019-07-13 14:01:05
至於strftime中所使用的符號如以下表格:
1 %a: The abbreviated weekday name (Sun).
2 %A: The full weekday name (Sunday).
3 %b: The abbreviated month name (Jan).
4 %B: The full month name (January).
5 %c: The preferred local date and time representation.
6 %d: Day of the month (01 to 31).
7 %H: Hour of the day, 24-hour clock (00 to 23).
8 %I: Hour of the day, 12-hour clock (01 to 12).
9 %j: Day of the year (001 to 366).
10 %m: Month of the year (01 to 12).
11 %M: Minute of the hour (00 to 59).
12 %p: Meridian indicator (AM or PM).
13 %S: Second of the minute (00 to 60).
14 %U: Week number of the current year, starting with the first Sunday as the first day of the first week (00 to 53).
15 %W: Week number of the current year, starting with the first Monday as the first day of the first week (00 to 53).
16 %w: Day of the week (Sunday is 0, 0 to 6).
17 %x: Preferred representation for the date alone, no time.
18 %X: Preferred representation for the time alone, no date.
19 %y: Year without a century (00 to 99).
20 %Y: Year with century.
21 %Z: Time zone name.
22 %%: Literal % character.

時間是可以加減的,使用+、-符號即可:
now = Time.now
tenMinAgo = now - 10*60 # 單位為seconds
oneHourLater = now + 60*60
puts tenMinAgo, now, oneHourLater
difference = oneHourLater-tenMinAgo
puts difference, difference.class # 時間差為實數

Class & Object

在ruby中物件類別的建立跟很多其他語言類似,都是使用關鍵字class,例如:
class Greeting
    def hi
        puts "Hi"
    end
end
g = Greeting.new
g.hi
已經很熟悉的使用new來建構新物件,然後直接呼叫class內的方法。通常我們會需要建構子(constructor),在ruby中,定義initialize這個方法作為建構子,例如:
class Greeting
    @@nice = "Nice to meet you."
    def initialize(name)
        @name = name
    end
    def hi
        print "Hi ", @name, ", ", @@nice, "\n"
    end
    puts @@nice, @name #@name==nil
end
g = Greeting.new "Tom"
g.hi
建構時傳入name,不過為何會有@name這樣的名稱呢?記得我們在變數的地方提到的Instance variable,也就是說ruby在class的方法內的變數需要使用@開頭來定義,如果我們將剛才的例子改為aname=name,將會出現錯誤。而我們之前也提到加上@@的變數稱為Class variable,也就是在整個class內使用,如果我們測試最後的@name==nil,會發現它傳回true(因為還沒初始化)。你可以試著將@@nice改為@nice,會發現在class內可以使用,但在method內卻是nil。現在將method hi修改如下:
    def hi
        @how = "How are you doing today?"
        print "Hi ", @name, ", ", @@nice, "\n"
        puts "#{@how}"
    end
此處可以將@how改為how,或是改為@@how也可正常運作(通常不會這樣做,因為不是定義在class下,在外部也無法使用)。最後再舉個例子,將之前提到的Global variable也納入:
$pi = Math::PI
class Polygon
    @@height = 20
    COLOR = "pink"
    def initialize(width)
        @width = width
    end
    def squareArea
        @width*@@height
    end
    def circleArea
        @width**2*$pi
    end
    def ellipseArea
        @width*@@height*$pi
    end
    def to_s
        "A #{COLOR} polygon, height: #{@@height}, width: #{@width}"
    end
end
poly = Polygon.new 10
puts poly.squareArea, poly.circleArea, poly.ellipseArea
puts Polygon::COLOR # pink
puts poly # A pink polygon, height: 20, width: 10
這例子顯然有點穿鑿附會,我們讓高度定為20,讓pi成為global variable,包含一個常數(constant)COLOR。而to_s則跟Python的__str__與__repr__類似,可以讓我們直接印出資訊。可以發現當我們要取得定義的常數COLOR時,使用::這個符號,這就是使用過的Math::PI的由來了。接下來再看一個例子:
class Student
    @@school = "NKUST"
    def initialize(name="Noname", id, department)
        @id = id
        @department = department
        @name = name
    end
    def self.info
        "Info of student in #{@@school}"
    end
    def to_s
        "Name: #{@name}, ID: #{@id}, Department: #{@department}"
    end
    def method_missing(m, *args, &block)
        puts "No such method \"#{m}\", try again"
    end
end
tom = Student.new "Tom", 1, "LM"
puts Student.info
puts tom
puts tom.age
首先看到其中的self.info這個方法,加上self甚麼意思?這跟Java的方法加上static一樣,需要使用classname.methodname來呼叫(稱之為class method,其它需要new一個instance才能使用的則稱之為instance method)。而method_missing()則是當呼叫不存在的method時所做的反應,若無此方法則會直接傳回錯誤。在ruby的物件,我們無法像其他語言直接使用像是tom.name這樣的語法來取得變數值,其實也不盡然,例如使用
puts tom.instance_variable_get :@name
puts tom.instance_variable_get :@id
puts tom.instance_variable_get :@department
,所以我們需要建立方法來讓其傳回,例如在上例中加上getters&setters:
def getId
    @id
end
def setId(newid)
    @id=newid
end
然後試試看:
puts tom.getId
tom.setId(2)
puts tom.getId
當我們的class內有許多參數時,寫getters&setters可就相當花時間與篇幅了,此時我們可以使用attr_accessor,可以自動幫我們完成getter與setter的方法,例如:
class Student
    attr_accessor :name, :id, :department
    @@school = "NKUST"
    def initialize(name="Noname", id, department)
        @id = id
        @department = department
        @name = name
    end
    def to_s
        return "Name: #{@name}, ID: #{@id}, Department: #{@department}"
    end
end
s = Student.new("Tom", 1, "LM")
puts s.name, s.id, s.department
s.name = "Mary"
s.id = 2
s.department = "LM"
puts s
也可以使用attr_reader與attr_writer,這兩者分別會產生getter與setter(可自行修改上述的例子觀察),不像attr_accessor會同時都產生。而使用了attr_accessor後,還可以自己建立getter或setter嗎?答案是肯定的。因為ruby中的東西都是物件,所以當我們說tom.id=18,實際上是有一個叫做id=的方法,應該是tom.id=(18),只是把小括號省略了,所以在上例中加上如下方法:
    def id=(newId)
        if newId < 0
            puts "Age has to be positive."
        else
            @id = newId
        end
    end
現在再使用
s.name = "Mary"
s.id = -2
s.department = "LM"
puts s
便會看到顯示的錯誤訊息了。至於要設定方法的存取權限,使用 使用方式則例如:
class AccessMethod
    def publicM
        "This is a public method"
    end
    protected
    def protectedM
        "This is a protected method"
    end
    private
    def privateM
        "This is a private method"
    end
end
acc = AccessMethod.new
puts acc.publicM
#puts acc.protectedM ## protected method `get2' called (NoMethodError)
#puts acc.privateM ## private method `privateM' called (NoMethodError)
可以看到除了public方法之外都無法呼叫。宣告方法的accessibility還可以如下:
class AccessMethod
    def publicM
        "This is a public method + " + protectedM + " + " + privateM
        # protect method can also be called: self.protectedM or send(:protectedM) or self.send(:protectedM)
        # privated method called can also be: self.send(:privateM)
    end
    protected
    def protectedM
        "(protected method + " + self.send(:privateM)+")"
    end
    private
    def privateM
        "private method"
    end
    # protected :protectedM
    # private :privateM
end
acc = AccessMethod.new
puts acc.publicM
puts acc.send(:protectedM)
puts acc.send(:privateM)
self是指自己,也就是class本身。而我們不能使用self.privateM這樣的方式來使用,因為不能有receiver(在privateM之前不能有小數點)。而private方法也不是都無法在外部使用,我們可以使用send()來access。

Duck type

If it walks, quacks and swims like a duck then it must be a duck. In other words if an object has the correct method then it must be the correct object. Duck typing judges two types to be equivalent if they both have same methods. 因為ruby沒有像Java的interface,duck type讓我們可以讓有相同方法的class變成相同型態。例如:
class C1
    def mOne
        puts "mOne in C1"
    end
end
class C2
    def mOne
        puts "mOne in C2"
    end
end
arrayC = [C1.new, C2.new]
arrayC[0].mOne # mOne in C1
arrayC[1].mOne # mOne in C2
這樣的做法在Java顯然是不可行的。再看一例:
class Car
    attr_accessor :engine, :tires, :airConditioner
    def initialize(engine, tires, airConditioner)
        @engine = engine
        @tires = tires
        @airConditioner = airConditioner
    end
    def routineService(staffs)
        staffs.each do |staffs|
            staffs.exam(self)
        end
    end
end
class EngineMechanic
    def exam(car)
        puts "exam #{car.engine}"
    end
end
class TiresMechanic
    def exam(car)
        puts "exam #{car.tires}"
    end
end    
class ACMechanic
    def exam(car)
        puts "exam #{car.airConditioner}"
    end
end 
car = Car.new("engine brand", "tires brand", "AC brand")
em = EngineMechanic.new
tm = TiresMechanic.new
am = ACMechanic.new
car.routineService([em, tm, am])
此例中有一car class,其中變數包含引擎、輪胎、冷氣。另外三個class分別是各部位的維修技師,三者皆有一exam(car)方法,我們在class car內設計一方法routineService,將三個維修技師的instance做成array傳入,三者皆會執行exam方法來檢查汽車,但是檢查不同部位。

繼承(Inheritance)

ruby繼承的符號就是簡單的<,例如:
class Car
    attr_accessor :wheels, :seats
    def initialize(wheels, seats)
        @wheels, @seats = wheels, seats
    end
    def to_s
        "A #{@wheels} wheels car with #{@seats} seats"
    end
end
class Bike < Car
    attr_accessor :maxSpeed
    def initialize wheels, seats, maxSpeed
        super wheels, seats
        @maxSpeed = maxSpeed
    end
    def ride
        %Q(Riding a bike with #{@wheels} wheels and max speed is #{@maxSpeed})
    end
    def to_s
        "A #{@wheels} wheels car with #{@seats} seats and max speed #{maxSpeed} kph"
    end
end
bike = Bike.new(2, 2, 35)
puts bike.ride
puts bike
使用super關鍵字來取得被繼承class之建構內容。相同名稱的method會被override(overriding),所以顯示bike內的to_s內容。

Polymorphism

多形(Polymorphism)在繼承原則上是說繼承自相同class的眾多subclass可以有相同名稱但不同內容的方法,例如:
class Car
    attr_accessor :wheels, :seats
    def initialize(wheels, seats)
        @wheels, @seats = wheels, seats
    end
    def to_s
        "A #{@wheels} wheels car with #{@seats} seats"
    end
end
class MyBenz < Car
    def price
        "Expensive..."
    end
end
class MyMaserati < Car
    def price
        "Even more expensive..."
    end
end
mb = MyBenz.new 4, 5
mm = MyMaserati.new 4, 5
puts mb.price # Expensive...
puts mm.price # Even more expensive...

Open class

這是一個特別的用法,先看下例,首先將以下class加到剛剛的例子:
class Bike
    def bikeColor(color)
        "I hava a #{color} bike."
    end
end
然後執行改為:
bike = Bike.new(2, 2, 35)
puts bike.bikeColor("blue") # I hava a blue bike.
puts bike.ride # Riding a bike with 2 wheels and max speed is 35
根據顯示的結果,我們將發現程式中有兩個相同名稱的class,他們將會合併在一起。這是一個特殊的使用,目的是讓我們可以修改之前的class方法。若是有相同名稱的方法定義在後面的class,則使用後面定義的方法。例如:
class Bike
    def bikeColor(color)
        "I hava a #{color} bike."
    end
    def ride
        %Q(Riding a bike so hard on the field...)
    end
end
bike = Bike.new(2, 2, 35)
puts bike.bikeColor("blue") # I hava a blue bike.
puts bike.ride # Riding a bike so hard on the field...
而若是也想要使用原來的方法,可以將其更名:
class Bike
    def bikeColor(color)
        "I hava a #{color} bike."
    end
    alias_method :oride, :ride
    def ride
        %Q(Riding a bike so hard on the field...)
    end
end
bike = Bike.new(2, 2, 35)
puts bike.bikeColor("blue") # I hava a blue bike.
puts bike.oride # Riding a bike with 2 wheels and max speed is 35
puts bike.ride # Riding a bike so hard on the field...
這樣的形式稱為開放類別,讓我們可以修改之前已存在但是不符合我們需要的方法,甚至程式內定的方法:
class String
    def hi
        "Hello, #{self}. How are you?"
    end
end
puts "Tom".hi # Hello, Tom. How are you?
puts "Mary".hi # Hello, Mary. How are you?
或是
class Integer
    def days
        self*24*60*60
    end
    def ago
        Time.now - self
    end
end
puts 3.days.ago #2019-07-12 11:32:42 +0800

freezing

有些變數數值當我們建立它時便不期待在修改其值,例如Hash的key或常數,我們稱為freezing,如下例:
HI = "Hello"
HI << " ,Tom"
puts HI
如果把上例中的HI設為freeze,那麼便無法再修改,例如:
HI = "Hello".freeze
puts HI.frozen? # true
HI << " ,Tom" # can't modify frozen String (FrozenError)
puts HI
frozen?方法可以確認是否是frozen狀態。當我們建立class時,若不希望傳入的參數再有變動,可以使用freeze關鍵字例如:
class FreezeVar
    attr_accessor :x, :y
    def initialize x, y
        @x = x
        @y = y
        freeze
    end
    def to_s
        "x: #{@x}, y: #{@y}"
    end
end
fv = FreezeVar.new 1,2
puts fv
puts fv.x.frozen?, fv.y.frozen?
fv.x = 10 # can't modify frozen FreezeVar (FrozenError)
fv.y = 20 # can't modify frozen FreezeVar (FrozenError)
puts fv.x, fv.y
這個例子內的x與y都是frozen,所以皆不能再被修改。也可以這樣做:
class FreezeVar
    attr_accessor :x, :y
    def initialize x, y
        @x = x
        @y = y
    end
    def to_s
        "x: #{@x}, y: #{@y}"
    end
end
fv = FreezeVar.new 1,2
fv.freeze # freeze the class
puts (fv.frozen?)? "frozen" : "Not frozen"
fv.x = 10 # can't modify frozen FreezeVar (FrozenError)
fv.y = 20 # can't modify frozen FreezeVar (FrozenError)
puts fv.x, fv.y
將整個class都freeze起來。利用這個例子順帶一提,我們也可以使用allocate關鍵字來宣告一個instance,但是其中變數值皆為nil,也就是不經由initialize建構,例如:
fva = FreezeVar.allocate
fva.x, fva.y = 10, 20
puts fva
這跟Java的預設建構子類似。

Module

模組原則上是class的superclass,所以如果你在interactive Ruby輸入指令Class.superclass將會得到Module。兩者間的不同處經由輸入指令Class.instance_methods - Module.instance_methods可以得到[:new, :allocate, :superclass],也就是說Module無法new一個instance且無法繼承。而使用的時機是如果某功能可能會使用在許多不同的class內,則可以考慮寫成Module然後再使用include指令將其包含即可。而且Module還提供namespace以避免名稱重複產生的混亂。先看一個簡單的例子:
module Salary
    BASE = 25000
    def pay(bonus)
        BASE+bonus
    end
end
class Employee
    include Salary
end
class InternationalEmployee < Employee
end
em1 = Employee.new
em2 = InternationalEmployee.new
puts em1.pay(10000), em2.pay(12000), Salary::BASE # 35000, 37000, 25000
首先設計一個名為ModOne的module(注意module與class的名稱第一個字母需大寫),寫法跟class類似,只是換成module關鍵字。然後設計一個名為Employee的陽春class,再設計一個繼承自Employee的class名為InternationalEmployee。無論是哪一類型的雇員,或許算法會有不同,但都需要支薪,接著分別針對兩個class來new兩個instance,此時的instance中都有pay這個方法。記得要取得constant,需使用::符號。Module的寫法是跟class類似的,所以可以再修改如下:
module Salary
    attr_accessor :base
    def initialize(base)
        @base = base
    end
    def pay(bonus)
        @base+bonus
    end
end
class Employee
    include Salary
    def initialize(name, base, bonus)
        @name = name
        @bonus = bonus
        super(base)
    end
    def to_s
        "#{@name}'s salary is #{pay(@bonus)}"
    end
end
class InternationalEmployee < Employee
end
em1 = Employee.new("Tom", 22000, 20000)
puts em1 # Tom's salary is 42000
em2 = InternationalEmployee.new("Mary", 22000, 25000)
puts em2 # Mary's salary is 47000
此處因為module ModOne也有initialize方法,根據其繼承鏈(使用em2.class.ancestors印出),我們可以使用super。接著就是在class內直接使用Salary內的方法了。如果有超過一個的module呢?例如:
module Salary
    attr_accessor :base
    def initialize(base)
        @base = base
    end
    def pay(bonus)
        @base+bonus
    end
end
module PersonalInfo
    attr_accessor :name, :gender
    def initialize(name, gender)
        @name = name
        @gender = gender
    end
end
class Employee
    include Salary
    include PersonalInfo
    def initialize(n, gender, base, bonus)
        @bonus = bonus        
        PersonalInfo.instance_method(:initialize).bind(self).call(n,gender)
        Salary.instance_method(:initialize).bind(self).call(base)
    end
    def to_s
        "#{name} is a #{gender} and salary is #{pay(@bonus)}"
    end
end
class InternationalEmployee < Employee
end
em1 = Employee.new("Tom", "male", 22000, 20000)
puts em1 # Tom is a male and salary is 42000
em2 = InternationalEmployee.new("Mary", "female", 22000, 25000)
puts em2 # Mary is a female and salary is 47000
因為superclass不只一個,此處使用類似PersonalInfo.instance_method(:initialize).bind(self).call(n,gender)。不過好像不需要搞得這麼複雜= =。那麼若是一個module內include另一個module呢?例如:
module Salary
    attr_accessor :base
    def initialize(base)
        @base = base
    end
    def pay(bonus)
        @base+bonus
    end
end
module FatCat
    include Salary
    def pay(bonus)
        @base+bonus*10
    end
end
class Employee
    include FatCat
    def initialize(name, base, bonus)
        @name = name
        @bonus = bonus        
        super(base)
    end
    def to_s
        "#{@name}'s salary is #{pay(@bonus)}"
    end
end
class InternationalEmployee < Employee
end
em1 = Employee.new("Tom", 22000, 20000)
puts em1 # Tom's salary is 222000
em2 = InternationalEmployee.new("Mary", 22000, 25000)
puts em2 # Mary's salary is 272000
可以看到被include的module內的方法被後續的同名方法覆蓋了。

Name Space

當我們include兩個module,而兩個module中有同名的方法時要如何處理呢?
module Mone
    def Mone.hello
        puts "Hello from Mone"
    end
end
module Mtwo
    def Mtwo.hello
        puts "Hello from Mtwo"
    end
end
class Cone
    include Mone
    include Mtwo
    def hitwice
        Mone.hello
        Mtwo.hello
    end
end
co = Cone.new
co.hitwice
Mone.hello
Mtwo.hello
可以看出此時可以使用modulename.methodname來呼叫module內的方法,只要我們在module中設計方法時將名稱設計成modulename.methodname即可。

Mixins

Ruby並不支援多重繼承,不過我們可以藉由include多個module來發展完整功能的class,此稱之為Mixin。之前已有類似的例子,這裡再舉個例:
module Customer
    attr_accessor :name
    def initialize(name, x, y)
        @name = name
        @x = x
        @y = y
    end
    def to_s
        "#{@name} at (#{@x}, #{@y})"
    end
end
module Cargo
    def item(i)
        puts "Sending item: #{i}"
    end
    def value(v)
        puts "The value of the item is #{v}"
    end 
end
class Delivery
    include Customer
    include Cargo
end
de = Delivery.new("Tom", 1, 10)
puts de # Tom at (1, 10)
de.item("book") # Sending item: book
de.value(100) # The value of the item is 100
delivery的instance可以同時使用Customer與Cargo內的方法。當然也可以像之前介紹的先在Cargo內include Customer,合為一之後,再在Delivery內include Cargo即可。

extend

extend跟include類似但有不同,extend會將Module引入到class的singleton class上方,也就是說使用extend來導入module會讓其方法變成class method,例如:
module Mone
    def hello
        "Hello, World."
    end
end
class Cone
    extend Mone
    def sayhi
        puts Cone.hello
    end
end
co = Cone.new
co.sayhi # or: puts Cone.hello
還記得class method的呼叫方式是classname.methodname。

prepend

之前提及若是使用include表示將module加到繼承鏈中class的上方,而若是使用prepend則是加到下方,例如:
module Hello
    def hello
        "Hello. " + super #super is for calling the method hello inside Cone
    end
end
class Greeting
    prepend Hello
    def hello
        "What's up?"
    end
end
gt = Greeting.new
puts gt.hello # Hello. What's up?
print Greeting.ancestors # [Hello, Greeting, Object, Kernel, BasicObject]
# will be [Greeting, Hello, Object, Kernel, BasicObject] if use "include"
使用gt.hello呼叫hello實際上是在呼叫module內的方法,因為此時class變成module的父類別,所以可以在module中使用super來呼叫class內的方法(也就是hello)。

require & load

require與load都是要讀取其他檔案時使用,原則上效果相同,只是require會追蹤所需的library是否已經導入,若是已經導入而又require相同的library將會傳回false,而load則不會。所以若是需要導入的library內容會變動,可以使用load。而使用方法舉例如下,先設計一個myModule.rb:
module Square
    def area(w, h)
        w*h
    end
    def circumference(w,h)
        2*(w+h)
    end
end
接著在主程式檔案內:
$LOAD_PATH << '.' ## 表示目前目錄'
require 'myModule.rb' ## or: load 'myModule.rb'
#require_relative "./myModule.rb" ## 相對路徑'
#require 'E:\Foldername\subfoldername\Ruby\myModule.rb' ##絕對路徑'
#require './myModule.rb' ## 相對路徑'

#puts require './myModule.rb' ## << if you have second require >> return false
class Polygon
    include Square
    def squareInfo(w, h)
        "#{area(w,h)}, #{circumference(w,h)}"
    end
end
poly = Polygon.new
puts poly.squareInfo(5,10)
使用require或load時,需要標明要導入的檔案位置,此處可以使用$LOAD_PATH << '.'來表示當前目錄,或是使用絕對或相對路徑。如前所述,如果require兩次,將會傳回false。如果我們把myModule.rb內的module改為class的話,那麼導入後可以直接使用,例如:
s = Square.new
puts s.area(5,10), s.circumference(5,10)
或是將原class改為:
class Polygon
    def squareInfo(w, h)
        "#{Square.new.area(w,h)}, #{Square.new.circumference(w,h)}"
    end
end
不過這樣應該還是使用module才對。現在把myModule的內容修改為有兩個Module(Square, Circle),兩者有相同的方法,再加上一個class Node,再加上一個方法:
module Square
    def Square.area(w, h)
        w*h
    end
    def Square.circumference(w,h)
        2*(w+h)
    end
end

module Circle
    def Circle.area(r)
        r*r*Math::PI
    end
    def Circle.circumference(r)
        2*r*Math::PI
    end
end

class Node
    attr_accessor :x, :y
    def initialize(x,y)
        @x, @y = x, y
    end
    def to_s
        "x: #{@x}, y: #{@y}"
    end
end

def triangleArea(base, height)
    "Triangle Area(base=#{base}, height=#{height}):#{base*height/2}"
end
現在我們可以自在的取用其中的物件或方法:
require_relative "./myModule.rb"
class Polygon
    include Square
    include Circle
    def squareInfo(w, h)
        "#{Square.area(w,h)}, #{Square.circumference(w,h)}"
    end
    def circleInfo(r)
        "#{Circle.area(r)}, #{Circle.circumference(r)}"
    end
    def anode(x, y)
        Node.new(x,y)
    end
    def triangleInfo(w, h)
        triangleArea(w,h)
    end
end
poly = Polygon.new
puts poly.squareInfo(5,10) # 50, 30
puts poly.circleInfo(10) # 314.1592653589793, 62.83185307179586
puts poly.anode(100,100) # x: 100, y: 100
puts poly.triangleInfo(10, 20) # Triangle Area(base=10, height=20):100

autoload

就是load,只是只有在第一次碰到某特定namespace中的module或class時才會load。例如還是使用上列的myModule.rb,現在在主程式中寫:
autoload(:Square, "./myModule.rb")
puts autoload?(:Square) # ./myModule.rb
puts Square.area(10,20) # 200
puts Circle.area(100) # 31415.926535897932
puts autoload?(:Square)==nil # true
首先設定如果碰到Square時就load(./myModule.rb),而autoload?(:Square)在還沒load時傳回路徑,在已經load後傳回nil。因為在碰到Square時就load了,所以Circle的module也可以用了。如果我們把Circle.area()這一行移到Square.area()之前,就會傳回錯誤,因為還沒有碰到Square所以module還沒有load進來。

Exceptions handling

在ruby中處理exceptions,主要的關鍵字為begin...rescue...end,begin到rescue之間是主要的程式碼,有可能會出現exception,若是出現則跳到rescue之後,執行到end之間的程式碼。例如:
begin
    numerator = 10.0
    denominator = 0 # change to other value intead of 0
    puts numerator/denominator
rescue
    puts "Denominator cannot be zero."
end
當分母設為0則跳到rescue,否則會順利完成。這類的runtime exception會自動偵測,若是exception是根據我們自己判斷,則可以使用raise來拋出exception,當raise被執行,一樣會跳到rescue來救援。例如:
begin
    print "How old are you?"
    age = gets.to_i ## gets can receive the value inputed from keyboard
    if age < 0
        raise "Negative Age Exception."
    end
    puts "So you are #{age} years old."
rescue ## 當錯誤發生時執行
    puts "Age cannot be negative. Please reenter:"
    age = gets.to_i
    puts "So you are #{age} years old."
else ## 當不需要rescue時執行
    puts "Perfect. You are doing a good job."
ensure ## 一定會執行
    puts "See you."
end
此例中的gets會傳回keyboard輸入的資料。此處將整個架構列出,首先若是raise錯誤發生,跳到rescue,若是沒有raise錯誤,會執行else,而ensure一定會執行。執行後嘗試輸入負值試看看,會再次讓我們輸入。不過如果第二次還是輸入負值呢?此時因為不再判斷,所以直接會顯示負的年齡,這又跟我們想要的不同,而且rescue內的程式碼與begin內的重複,所以我們可以使用retry讓其再次執行begin的程式碼,如下:
begin
    print "How old are you?"
    age = gets.to_i ## gets can receive the value inputed from keyboard
    if age < 0
        raise "Negative Age Exception."
    end
    puts "So you are #{age} years old."
rescue ## 當錯誤發生時執行
    puts "Age cannot be negative. Please reenter:"
    retry
else ## 當不需要rescue時執行
    puts "Perfect. You are doing a good job."
ensure ## 一定會執行
    puts "See you."
end
使用retry來跳回begin,一直到輸入數值正確為止。在ruby中定義了許多類型的exception與error,參考自此網頁的圖: 其中有些是常用的,所以如果我們的程式碼會出現超過一個的錯誤,要如何處理?
begin
    print "How old are you?"
    age = gets 
    if age.scan(/\D/)==["\n"] or age.scan(/\D/)==["-", "\n"]
        age = age.to_i
    else
        raise TypeError, "Need numbers"
    end
    if age < 0
        raise ArgumentError, "Age cannot be negative"
    end
    if age > 150
        raise "Seriously?"
    end
    puts "So you are #{age} years old."
rescue TypeError => e
    puts "Error: #{e}"
    retry
rescue ArgumentError => e
    puts "Nagative age error: #{e}"
    retry
rescue StandardError => e # or: rescue => e
    puts "#{e} Are you truly #{age} years old?"
else 
    puts "Perfect. You are doing a good job."
ensure
    puts "See you."
end
解釋一下,(/\D/)的意思是取得字串中的非數字,也接受負數。雖然詢問的是數字,但是若是輸入字母(例如abc),此種錯誤與負數錯誤要分開處理的話,我們可以給他不同型態的錯誤。而沒有被指定的錯誤,則由不指定的rescue吸收。不過不指定好像預設是runtime error,所以最好用StandardError來當通用型態,不要使用Exception免得把其他有的沒有的都納進。最後再看如何自訂Exception,原則上就是建立繼承自Exception的class,例如:
class KiddingException < StandardError
    def initialize(message, others) # others is customized variable
        super(message)
        @others = others
    end
    def more
        @others
    end
end
begin
    print "How old are you?"
    age = gets 
    if age.scan(/\D/)==["\n"] or age.scan(/\D/)==["-", "\n"]
        age = age.to_i
    else
        raise TypeError, "Need numbers"
    end
    if age < 0
        raise ArgumentError, "Age cannot be negative"
    end
    if age > 150 and age <= 200
        raise "Seriously?"
    end
    if age > 200
        raise KiddingException.new("Are you kidding me?", "I don't believe it.")
    end
    puts "So you are #{age} years old."
rescue TypeError => e
    puts "Error: #{e}"
    retry
rescue ArgumentError => e
    puts "Nagative age error: #{e}"
    retry
rescue KiddingException => e
    puts e, e.more
rescue StandardError => e # or: rescue => e
    puts "#{e} Are you truly #{age} years old?"
else 
    puts "Perfect. You are doing a good job."
ensure
    puts "See you."
end
此處我們自訂一個名為KiddingException的class,繼承自StandardError。在initialize時原來僅需要給message參數,others是我們自己額外加的。在主程式找到條件raise這個exception(age>200),再設計一個rescue來執行此exception即可。

Input & Output

我們已經知道使用puts, print, p(p原則上就是呼叫inspect方法)來輸出到螢幕,且可使用gets來取得keyboard輸入的資料。接下來再看一個其他的用法:
puts 65 # 65
putc "Hello" # H
putc 65 # A
putc是印出character,如果參數是字串則印出第一個字母,如果是數字則印出對應字元(see Code page 437)。 接下來重點在如何將資料讀寫到檔案,如果要產生一個新的檔案,我們可以使用new或是open,例如:
f = File.new('test.txt', 'w') # or open
f.puts "This is a test."
f.close
首先使用File.new或是File.open會建立未存在檔案或開啟已存在檔案,後面兩個參數是檔案名與工作模式(mode),模式的選擇有: 也就是說我們適才建立了一個名為text.txt的檔案,並使用puts方法寫入資料,最後使用.close方法關閉資訊流。使用open可以做到完全一樣,那麼差別是?
File.open('test.txt', 'w') do |file|
    file.puts "This is a test of file open."
    file.write "Write something more.\n"
    file << "This will also do.\n"
end
此例中使用File.open然後之後接一個block,這便是與File.new的差別,File.new之後不可接block,而且File.open之後接block會自動關閉檔案資料流,也就是說不需要再使用.close方法。上例中使用三種不同方式寫資料入檔案,可任選,只是只有puts會自動換行。若是要讀取檔案內容,需要將mode改為r,然後使用gets方法來讀取一行。
file = File.new("test.txt", "r")
while line = file.gets do
    puts line
end
file.close
類似的做法,使用open然後在之後加上block:
File.open("test.txt", "r") do |file|
    while line = file.gets do
        puts line
    end
end
也可以使用如下方式:
File.readlines("test.txt").each do |line|
    puts line
end
或是
f = File.new("test.txt")
f.each {|line| puts line}
puts f.closed?
f.close
puts f.closed?
注意此二方式無須註明mode為r。

Some File methods

下例列舉出部分File的方法,使用File表示是class method,使用instance name(file)表示是instance method。

IO

除了上列的方法,也可以直接使用IO物件來操作,例如: