Ruby
設定環境
使用之前先設定環境,首先到官網(https://www.ruby-lang.org/en/downloads/)會看到Ways of Installing Ruby,選擇你的作業系統所需要的檔案,若是與此處相同使用Windows,點擊連結到此下載網頁。此處選擇其所建議的Ruby+Devkit 2.5.5-1 (x64),下載後雙擊檔案執行,一路確定到底即可。安裝完成後,確認一下是否安裝完成,打開DOS介面(在Windows開始處輸入cmd),輸入
接下來選擇使用的編輯軟體。此處選擇編譯方式是在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 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)有不同之命名規則,原則上可分為以下四種:- Global variable: $varname
- Instance variable: @varname
- Class variable: @@varname
- Constant: VARNAME
運算子
Ruby的運算子與其他許多語言多有類似,在此列出所有並解釋部分較可能混淆者:- Arithmetic Operators:
+ - * / % ** puts 2**3
而/數為兩數相除,若是兩者皆為整數,則傳回商,若欲求得至小數後的數值,將分子或分母其中之一改為實數即可,例如:puts 2/3.0
此外,+號除了計算相加,如前所述,亦可連結兩字串。 - Assignment Operators:
= += -= *= /= %= **= candy = 2 candy += 1 #等同於candy = candy + 1 print "You get ", candy, " candies."
其餘運算子概念相同。此外,Ruby支援Parellel Assignment ,所以可以如此宣告變數:a,b,c = 1,2,3 print a," ", b," ",c
此方式可以很容易地進行互換(swap),例如:b, c = c, b print b," ", c
- Comparison Operators:
== != > < >= <= <=> === .eql? equal? a = 2 b = 2.0 puts a==b puts a===b puts a.eql?b puts a.equal?b
其中===用來驗證是否右邊的值包含於(或歸屬於)左邊(與JavaScript中的===意義不同),也可以如此使用例如:puts String==="Hello"
而.eql?會在值跟型態都相等時傳回true(此與JavaScript中的===意義相同),所以上例中會傳回false。而equal?則是比較是否物件id(記憶體位置)是否相同,所以以下程式碼會傳回true。a = 2 b = a puts a.equal?b
上述有一運算子是傳回整數而非Boolean,即<=>。此運算子比較兩數大小,相等傳回0,左大於右傳回1,否則傳回-1。puts 3<=>6
- Bitwise Operators:
& | ^ ~ << >> a = 60 b = 13 puts a&b, a|b, a^b, ~a, a<<1, a>>1
左移一位數表示乘以2,右移一位表示除以2。 - Logical Operators:
and && or || ! not a = true b = false p (a and b), (a or b), (not a), a&&b, a||b, !a
此處使用p代替puts,類似但不大相同,p會傳回值,puts僅有印出值。在Interactive Ruby上練習看看,如下: 使用puts會顯示=>nil表示傳回空,而使用p傳回10。
若是有以下程式碼:
b = 1 a ||= b # a || a = b的縮寫 puts aa印出來會等於1,其意思就是如果a為nil或是false,則a=b,否則就還是a。因為其實就是a || a = b的縮寫。
運算優先順序
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" endWell,原則上有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,可使用的符號如下:
- %q: 替代單引號
print %q('single quote') #可以不使用\'來顯示'
- %Q: 替代雙引號
print %Q("double quote") #可以不使用\"來顯示"
- %r: 替代regular expression符號
print %r{#.abc(.*)}, /#.abc(.*)/ #二者相同
- %x: 替代反引號(`)符號
- %s: 替代symbol符號(:)
h = {%s(name) => "Tom", %s(age) => 18} print h #{:name=>"Tom", :age=>18}
- %i: 產生內容為symbol的array
print%i(a b c) # [:a, :b, :c]
- %W: 替代array邊界符號([])且元素被雙引號包含(也就是形成字串)
print %W(a b c) # ["a", "b", "c"]
- %w: 替代array邊界符號([])且元素被雙引號包含(也就是形成字串),但不受逸出字等符號影響(但若是下例使用%W便會出錯)
print %w(a b c #{abc} def\t\ ghi\t\ jkl) # ["a", "b", "c", "\#{abc}", "def\\t ghi\\t jkl"]
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中的常數。以下為包含之方法:
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) |
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便會看到顯示的錯誤訊息了。至於要設定方法的存取權限,使用
- public: default
- protected: can be invoked only by objects of the defining class and its subclasses
- private: cannot be accessed or viewed from outside the class. Only the class methods can access it.
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 HIfrozen?方法可以確認是否是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 100delivery的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 # Aputc是印出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),模式的選擇有:
- r - Read only. The file must exist.
- w - Create an empty file for writing.
- a - Append to a file.The file is created if it does not exist.
- r+ - Open a file for update both reading and writing. The file must exist.
- w+ - Create an empty file for both reading and writing.
- a+ - Open a file for reading and appending. The file is created if it does not exist.
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。- 關於時間:
if File.exist? "test.txt" # is exists? file = File.open("test.txt", "r") puts "test.txt is open." end puts File.atime "test.txt" # last access time puts "Last access time is #{file.atime}" # last access time puts File.ctime "test.txt" # directory change time(not the file itself changed) print "The ctime >>> ",file.ctime, "\n" # directory change time(not the file itself changed) puts File.mtime "test.txt" # file modification time print "The mtime >>> ",file.mtime, "\n" # file modification time puts File.birthtime "test.txt" # time of birth puts file.birthtime # time of birth file.close
- 關於檔案名與路徑:
if File.exist? "test.txt" # is exists? file = File.open("test.txt", "r") puts "test.txt is open." end puts File.extname(file) # filename extension >>> .txt print "The base name >>> ", (File.basename file), "\n" # the base name >>> test.txt puts File.basename file, File.extname(file) # filename without extension puts File.basename file, ".txt" # filename without extension, same to above puts File.basename file, ".*" # filename without extension, same to above print "file path >>> ", file.path, " >>> ", file.to_path, "\n" # the pathname to create file puts File.dirname "E:/NKFUST/www/Ruby/test.txt" # return the directory name >> E:/NKFUST/www/Ruby puts File.absolute_path("test.txt") # return absolute path puts File.realdirpath "test.txt" # return the real(absolute) pathname puts File.realpath "test.txt" # return the real(absolute) pathname print File.split("E:/NKFUST/www/Ruby/test.txt") # ["E:/NKFUST/www/Ruby", "test.txt"] file.close
- 關於檔案本身資訊與狀態:
if File.exist? "test.txt" # is exists? file = File.open("test.txt", "r") puts "test.txt is open." end puts File.file? "test.txt" # if file exists and is a regular file puts File.readable? "test.txt" # if file is readable puts File.writable? "test.txt" # if file is writable puts File.size "test.txt" # file size puts file.size # file size puts File.size? "test.txt" # file size, if file does not exist or has zero size, return nil puts File.zero? "test.txt" # if file exists and size = 0 file.close File.delete "test.txt" # delete the corresponding file >>> cannot delete while open, close first
IO
除了上列的方法,也可以直接使用IO物件來操作,例如:- Read:
puts IO.binread "test.txt" # read the whole file content, CAN USE READ TO REPLACE BINREAD puts IO.binread "test.txt", 20 # read the first 20 bytes(length) puts IO.binread "test.txt", 20, 10 # read 20 bytes(length) starts from 10 bytes(offset) print IO.readlines "test.txt" # read all lines, return an array IO.readlines("test.txt").each {|line| print ">>>", line} IO.readlines("test.txt", sep=",").each {|line| print ">>>", line} # use , as seperator IO.readlines("test.txt", limit=8).each {|line| print ">>>", line} # read every 8 bytes IO.foreach "test.txt" do |line| # or: IO.foreach("test.txt") {|line| puts line} puts line end
binread等同於read。 - Write:
puts IO.binwrite("test.txt", "Infomation that are going to write into the file") # return the size of infomation to write puts IO.binwrite("test.txt", ">>>", offset = 10) # return the size of infomation to write # if offset is not given, the file is truncated. # Otherwise, write new information starts from offset(replace the old content)
以上寫法都可以。binwrite等同於write。如果沒有給offset參數,會直接清除內容重寫。如果有給,會在offset位置開始將新內容至換掉原內容。 - Copy:
IO.copy_stream("test.txt", "test1.txt") # copy content from source to destination IO.copy_stream("test.txt", "test1.txt", 20) # copy specific length of content (20 bytes) from source to destination
copy原則上就是先讀再寫,將原檔案內容覆蓋另一檔案內容。若給copy_length參數,則限定copy的內容範圍。