將 Swift 序列切分爲頭部和尾部

做者:Ole Begemann,原文連接,原文日期:2018-11-29git

譯者:WAMaker;校對:numbbbbbBigNerdCoding;定稿:Forelaxgithub

函數式編程語言的一個經常使用範式是把一個列表切分爲頭部(第一個元素)和尾部(其他元素)。在 Haskell 中,x:xs 會匹配非空列表,將頭部綁定給變量 x,尾部綁定給 xs。編程

Swift 不是一門函數式編程語言。既沒有內置的 List 類型,也沒有集合的特定匹配語法。[1]swift

集合(Collections)

儘管如此,將 SequenceCollection 切分紅頭部和尾部偶爾頗有用。對於集合來講這很容易:api

extension Collection {
    var headAndTail: (head: Element, tail: SubSequence)? {
        guard let head = first else { return nil }
        return (head, dropFirst())
    }
}

if let (firstLetter, remainder) = "Hello".headAndTail {
    // firstLetter: Character == "H"
    // remainder: Substring == "ello"
}
複製代碼

序列(Sequence)

對於序列來講卻很困難,由於它們能夠是單向(single-pass)的:一些序列只能被迭代一次,迭代器會消耗其中的元素。以網絡流爲例,一旦你從緩衝區裏讀取了一個字節,操做系統便將它拋棄了。你沒法讓它從新來一遍。網絡

一個可能的解決方案是 建立一個迭代器 讀取第一個元素,並把當前的迭代器狀態包裹進一個新的 AnySequence 實例:app

extension Sequence {
var headAndTail: (head: Element, tail: AnySequence<Element>)? {
    var iterator = makeIterator()
    guard let head = iterator.next() else { return nil }
    let tail = AnySequence { iterator }
    return (head, tail)
}
}
複製代碼

以上代碼可以實現功能,但不是一個好的通用解決方案,尤爲是對知足 Collection 的類型而言。將尾部包進 AnySequence 會是一個 性能殺手,也不可使用合適的集合類型 SubSequence編程語言

爲了保護集合的 SubSequence 類型,最好給 CollectionSequence 分別寫擴展。(咱們也將會看到,這是 Swift 5 所推崇的方案,這點會在後面談到。)函數式編程

保護 SubSequence 類型

我沒有找到一個通用的方案,可以讓尾部的 SubSequence 類型無缺,也同時能讓單向序列正常工做。很感謝 Dennis Vennink 可以找出一個解決方案並 分享給我。下面是 他的代碼(我略微對格式進行了修改):函數

extension Sequence {
var headAndTail: (head: Element, tail: SubSequence)? {
    var first: Element? = nil
    let tail = drop(while: { element in
        if first == nil {
            first = element
            return true
        } else {
            return false
        }
    })
    guard let head = first else {
        return nil
    }
    return (head, tail)
}
}
複製代碼

Dennis 的竅門是調用 Sequence.drop(while:),爲尾部保留了 SubSequence 類型,同時在 drop(while:) 內部「捕獲」了第一個元素。幹得漂亮!

Swift 5

上面的代碼使用 Swift 4.2。在 Swift 5 中因爲序列再也不會有關聯 SubSequence 類型,只存在於集合中(Swift Evolution proposal SE-0234),以上代碼會崩潰。[2]

這個改變有不少優點,但一樣意味着不可能有一種通用的方法可以讓 SubSequence 同時對 SequenceCollection 有效。

相對的,咱們把那個簡單的解決方案添加給 Collection

extension Collection {
var headAndTail: (head: Element, tail: SubSequence)? {
    guard let head = first else { return nil }
    return (head, dropFirst())
}
}
複製代碼

若是咱們須要讓 Sequence 擁有一樣的功能,就須要添加一個獨立的擴展,使用新的 DropWhileSequence 做爲返回類型的尾部:

extension Sequence {
var headAndTail: (head: Element, tail: DropWhileSequence<Self>)? {
    var first: Element? = nil
    let tail = drop(while: { element in
        if first == nil {
            first = element
            return true
        } else {
            return false
        }
    })
    guard let head = first else {
        return nil
    }
    return (head, tail)
}
}
複製代碼

(實現和以前的代碼同樣,僅僅改變了返回的類型。)


[1]爲集合添加一種模式匹配結構做爲一個 可行 的特性已經在論壇屢次被 說起。有了它,你能夠像下面這樣將一個有序集合解構成頭部和尾部:

let numbers = 1...10
let [head, tail...] = numbers
// head == 1
// tail == 2...10
複製代碼

switch 表達式中會頗有用。

[2]很遺憾咱們被誤導性的名字 Sequence 給束縛住了。要將 Collection.SubSequence 重命名成更合適的 Slice 會形成 嚴重的代碼破壞

本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 swift.gg

相關文章
相關標籤/搜索