Swift 中 10 個震驚小伙伴的單行代碼

qqlsn 8年前發布 | 54K 次閱讀 Swift Apple Swift開發

作者:uraimo, 原文鏈接 ,原文日期:2016-01-06

譯者: bestswifter ;校對: numbbbbb ;定稿: 小鍋

幾年前,函數式編程的復興正值巔峰,一篇介紹 Scala 中 10 個單行函數式代碼 的博文在網上走紅。很快地,一系列使用其他語言實現這些單行代碼的文章也隨之出現,比如 HaskellRubyGroovyClojurePythonC#F#CoffeeScript

我們永遠無法得知有多少人在社交聚會中對這些單行代碼留下了深刻的印象,但根據我的猜測,越復雜的例子越能激勵我們學習更多函數式編程的知識,至少對外行人來說是這樣。

通過使用單行代碼完成同樣的 10 個練習,我們來看看 Swift 和其他語言之間的較量。在這個過程中,你也許還能學到一些有趣的東西(參見 #6 和 #10)。

你可以從 GitHubzipped 上下載本文的 playground。

#1 將數組中每個元素的值乘以 2

第一個例子中沒什么干貨, 我們都知道 只要使用 map 函數就可以簡單地解決問題:

(1...1024).map{$0 * 2}

#2 求一組數字的和

這個問題可以通過使用 reduce 方法和加號運算符解決,這是因為加號運算符實際上也是一個函數。不過這個解法是非常顯而易見的,待會兒我們會看到 reduce 方法更具有創造力的使用。

(1...1024).reduce(0,combine: +)

#3 證明字符串中含有某個單詞

我們使用 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 建議這樣寫會更好:

words.contains(tweet.containsString)

這種寫法更加簡練。另外,也可以這樣寫:

tweet.characters
  .split(" ")
  .lazy
  .map(String.init)
  .contains(Set(words).contains)

#4 讀取一個文件

和其他語言不同,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)轉換為字符串。

#5 祝你生日快樂

這段代碼會將“祝你生日快樂”這首歌的歌詞輸出到控制臺中,它在一段區間內簡單的使用了 map 函數,同時也用到了三元運算符。

let name = "uraimo"
(1...4).forEach{print("Happy Birthday " + (($0 == 3) ? "dear \(name)":"to You"))}

#6 數組過濾

假設我們需要使用一個給定的過濾函數將一個序列(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])

這種解法略好一些,但是他遍歷了序列兩次。而且為了用單行代碼實現,我們刪除了閉合函數,這會導致很多重復的內容(過濾函數和數組會在兩處被用到)。

能不能只用單個數據流就對原來的序列進行轉換,把兩個部分分別存入一個元組中呢?答案是是可以的,使用 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 方法來構造兩個部分的數組,所以這實際上比前兩種實現慢一些。

#7 獲取并解析 XML 格式的網絡服務

上述的某些語言不需要依賴外部的庫,而且默認有不止一種方案可以處理 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,
}

#8 找到數組中最小(或最大)的元素

我們有多種方式求出 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()

#9 并行處理

某些語言支持用簡單透明的方式允許對序列的并行處理,比如使用 map 和 flatMap 這樣的函數。這使用了底層的線程池,可以加速多個依次執行但又彼此獨立的操作。

Swift 還不具備這樣的特性,但我們可以用 GCD 實現:

http://moreindirection.blogspot.it/2015/07/gcd-and-parallel-collections-in-swift.html

#10 埃拉托色尼選篩法

古老而優秀的埃拉托色尼選篩法被用于找到所有小于給定的上限 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 展開嵌套數組 這篇文章很好的一個例子。

#11 福利:使用析構交換元組中的值

既然是福利,自然并非每個人都知道這一點。和其他具有元組類型的語言一樣,Swift 的元組可以被用來交換兩個變量的值,代碼很簡潔:

var a=1,b=2

(a,b) = (b,a)
a //2
b //1

以上就是全部內容,正如我們預料的那樣,Swift 和其他語言一樣富有表現力。

來自: http://swift.gg/2016/04/18/10-Swift-One-Liners-To-Impress-Your-Friends/

 本文由用戶 qqlsn 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!