學習軌道
/
Crystal
Crystal
/
練習
/
廚房計算機
廚房計算機

廚房計算機

學習練習

簡介

元組是由元素組成、具有固定順序且不可變的有限序列。 元組要求每個位置都有固定的型別。 這表示編譯器知道每個位置上是什麼型別。 元組中每個位置使用的型別可以不同,但這些型別都必須在編譯期就已知。

建立元組

依據元組中值的型別能否在編譯時解讀,可以用不同方式建立元組。 如果這些值在編譯期就已知,就可以用元組字面值語法來建立元組,否則就必須明確宣告。 另外也要注意,值的型別必須符合元組中指定的型別,而且值的數量必須與指定的型別數量一致。 以下是用元組字面值語法定義的範例:

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 中的符號

Crystal 的符號和 Ruby 很不一樣。 在 Crystal 裡,符號算是一種常數,因此是在編譯期就賦予的。 這表示符號無法動態建立,而在 Ruby 裡是可以的。

Crystal 中的符號以Int32表示,這讓它們非常有效率。

轉換

符號可以轉換成字串,但反過來不行。 這是因為符號是在編譯期建立,而字串是在執行期建立。

:foo.to_s # => "foo"

說明

在準備烤餅乾給朋友吃的時候,你發現食譜裡有些計量單位必須換算。由於你只熟悉公制,得想個辦法把常見的美制烘焙計量換算成毫升(mL),這樣自己用起來才方便。

你的解法請參考這份換算表:

要換算的單位 體積 毫升(mL)
mL 1 1
美制杯 1 240
美制液量盎司 1 30
美制茶匙 1 5
美制湯匙 1 15

身為一個正在受訓的優秀程式設計師,你決定用毫升當作中間單位,方便把表上的任何單位換算成其他任何單位(甚至是它自己)。

1. 取得體積配對中的數值部分

實作 KitchenCalculator.get_volume 方法。 給定一個體積配對元組,它應該只回傳數值部分。

KitchenCalculator.get_volume({:cup, 2.0})
# => 2.0

2. 把體積配對換算成毫升

實作 KitchenCalculator.to_milliliter 方法。 給定一個體積配對元組,它應該依照換算表把體積換算成毫升。

KitchenCalculator.to_milliliter({:cup, 2.5})
# => {:milliliter, 600.0}

3. 把毫升體積配對換算成其他單位

實作 KitchenCalculator.from_milliliter 方法。 給定一個體積配對元組和想要的單位,它應該依照換算表把體積換算成該單位。

KitchenCalculator.from_milliliter({:milliliter, 1320.0}, :cup)
# => {:cup, 5.5}

4. 把任何單位換算成任何單位

實作 KitchenCalculator.convert 方法。 給定一個體積配對元組和想要的單位,它應該把給定的體積換算成想要的單位。

KitchenCalculator.convert({:teaspoon, 9.0}, :tablespoon)
# => {:tablespoon, 3.0}
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Crystal Exercism

準備好開始 廚房計算機 了嗎?

註冊 Exercism,透過 26 個概念142 個練習 和真人引導來學習並精通 Crystal,全部免費。