将 Swift 序列切分为头部和尾部

栏目: Swift · 发布时间: 5年前

内容简介:作者:Ole Begemann,译者:函数式编程语言的一个常用范式是把一个列表切分为头部(第一个元素)和尾部(其余元素)。在 Haskell 中,

作者:Ole Begemann, 原文链接 ,原文日期:2018-11-29

译者: WAMaker ;校对: numbbbbbBigNerdCoding ;定稿: Forelax

函数式编程语言的一个常用范式是把一个列表切分为头部(第一个元素)和尾部(其余元素)。在 Haskell 中, x:xs 会匹配非空列表,将头部绑定给变量 x,尾部绑定给 xs。

Swift 不是一门函数式编程语言 。既没有内置的 List 类型,也没有集合的特定匹配语法。

集合(Collections)

尽管如此,将 SequenceCollection 切分成头部和尾部偶尔很有用。对于集合来说这很容易:

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 实例:

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 ),以上代码会崩溃。

这个改变有很多优势,但同样意味着不可能有一种通用的方法能够让 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)
}
}

(实现和之前的代码一样,仅仅改变了返回的类型。)

为集合添加一种模式匹配结构作为一个 可行 的特性已经在论坛多次被 提及 。有了它,你可以像下面这样将一个有序集合解构成头部和尾部:

let numbers = 1...10
let [head, tail...] = numbers
// head == 1
// tail == 2...10

switch 表达式中会很有用。

很遗憾我们被误导性的名字 Sequence 给束缚住了。要将 Collection.SubSequence 重命名成更合适的 Slice 会造成 严重的代码破坏

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问http://swift.gg。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

高效团队开发

高效团队开发

[日] 池田尚史、[日] 藤仓和明、[日] 井上史彰 / 严圣逸 / 人民邮电出版社 / 2015-7 / 49.00

本书以团队开发中所必需的工具的导入方法和使用方法为核心,对团队开发的整体结构进行概括性的说明。内容涉及团队开发中发生的问题、版本管理系统、缺陷管理系统、持续集成、持续交付以及回归测试,并且对“为什么用那个工具”“为什么要这样使用”等开发现场常有的问题进行举例说明。 本书适合初次接手开发团队的项目经理,计划开始新项目的项目经理、Scrum Master,以及现有项目中返工、延期问题频发的开发人......一起来看看 《高效团队开发》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具