學習軌道
/
Swift
Swift
/
練習
/
Double-Null0111:閉包恆久遠
Double-Null0111:閉包恆久遠

Double-Null0111:閉包恆久遠

學習練習

簡介

Swift 中的閉包是獨立的程式區塊,可以接收參數來觸發運算並回傳值 閉包也會從自身的環境中捕獲值,並在運算中使用它們。 由於閉包是獨立的,它們可以像其他值一樣在程式中傳遞,也可以指定給常數和變數。

閉包聽起來可能很像 Swift 的函式;確實如此,而且這是有原因的。 Swift 中的函式是閉包的特例,它們必須有名稱,而且定義時使用的語法略有不同。

雖然 Swift 中的函式在技術上算是閉包,但當人們在 Swift 裡提到「closures」時,指的其實是閉包運算式。 閉包運算式的寫法是一串參數列表,接著是回傳型別和關鍵字in,然後是閉包的主體,全部包在一對大括號裡:

{ (a: Double, b: Double) -> Double in
  return (a + b) / 2.0
}

這定義了一個型別為(Double, Double) -> Double的閉包運算式,執行方式類似於同型別的函式,只要將它套用到適當型別的參數上:

{ (a: Double, b: Double) -> Double in
  return (a + b) / 2.0
}(10.0, 15.0)
// returns 12.5

由於每次想執行閉包時都寫出完整的閉包運算式並不太方便,閉包可以像 Swift 中其他任何值一樣被指定給名稱。 之後只要把這個名稱套用到參數上,就能呼叫它:

let mean = { (a: Double, b: Double) -> Double in
  (a + b) / 2.0
}

mean(13.0, 100.0)
// returns 56.5

如上面所見,閉包和一般函式一樣,如果主體只有單一運算式,可以省略return關鍵字。

閉包最常和高階函式一起使用,不是作為參數傳入,就是作為結果回傳:

[11, 75, 3, 99, 53].contains(where: { (x: Int) -> Bool in x > 100 })
// returns false

["apple", "ball", "carrot"].sorted(by: { (s1: String, s2: String) -> Bool in s1.count < s2.count })
// returns ["ball", "apple", "carrot"]

func makeAdder(base: Int) -> (Int) -> Int {
  { (x: Int) -> Int in base + x }
}

說明

你好,Double-Null0111 探員,我們又見面了。

UMBRA 的勢力又開始蠢蠢欲動,這次要靠你來粉碎他們最新的陰謀。 我們的情報部門告訴我們,那些 Minions 正打算在州長的舞會上引爆一枚臭彈。

由於 Minions 實在太笨手笨腳,他們常常在實驗室和辦公室裡不小心啟動臭彈。 又因為他們老是忘記怎麼拆除自己的臭彈,所以他們做了一套系統,會根據炸彈的 ID 號碼產生拆彈指令,列出電線必須剪斷的順序。

你的任務是寫出程式碼,讓你能在舞會上的臭彈引爆前把它拆除。

1. 撰寫用來翻轉兩條電線的閉包

每枚臭彈上都有三條顏色不同的電線。 撰寫一個閉包,接受一個 (String, String, String) 元組,並回傳把前兩個元素順序對調後的元組。 將這個閉包指定給名稱flip。

請注意,型別必須是 ChangeClosure(或可傳送的型別),型別定義已經寫在 stub 檔案裡。 否則程式碼將無法編譯。

flip(("red", "yellow", "blue"))
// Returns ("yellow", "red", "blue")

2. 撰寫用來旋轉電線的閉包

撰寫一個閉包,接受一個 (String, String, String) 元組,並回傳把電線順序向左旋轉後的元組。 將這個閉包指定給名稱rotate。

請注意,型別必須是 ChangeClosure(或可傳送的型別),型別定義已經寫在 stub 檔案裡。

rotate(("red", "yellow", "blue"))
// Returns ("yellow", "blue", "red")

3. 實作電線洗牌產生器

為了找出剪斷臭彈電線的順序,你需要實作一個函式,根據臭彈的 ID 號碼產生正確的洗牌函式。

實作這個函式:

makeShuffle(
  flipper: @escaping ((String, String, String)) -> (String, String, String),
  rotator: @escaping ((String, String, String)) -> (String, String, String)
) -> ([UInt8], (String, String, String)) -> (String, String, String)

它接受一個翻轉兩條電線的閉包,以及一個旋轉三條電線的閉包,並回傳一個閉包。 這個回傳的閉包會接受臭彈的 ID 號碼和三條電線的順序,然後計算出電線需要剪斷的順序。 ID 號碼由八個位元組成,每個位元會決定要翻轉還是旋轉電線。

從最右邊的位元開始,針對 ID 號碼中的每個位元,如果該位元是 0,就把 flipper 閉包套用到電線元組上;如果是 1,就套用 rotator 閉包,得到電線的新狀態。 當 ID 的八個位元都套用完對應的閉包後,電線的最終狀態就是它們需要被剪斷的順序。

let shuffler = makeShuffle(flipper: flip, rotator: rotate)
// Returns ([UInt8], (String, String, String)) -> (String, String, String)
shuffler([1, 0, 0, 1, 1, 0, 1, 1], ("red", "yellow", "blue"))
// Returns ("red", "blue", "yellow")
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Swift Exercism

準備好開始 Double-Null0111:閉包恆久遠 了嗎?

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