譯者:bestswifter;校對:numbbbbb;定稿:小鍋
幾年前,函數(shù)式編程的復(fù)興正值巔峰,一篇介紹 Scala 中 10 個(gè)單行函數(shù)式代碼的博文在網(wǎng)上走紅。很快地,一系列使用其他語言實(shí)現(xiàn)這些單行代碼的文章也隨之出現(xiàn),比如Haskell,Ruby,Groovy,Clojure,Python,C#,F#,CoffeeScript。
我們永遠(yuǎn)無法得知有多少人在社交聚會中對這些單行代碼留下了深刻的印象,但根據(jù)我的猜測,越復(fù)雜的例子越能激勵(lì)我們學(xué)習(xí)更多函數(shù)式編程的知識,至少對外行人來說是這樣。
通過使用單行代碼完成同樣的 10 個(gè)練習(xí),我們來看看 Swift 和其他語言之間的較量。在這個(gè)過程中,你也許還能學(xué)到一些有趣的東西(參見 #6 和 #10)。
第一個(gè)例子中沒什么干貨,我們都知道只要使用 map函數(shù)就可以簡單地解決問題:
(1...1024).map{$0 * 2}
這個(gè)問題可以通過使用 reduce 方法和加號運(yùn)算符解決,這是因?yàn)榧犹栠\(yùn)算符實(shí)際上也是一個(gè)函數(shù)。不過這個(gè)解法是非常顯而易見的,待會兒我們會看到 reduce 方法更具有創(chuàng)造力的使用。
(1...1024).reduce(0,combine: +)
我們使用 filter 方法判斷一條推文中是否至少含有一個(gè)被選中的關(guān)鍵字:
let words = ["Swift","iOS","cocoa","OSX","tvOS"] let tweet = "This is an example tweet larking about Swift" let valid = !words.filter({tweet.containsString($0)}).isEmpty valid //true更新 : @oisdk 建議這樣寫會更好:
words.contains(tweet.containsString)這種寫法更加簡練。另外,也可以這樣寫:
tweet.characters .split(" ") .lazy .map(String.init) .contains(Set(words).contains)
和其他語言不同,Swift 不能使用內(nèi)建的函數(shù)讀取文件,并把每一行存放到數(shù)組中。不過我們可以結(jié)合 split 和map 方法寫一段簡短的代碼,這樣就無需使用 for 循環(huán):
let path = NSBundle.mainBundle().pathForResource("test", ofType: "txt") let lines = try? String(contentsOfFile: path!).characters.split{$0 == "\n"}.map(String.init) if let lines=lines { lines[0] // O! for a Muse of fire, that would ascend lines[1] // The brightest heaven of invention! lines[2] // A kingdom for a stage, princes to act lines[3] // And monarchs to behold the swelling scene. }
最后一步使用 map 函數(shù)和字符串的構(gòu)造方法,將數(shù)組中的每個(gè)元素從字符數(shù)組(characters)轉(zhuǎn)換為字符串。
這段代碼會將“祝你生日快樂”這首歌的歌詞輸出到控制臺中,它在一段區(qū)間內(nèi)簡單的使用了 map 函數(shù),同時(shí)也用到了三元運(yùn)算符。
let name = "uraimo" (1...4).forEach{print("Happy Birthday " + (($0 == 3) ? "dear \(name)":"to You"))}
假設(shè)我們需要使用一個(gè)給定的過濾函數(shù)將一個(gè)序列(sequence)分割為兩部分。很多語言除了有常規(guī)的map,flatMap,reduce,filter 等函數(shù)外,還有一個(gè) partitionBy 函數(shù)恰好可以完成這個(gè)需求。正如你所知,Swift 沒有類似的函數(shù)(我們不想在這里使用 NSArray 中的函數(shù),并通過 NSPredicate 實(shí)現(xiàn)過濾功能)。
所以,我們可以通過拓展 SequenceType,并為它添加 partitionBy 函數(shù)來解決這個(gè)問題。我們使用這個(gè)函數(shù)將整數(shù)數(shù)組分割為兩部分:
extension SequenceType{ typealias Element = Self.Generator.Element func partitionBy(fu: (Element)->Bool)->([Element],[Element]){ var first=[Element]() var second=[Element]() for el in self { if fu(el) { first.append(el) }else{ second.append(el) } } return (first,second) } } let part = [82, 58, 76, 49, 88, 90].partitionBy{$0 < 60} part // ([58, 49], [82, 76, 88, 90])實(shí)際上,這不是單行代碼,而且使用了命令式的解法。能不能使用 filter 對它略作改進(jìn)呢?
extension SequenceType{ func anotherPartitionBy(fu: (Self.Generator.Element)->Bool)->([Self.Generator.Element],[Self.Generator.Element]){ return (self.filter(fu),self.filter({!fu($0)})) } } let part2 = [82, 58, 76, 49, 88, 90].anotherPartitionBy{$0 < 60} part2 // ([58, 49], [82, 76, 88, 90])
這種解法略好一些,但是他遍歷了序列兩次。而且為了用單行代碼實(shí)現(xiàn),我們刪除了閉合函數(shù),這會導(dǎo)致很多重復(fù)的內(nèi)容(過濾函數(shù)和數(shù)組會在兩處被用到)。
能不能只用單個(gè)數(shù)據(jù)流就對原來的序列進(jìn)行轉(zhuǎn)換,把兩個(gè)部分分別存入一個(gè)元組中呢?答案是是可以的,使用reduce 方法:
var part3 = [82, 58, 76, 49, 88, 90].reduce( ([],[]), combine: { (a:([Int],[Int]),n:Int) -> ([Int],[Int]) in (n<60) ? (a.0+[n],a.1) : (a.0,a.1+[n]) }) part3 // ([58, 49], [82, 76, 88, 90])
這里我們創(chuàng)建了一個(gè)用于保存結(jié)果的元組,它包含兩個(gè)部分。然后依次取出原來序列中的元素,根據(jù)過濾結(jié)果將它放到第一個(gè)或第二個(gè)部分中。
我們終于用真正的單行代碼解決了這個(gè)問題。不過有一點(diǎn)需要注意,我們使用 append 方法來構(gòu)造兩個(gè)部分的數(shù)組,所以這實(shí)際上比前兩種實(shí)現(xiàn)慢一些。
上述的某些語言不需要依賴外部的庫,而且默認(rèn)有不止一種方案可以處理 XML 格式的數(shù)據(jù)(比如 Scala 自身就可以將 XML 解析成對象,盡管實(shí)現(xiàn)方法比較笨拙),但是 (Swift 的)Foundation 庫僅提供了 SAX 解析器,叫做 NSXMLParser。你也許已經(jīng)猜到了:我們不打算使用這個(gè)。
在這種情況下,我們可以選擇一些開源的庫。這些庫有的用 C 實(shí)現(xiàn),有的用 Objective-C 實(shí)現(xiàn),還有的是純 Swift 實(shí)現(xiàn)。
這次,我們打算使用純 Swift 實(shí)現(xiàn)的庫:AEXML:
let xmlDoc = try? AEXMLDocument(xmlData: NSData(contentsOfURL: NSURL(string:"https://www.ibiblio.org/xml/examples/shakespeare/hen_v.xml")!)!) if let xmlDoc=xmlDoc { var prologue = xmlDoc.root.children[6]["PROLOGUE"]["SPEECH"] prologue.children[1].stringValue // Now all the youth of England are on fire, prologue.children[2].stringValue // And silken dalliance in the wardrobe lies: prologue.children[3].stringValue // Now thrive the armourers, and honour's thought prologue.children[4].stringValue // Reigns solely in the breast of every man: prologue.children[5].stringValue // They sell the pasture now to buy the horse, }
我們有多種方式求出 sequence 中的最大和最小值,其中一種方式是使用 minElement 和 maxElement 函數(shù):
//Find the minimum of an array of Ints [10,-22,753,55,137,-1,-279,1034,77].sort().first [10,-22,753,55,137,-1,-279,1034,77].reduce(Int.max, combine: min) [10,-22,753,55,137,-1,-279,1034,77].minElement() //Find the maximum of an array of Ints [10,-22,753,55,137,-1,-279,1034,77].sort().last [10,-22,753,55,137,-1,-279,1034,77].reduce(Int.min, combine: max) [10,-22,753,55,137,-1,-279,1034,77].maxElement()
某些語言支持用簡單透明的方式允許對序列的并行處理,比如使用 map 和 flatMap 這樣的函數(shù)。這使用了底層的線程池,可以加速多個(gè)依次執(zhí)行但又彼此獨(dú)立的操作。
Swift 還不具備這樣的特性,但我們可以用 GCD 實(shí)現(xiàn):
http://moreindirection.blogspot.it/2015/07/gcd-and-parallel-collections-in-swift.html
古老而優(yōu)秀的埃拉托色尼選篩法被用于找到所有小于給定的上限 n 的質(zhì)數(shù)。
首先將所有小于 n 的整數(shù)都放入一個(gè)序列(sequence)中,這個(gè)算法會移除每個(gè)數(shù)字的倍數(shù),直到剩下的所有數(shù)字都是質(zhì)數(shù)。為了加快執(zhí)行速度,我們其實(shí)不必檢查每一個(gè)數(shù)字的倍數(shù),當(dāng)檢查到 n 的平方根時(shí)就可以停止。
基于以上定義,最初的實(shí)現(xiàn)可能是這樣的:
var n = 50 var primes = Set(2...n) (2...Int(sqrt(Double(n)))).forEach{primes.subtractInPlace((2*$0).stride(through:n, by:$0))} primes.sort()
在外層的區(qū)間里,我們遍歷每一個(gè)需要檢查的數(shù)字。對于每一個(gè)數(shù)字,我們使用 stride(through:Int by:Int)函數(shù)計(jì)算出由它的倍數(shù)構(gòu)成的序列。最初,我們用所有 2 到 n 的整數(shù)構(gòu)造了一個(gè)集合(Set),然后從集合中減掉每一個(gè)生成的序列中的元素。
不過正如你所見,為了真正的刪除掉這些倍數(shù),我們使用了一個(gè)外部的可變集合,這會帶來副作用。
我們總是應(yīng)該嘗試消除副作用,所以我們先計(jì)算所有的子序列,然后調(diào)用 flatMap 方法將其中所有的元素展開,存放到單個(gè)數(shù)組中,最后再從原始的集合中刪除這些整數(shù)。
var sameprimes = Set(2...n) sameprimes.subtractInPlace((2...Int(sqrt(Double(n)))) .flatMap{ (2*$0).stride(through:n, by:$0)}) sameprimes.sort()
這種寫法更加清楚,它也是 使用 flatMap 展開嵌套數(shù)組 這篇文章很好的一個(gè)例子。
既然是福利,自然并非每個(gè)人都知道這一點(diǎn)。和其他具有元組類型的語言一樣,Swift 的元組可以被用來交換兩個(gè)變量的值,代碼很簡潔:
var a=1,b=2 (a,b) = (b,a) a //2 b //1
以上就是全部內(nèi)容,正如我們預(yù)料的那樣,Swift 和其他語言一樣富有表現(xiàn)力。
你還有其他用 Swift 實(shí)現(xiàn)的有趣的單行代碼想與我們分享么?如果有,請讓我知道
如果你想發(fā)表評論,請?jiān)?nbsp;Twitter 上和我聯(lián)系。
文章來源: 走心的 Swift 翻譯組