做者:uraimo,原文連接,原文日期:2016-01-06
譯者:bestswifter;校對:numbbbbb;定稿:小鍋html
幾年前,函數式編程的復興正值巔峯,一篇介紹 Scala 中 10 個單行函數式代碼的博文在網上走紅。很快地,一系列使用其餘語言實現這些單行代碼的文章也隨之出現,好比 Haskell,Ruby,Groovy,Clojure,Python,C#,F#,CoffeeScript。python
咱們永遠沒法得知有多少人在社交聚會中對這些單行代碼留下了深入的印象,但根據個人猜想,越複雜的例子越能激勵咱們學習更多函數式編程的知識,至少對外行人來講是這樣。git
經過使用單行代碼完成一樣的 10 個練習,咱們來看看 Swift 和其餘語言之間的較量。在這個過程當中,你也許還能學到一些有趣的東西(參見 #6 和 #10)。github
第一個例子中沒什麼乾貨,咱們都知道只要使用 map
函數就能夠簡單地解決問題:編程
(1...1024).map{$0 * 2}
這個問題能夠經過使用 reduce
方法和加號運算符解決,這是由於加號運算符實際上也是一個函數。不過這個解法是很是顯而易見的,待會兒咱們會看到 reduce
方法更具備創造力的使用。swift
(1...1024).reduce(0,combine: +)
咱們使用 filter
方法判斷一條推文中是否至少含有一個被選中的關鍵字:數組
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 建議這樣寫會更好:ruby
words.contains(tweet.containsString)
這種寫法更加簡練。另外,也能夠這樣寫:網絡
tweet.characters .split(" ") .lazy .map(String.init) .contains(Set(words).contains)
和其餘語言不一樣,Swift 不能使用內建的函數讀取文件,並把每一行存放到數組中。不過咱們能夠結合 split
和 map
方法寫一段簡短的代碼,這樣就無需使用 for
循環:
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
函數和字符串的構造方法,將數組中的每一個元素從字符數組(characters)轉換爲字符串。
這段代碼會將「祝你生日快樂」這首歌的歌詞輸出到控制檯中,它在一段區間內簡單的使用了 map
函數,同時也用到了三元運算符。
let name = "uraimo" (1...4).forEach{print("Happy Birthday " + (($0 == 3) ? "dear \(name)":"to You"))}
假設咱們須要使用一個給定的過濾函數將一個序列(sequence)分割爲兩部分。不少語言除了有常規的 map
,flatMap
,reduce
,filter
等函數外,還有一個 partitionBy
函數剛好能夠完成這個需求。正如你所知,Swift 沒有相似的函數(咱們不想在這裏使用 NSArray 中的函數,並經過 NSPredicate 實現過濾功能)。
因此,咱們能夠經過拓展 SequenceType
,併爲它添加 partitionBy
函數來解決這個問題。咱們使用這個函數將整數數組分割爲兩部分:
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])
實際上,這不是單行代碼,並且使用了命令式的解法。能不能使用 filter
對它略做改進呢?
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])
<!--enclosing function 這邊不太理解-->
這種解法略好一些,可是他遍歷了序列兩次。並且爲了用單行代碼實現,咱們刪除了閉合函數,這會致使不少重複的內容(過濾函數和數組會在兩處被用到)。
能不能只用單個數據流就對原來的序列進行轉換,把兩個部分分別存入一個元組中呢?答案是是能夠的,使用 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])
這裏咱們建立了一個用於保存結果的元組,它包含兩個部分。而後依次取出原來序列中的元素,根據過濾結果將它放到第一個或第二個部分中。
咱們終於用真正的單行代碼解決了這個問題。不過有一點須要注意,咱們使用 append
方法來構造兩個部分的數組,因此這實際上比前兩種實現慢一些。
上述的某些語言不須要依賴外部的庫,並且默認有不止一種方案能夠處理 XML 格式的數據(好比 Scala 自身就能夠將 XML 解析成對象,儘管實現方法比較笨拙),可是 (Swift 的)Foundation 庫僅提供了 SAX 解析器,叫作 NSXMLParser。你也許已經猜到了:咱們不打算使用這個。
在這種狀況下,咱們能夠選擇一些開源的庫。這些庫有的用 C 實現,有的用 Objective-C 實現,還有的是純 Swift 實現。
此次,咱們打算使用純 Swift 實現的庫: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
函數:
//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
這樣的函數。這使用了底層的線程池,能夠加速多個依次執行但又彼此獨立的操做。
Swift 還不具有這樣的特性,但咱們能夠用 GCD 實現:
http://moreindirection.blogspot.it/2015/07/gcd-and-parallel-collections-in-swift.html
古老而優秀的埃拉托色尼選篩法被用於找到全部小於給定的上限 n 的質數。
首先將全部小於 n 的整數都放入一個序列(sequence)中,這個算法會移除每一個數字的倍數,直到剩下的全部數字都是質數。爲了加快執行速度,咱們其實沒必要檢查每個數字的倍數,當檢查到 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()
在外層的區間裏,咱們遍歷每個須要檢查的數字。對於每個數字,咱們使用 stride(through:Int by:Int)
函數計算出由它的倍數構成的序列。最初,咱們用全部 2 到 n 的整數構造了一個集合(Set),而後從集合中減掉每個生成的序列中的元素。
不過正如你所見,爲了真正的刪除掉這些倍數,咱們使用了一個外部的可變集合,這會帶來反作用。
咱們老是應該嘗試消除反作用,因此咱們先計算全部的子序列,而後調用 flatMap
方法將其中全部的元素展開,存放到單個數組中,最後再從原始的集合中刪除這些整數。
var sameprimes = Set(2...n) sameprimes.subtractInPlace((2...Int(sqrt(Double(n)))) .flatMap{ (2*$0).stride(through:n, by:$0)}) sameprimes.sort()
這種寫法更加清楚,它也是 使用 flatMap 展開嵌套數組 這篇文章很好的一個例子。
既然是福利,天然並不是每一個人都知道這一點。和其餘具備元組類型的語言同樣,Swift 的元組能夠被用來交換兩個變量的值,代碼很簡潔:
var a=1,b=2 (a,b) = (b,a) a //2 b //1
以上就是所有內容,正如咱們預料的那樣,Swift 和其餘語言同樣富有表現力。
你還有其餘用 Swift 實現的有趣的單行代碼想與咱們分享麼?若是有,請讓我知道
感謝 @oisdk 審覈這篇文章。
若是你想發表評論,請在 Twitter 上和我聯繫。
本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 http://swift.gg。