如何使用Swift的第一个/头部和休息/尾部?

时间:2016-10-13 21:06:59

标签: swift recursion functional-programming swift3 tail

为了在功能样式中使用Swift,我们应该如何处理列表的headtailArrayArraySlice是否合适(看起来像是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())
    }
}

2 个答案:

答案 0 :(得分:5)

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)

为了解决这个问题,更好的实现只需在ArraySlice上运行,允许无副本切片,但这需要首先将输入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)))

此代码抽象出列表如何拆分为头尾的详细信息,让您只需担心为您提供的sumhead来编写tail

答案 1 :(得分:1)

您的示例存在的问题是,您不会使用headtail来添加数字列表。您拨打reduce

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

所以,虽然我喜欢LISP / Scheme作为下一个男人,但是当我们需要 head和{时,你需要一个更有说服力的案例{1}},因为我们有tailmapfilter(等等)。