【问题标题】:How to use first/head and rest/tail with Swift?如何在 Swift 中使用 first/head 和 rest/tail?
【发布时间】:2016-10-13 21:06:58
【问题描述】:

为了以函数式风格使用 Swift,我们应该如何处理 headtails 列表? Arrays 和 ArraySlices 是否合适(似乎是因为 ArraySlice 是获取子列表的有效机制)?将Array 转换为ArraySlice 并使用.first!.dropFirst() 作为headtail 的等价物是否是正确的机制?

作为添加数字列表的示例:

func add(_ nums: ArraySlice<Int>) -> Int {
    if nums.count == 0 {
        return 0
    } else {
        return nums.first! + add(nums.dropFirst())
    }
}

【问题讨论】:

    标签: swift recursion functional-programming swift3 tail


    【解决方案1】:

    Array 有一个初始化器 (init(_:)),它可以从任何 Sequence 生成 Array,例如 ArraySlice。但是,使用它会强制复制数组数据,这使得像这样的简单求和算法实际上具有O(nums.count^2) 的性能,即使它看起来只扫描数组一次。

    func sum(_ nums: [Int]) -> Int {
        guard let head = nums.first else { return 0 } //base case, empty list.
        return head + sum(Array(nums.dropFirst()))
    }
    
    let input = Array(1...10)
    let output = sum(input)
    print(output)
    

    为了解决这个问题,一个更好的实现将改为仅在 ArraySlices 上运行,允许无副本切片,但这需要首先将输入 Array 转换为 ArraySlice。幸运的是,内部函数可以帮助使这对公共 API 透明,但它确实使代码更长。

    func sum(_ nums: [Int]) -> Int {
        func sum(_ nums: ArraySlice<Int>) -> Int {
            guard let head = nums.first else { return 0 } //base case, empty list.
            return head + sum(nums.dropFirst())
        }
        return sum(ArraySlice(nums))
    }
    

    但真的,正如matt 所说,不要这样做。编程的头/尾方法在一种通过模式匹配、良好的编译器优化、尾调用优化等很好地促进它的语言中是有意义的。Swift 的设计鼓励使用reduce。它不仅更短、更易读,而且性能也更高。

    作为比较,以下是典型的 Swift 方法:

    extension Sequence where Iterator.Element: Integer {
        func sum() -> Iterator.Element {
            return self.reduce(0, +)
        }
    }
    
    • 它更简单、更短。
    • 它是多态的,所以它适用于任何Sequence,而不是仅限于Array
    • 它对任何Integer 类型都是通用的,而不仅仅是Int。所以这些都有效:

      print(Array<UInt  >(1...10).sum())
      print(Array<UInt8 >(1...10).sum())
      print(Array<UInt16>(1...10).sum())
      print(Array<UInt32>(1...10).sum())
      print(Array<UInt64>(1...10).sum())
      print(Array< Int  >(1...10).sum())
      print(Array< Int8 >(1...10).sum())
      print(Array< Int16>(1...10).sum())
      print(Array< Int32>(1...10).sum())
      print(Array< Int64>(1...10).sum())
      

    但是,如果您坚持采用这种头/尾方法,您可以尝试以下两种技术之一:

    extension Collection {
        func headTail1<Head, Tail, ReturnType>(_ closure: (Head?, Tail) -> ReturnType) -> ReturnType 
            where Head == Self.Element, Tail == Self.SubSequence {
            return closure(self.first, self.dropFirst())
        }
    
        func headTail2<Head, Tail>() ->(Head?, Tail)
            where Head == Self.Element, Tail == Self.SubSequence {
            return (self.first, self.dropFirst())
        }
    }
    
    func sum1<C: Collection, I: Numeric>(_ nums: C) -> I
        where C.Element == I {
        return nums.headTail1 { head, tail in
            guard let head = head else { return 0 } //base case, empty list
            return head + sum(tail)
        }
    }
    
    func sum2<C: Collection, I: Numeric>(_ nums: C) -> I
        where C.Element == I {
        let (_head, tail) = nums.headTail2()
        guard let head = _head else { return 0 } //base case, empty list
        return head + sum(tail)
    }
    
    print(sum(Array(1...10)))
    

    此代码抽象出列表如何拆分为头部和尾部的细节,让您只需担心为您提供的headtail 即可编写sum

    【讨论】:

    • Array 类的有趣扩展。 reduce 并不能解决所有的迭代和递归需求,所以这无关紧要。
    • @at。我回答你的问题了吗?
    • 是的,我希望在 Swift 中有一个更优雅的 headtail 机制,但也许这已经很优雅了..
    • 这就像期待 swift 有 GOTO,因为汇编有:有非常令人信服的理由反对它。
    【解决方案2】:

    您的示例的问题是您不会使用headtail 添加数字列表。你会打电话给reduce

    let nums = [1,2,3,4,5]
    let sum = nums.reduce(0,+)
    

    所以,虽然我和下一个人一样喜欢 LISP/Scheme,但你需要一个更有说服力的案例来说明我们何时需要 headtail,因为我们有mapfilterreduce(等等)。

    【讨论】:

    • 这是一个用于教学目的的示例。
    • 我就是这么说的。这不是一个好的样本。你需要一个更有说服力的案例。
    • @matt ok 然后演示reduce 的实现——我不明白为什么仅仅因为reduce 存在而遍历列表的基本递归函数不是“令人信服的情况”。跨度>
    • 这是因为reduce 函数式的,我们不需要递归来执行此操作。我喜欢 LISP,但 Swift 不是 LISP,所以假装它是(或应该是)似乎很愚蠢。
    • @naomik Reduce 是:更快、更简单、更短、更熟悉。你还需要什么?递归方法实际上没有任何用处,除了一些使用递归的任意愿望。
    猜你喜欢
    • 2022-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-30
    • 2021-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多