將if-else之類巢狀迴圈重構為函式式管道 - XP123
巢狀結構難以閱讀;管道stream通常更容易閱讀和思考。
巢狀結構具有“厄運之箭”的感覺,您需要同時管理所有父結構的上下文;而管道stream通常是線性的。
許多語言都新增了“函式式管道”風格,建立在首先在 Lisp 中探索的 map-filter-reduce 的基礎上,哦,大約 50 年前,大約 40 年前在 Unix 和 Smalltalk 中:)
在下面描述的內容在概念上適用於 Java、C#、Kotlin、Python 等。我將使用 Swift 中的示例,並解釋任何特定於 Swift 的構造。
我使用所有這三種方法:
- 工具:如果您的工具可以勝任,請使用該工具!例如,當 IntelliJ IDEA 看到一個知道如何轉換的迴圈時,它會彈出一個黃色的燈泡,提供執行此操作。
- 提取新集合:當迴圈遍歷集合時,將集合提取到變數中作為新管道的種子,並逐漸將迴圈的部分移入其中,直到原始迴圈消失。Martin Fowler 在他的優秀書籍和文章(請參閱參考資料)中探討了這種方法,因此我不會進一步探討。
- 就地轉換:將迴圈轉換為就地管道,一次一個巢狀級別。我們將在下面使用這種方法。
讓我們來看一個例子。我們將一步一步地將下面的迴圈變成一個函式性管道。該集合是一個陣列,包含字典(Map)。
var points = [ ["x":"17", "y":"23"], ["x": "x12", "y": "y100"], ["x": "3", " y": "2", "z": "11"], ["w":"21"]] |
目標是計算任何具有 y 座標的條目的平均值。
var sum = 0 var count = 0 for i in 0..<points.count { let item = points[i] if let y = item["y"] { if let theInt = Int(y) { sum += theInt 計數 += 1 } } } |
print(sum / count) |
使用 forEach() 替代for迴圈:
var sum = 0 var count = 0 points.forEach { item in if let y = item["y"] { if let theInt = Int(y) { sum += theInt count += 1 } } } print(sum / count) |
使用compactMap() 處理條件不滿足的情況:
var sum = 0
var count = 0
points
.compactMap { $0["y"] }
.forEach { y in
if let theInt = Int(y) {
sum += theInt
count += 1
}
}
print(sum / count)
再次使用 compactMap()忽略格式錯誤的整數
var sum = 0 var count = 0 points .compactMap { $0["y"] } .compactMap { Int($0) } .forEach { theInt in sum += theInt count += 1 } print(sum / count) |
路徑 1:拆分迴圈
我們可能會看到並意識到我們的程式碼正在計算兩件事,並拆分迴圈。要做到這一點,我們將儲存常用的計算成一個新的集合,獨立重複迭代處理sum和count。
let values = points .compactMap { $0["y"] } .compactMap { Int($0) } var sum = 0 values .forEach { theInt in sum += theInt } var count = 0 values .forEach { theInt in count += 1 } print(sum / count) |
在兩個迴圈中使用reduce()
let values = points .compactMap { $0["y"] } .compactMap { Int($0) } let sum = values.reduce(0, +) let count = values .map { _ in 1} .reduce(0, +) print(sum / count) |
我們可以更輕鬆地計算計數:
let count = values.count
路徑 2:單管道
我們可能會認識到 sum 和 count 可以儲存在一個元組中(一個主要是匿名的物件):
var tuple = (sum: 0, count: 0) points .compactMap { $0["y"] } .compactMap { Int($0) } .forEach { theInt in tuple = (sum: tuple.sum + theInt, count: tuple.count + 1) } print(tuple.sum / tuple.count) |
如果你像我一樣,這個元組看起來有點難看,而且可能令人困惑。我會省去你使用它的 reduce() 呼叫。相反,我們看到 sum 和 count 必須一起協調。聽起來像是放置實際物件的好地方:
class Average { var sum = 0 var count = 0 var value : Int? { if count == 0 { return nil } return sum / count } func add(_ item: Int) -> Average { sum += item count += 1 return self } } |
現在可以使用reduce :
let average = points .compactMap { $0["y"] } .compactMap { Int($0) } .reduce(Average(), { $0.add($1) }) print(average.value!) |
兩條路徑的比較
當它澄清程式碼時,拆分迴圈可能是一個很好的舉措(並且可能讓您在多個物件之間重新分配行為)但是它有一個缺點——如果您將部分工作儲存在一個集合中,您可能會強制一個真正的集合存在,使用需要的所有記憶體管理。
相比之下,將其保留為管道意味著可能永遠不會有集合。當然,我們的示例有一個陣列常量,但相同的管道適用於物件流,從不需要同時使用它們。
在這種情況下,新物件對我來說是勝利。我沒想到,但是這個小物件確實改進了程式碼。
結論
函式式管道通常勝過巢狀結構(while - if - while -if -if 等:)。
- 更容易理解:您需要維護的上下文更少。
- 潛在的記憶體效率更高:管道在處理流時與在集合上工作一樣愉快。
- 更容易並行化:您可以將每個階段想象成自己的計算機。(不幸的是,現實生活中的並行化比這更難。)
具有這些管道的語言之間有很多重疊:它們通常提供相似的功能,即使它們稍微更改了名稱。
相關文章
- 迴圈_巢狀巢狀
- Python基礎之函式的巢狀Python函式巢狀
- Python 迴圈巢狀Python巢狀
- 巢狀迴圈成本消耗巢狀
- 巢狀類遞迴巢狀遞迴
- 函式的動態引數 及函式巢狀函式巢狀
- java中如何將巢狀迴圈效能提高500倍Java巢狀
- 迴圈單連結串列建構函式、解構函式C++實現函式C++
- C++之類解構函式為什麼是虛擬函式C++函式
- MATLAB巢狀函式練習Matlab巢狀函式
- python怎麼迴圈巢狀Python巢狀
- python 跳出巢狀迴圈方法Python巢狀
- 第 8 節:函式-函式巢狀呼叫與返回值函式巢狀
- Python巢狀定義函式增強reduce()函式功能Python巢狀函式
- 詳解Python的函式巢狀Python函式巢狀
- 類的建構函式和解構函式函式
- 函式之遞迴函式遞迴
- jQuery - 函式 $().each() 的迴圈控制jQuery函式
- 迴圈_推導式_繪製棋盤_函式函式
- Sql 巢狀迴圈最佳化案例SQL巢狀
- 1kb的前端HTML模板解析引擎,不限於巢狀、迴圈、函式你能想到的解析方式前端HTML巢狀函式
- MySQL Join原理分析(緩衝塊巢狀與索引巢狀迴圈)MySql巢狀索引
- 微軟將為Linux帶來巢狀式虛擬化支援微軟Linux巢狀
- 1.5.2 Python函式巢狀及作用域Python函式巢狀
- js中的函式巢狀和閉包JS函式巢狀
- C++ 類建構函式和解構函式C++函式
- 外層函式的變數直接被巢狀函式引用計算函式變數巢狀
- PyThon range()函式中for迴圈用法Python函式
- python 利用 for ... else 跳出雙層巢狀迴圈Python巢狀
- Python的if else 巢狀 和forin while 迴圈Python巢狀While
- 兩表連線一:巢狀迴圈連線巢狀
- Linux Shell程式設計(17)——巢狀迴圈Linux程式設計巢狀
- for迴圈批量註冊事件處理函式事件函式
- 函式(三)作用域之變數作用域、函式巢狀中區域性函式作用域、預設值引數作用域函式變數巢狀
- setTimeout和箭頭函式巢狀中的this指向函式巢狀
- JavaScript匿名函式要外面巢狀小括號原因JavaScript函式巢狀
- 倉頡程式語言技術指南:巢狀函式、Lambda 表示式、閉包巢狀函式
- JS 函式式概念: 管道 和 組合JS函式