元組是由元素組成、具有固定順序且不可變的有限序列。 元組要求每個位置都有固定的型別。 這表示編譯器知道每個位置上是什麼型別。 元組中每個位置使用的型別可以不同,但這些型別都必須在編譯期就已知。
依據元組中值的型別能否在編譯時解讀,可以用不同方式建立元組。 如果這些值在編譯期就已知,就可以用元組字面值語法來建立元組,否則就必須明確宣告。 另外也要注意,值的型別必須符合元組中指定的型別,而且值的數量必須與指定的型別數量一致。 以下是用元組字面值語法定義的範例:
tuple = {1, "foo", 'c'} # Tuple(Int32, String, Char)
也可以使用Tuple類別來建立元組。
tuple = Tuple(Int32, String, Char).new(1, "foo", 'c')
或者,你也可以明確指定接收該元組的變數型別。
tuple : Tuple(Int32, String, Char) = {1, "foo", 'c'}
明確指定元組的型別很有用,因為這樣就能定義某個位置要存放聯合型別。 也就是說,同一個位置可以存放多種型別。
tuple : Tuple(Int32 | String, String, Char) = {1, "foo", 'c'}
你可以使用Tuple類別的from方法從陣列建立元組。
這需要明確指定元組的型別。
array = [1, "foo", 'c']
tuple = Tuple(Int32, String, Char).from(array)
你可以使用to_a方法把元組轉換成陣列。
產生的陣列,其元素型別是元組中每個欄位型別的聯合。
tuple = {1, "foo", 'c'}
array = tuple.to_a
array # => [1, "foo", 'c']
和陣列一樣,元組也是從 0 開始索引,也就是第一個元素的索引是 0。
不過,和陣列不同的是,元組中每個元素的型別都是固定的,而且在編譯期就已知,因此對元組取索引時,元素的型別取決於它所在的位置。
要存取元組中的元素,可以使用[]運算子。
array = [1, "foo", 'c']
array[0] # => 1
typeof(array[0]) # => Int32 | String | Char
tuple = {1, "foo", 'c'}
tuple[0] # => 1
typeof(tuple[0]) # => Int32
存取元素時,元組和陣列的另一個差異是:如果索引是直接寫出來的,編譯器會檢查該索引是否落在元組的範圍內。 這表示你會得到編譯期錯誤,而不是執行期錯誤。
tuple = {1, "foo", 'c'}
tuple[3]
# => Error: index out of bounds for Tuple(Int32, String, Char) (3 not in -3..2)
但如果索引存在變數裡,編譯器就無法在編譯期檢查它是否落在元組的範圍內,而會在執行時產生錯誤。
你可以搭配範圍使用[]運算子,取得元組的子元組。
回傳的是一個新的元組,內含指定範圍中的元素。
這個範圍必須在編譯期就給定,否則編譯器無法得知子元組中各元素的型別。
也就是說,範圍必須是範圍字面值,不能存在變數裡。
tuple = {1, "foo", 'c'}
subtuple = tuple[0..1] # Tuple(Int32, String)
i = 0..1
tuple[i]
# Error: Tuple#[](Range) can only be called with range literals known at compile-time
當你想把固定數量的值群組在一起,而且這些值的型別在編譯期就已知時,元組就很好用。 這是因為元組不可變,所以所需的記憶體比陣列少,速度也比陣列快。 另一個使用情境是從方法回傳多個值。 如果這些值的型別各不相同,這就特別好用,因為元組中每個位置的型別都可以不一樣。
如果需要的是大小能成長或縮減、或經常需要修改的資料結構,就不該使用元組。
符號是具有名稱的識別字,可以用來指向某個值。
符號是透過符號字面值建立的,做法是在名稱前面加上:字元,例如:foo。
符號也可以加上引號來寫,例如:"foo",這樣就能在名稱裡包含空白。
:foo # => :foo
:"foo boo" # => :"foo boo"
:foo == :"foo" # => true
符號在這門語言裡用於許多地方,包括當作具名元組的鍵,以及用來代表方法和變數的名稱。
Crystal 的符號和 Ruby 很不一樣。 在 Crystal 裡,符號算是一種常數,因此是在編譯期就賦予的。 這表示符號無法動態建立,而在 Ruby 裡是可以的。
Crystal 中的符號以Int32表示,這讓它們非常有效率。
符號可以轉換成字串,但反過來不行。 這是因為符號是在編譯期建立,而字串是在執行期建立。
:foo.to_s # => "foo"
在準備烤餅乾給朋友吃的時候,你發現食譜裡有些計量單位必須換算。由於你只熟悉公制,得想個辦法把常見的美制烘焙計量換算成毫升(mL),這樣自己用起來才方便。
你的解法請參考這份換算表:
| 要換算的單位 | 體積 | 毫升(mL) |
|---|---|---|
| mL | 1 | 1 |
| 美制杯 | 1 | 240 |
| 美制液量盎司 | 1 | 30 |
| 美制茶匙 | 1 | 5 |
| 美制湯匙 | 1 | 15 |
身為一個正在受訓的優秀程式設計師,你決定用毫升當作中間單位,方便把表上的任何單位換算成其他任何單位(甚至是它自己)。
實作 KitchenCalculator.get_volume 方法。
給定一個體積配對元組,它應該只回傳數值部分。
KitchenCalculator.get_volume({:cup, 2.0})
# => 2.0
實作 KitchenCalculator.to_milliliter 方法。
給定一個體積配對元組,它應該依照換算表把體積換算成毫升。
KitchenCalculator.to_milliliter({:cup, 2.5})
# => {:milliliter, 600.0}
實作 KitchenCalculator.from_milliliter 方法。
給定一個體積配對元組和想要的單位,它應該依照換算表把體積換算成該單位。
KitchenCalculator.from_milliliter({:milliliter, 1320.0}, :cup)
# => {:cup, 5.5}
實作 KitchenCalculator.convert 方法。
給定一個體積配對元組和想要的單位,它應該把給定的體積換算成想要的單位。
KitchenCalculator.convert({:teaspoon, 9.0}, :tablespoon)
# => {:tablespoon, 3.0}