Swift 泛型,将复杂计算运用于数组

1、假如我们需要写一个函数,它接受一个给定的整型数组,通过计算得到并返回一个新数组,新数组各项为原数组中对应的整型数据加一。这个简单的例子仅仅需要使用一个for循环就能解决,实现如下:

private func incrementArray(xs: [Int]) -> [Int] {
    var result: [Int] = []
    for x in xs {
        result.append(x + 1)
    }
    return result  
}

2、现在假设我们还需要一个函数,用于生成一一个每项都为参数数组对应项两倍的新数组。这同样能很容易使用一个for循环实现:

private func doubleArray(xs: [Int]) -> [Int] {
    var result: [Int] = []
    for x in xs {
        result.append(x * 2)
    }
    return result  
}

3、这两个函数有大量相同的代码,我们能不能将没有区别的地方抽象出来,并单独写一个体现这种模式且更通用的函数呢?像这样的函数需要追加一个新参数来接受一个函数,这个参数能根据各个数组项计算得到新的整型数值:

private func computeIntArray(xs: [Int], transform: (Int) -> Int) -> [Int] {
    var result: [Int] = []
    for x in xs {
        result.append(transform(x))
    }
    return result
}

4、现在,取决于我们想如何根据原数组得到一个新数组,我们可以向函数传递不同的参数来实现,如下

let array = [1, 2, 3, 4, 5]
print(computeIntArray(xs: array, transform: { x in
    x * 2
}))

5、代码仍然不像想象中的那么灵活,假如我们想要得到一个布尔类型的新数组,用于表示原数组中对应的数字是否是偶数,我们可以尝试编写一些像下面这样的代码:

private func isEvenArray(xs: [Int]) -> [Bool] {
    computeIntArray(xs: xs) { x in
        x % 2 == 0
    }
}

不幸的是,这段代码导致了一个类型错误。问题在于我们的 computeIntArray 函数接受一个 (Int) -> Int 类型的参数,也就是说该参数是一个返回整型值的参数。而在 isEvenArray 函数的定义中,我们传递了一个 (Int) -> Bool 类型的参数,于是导致了类型错误。

6、我们该如何解决这个问题呢?一种最普通的方案是定义新版本的 computeBoolArray 函数,接受一个 (Int) -> Bool 类型的参数,在这里的实现我就不写了,但是这个方案的扩展性并不好。如果接下来我们需要计算 String 类型呢?是否还需要定义一个高阶函数来接受 (Int) -> String 类型的参数?

幸运的是,该问题有一个解决方案:我们可以使用 泛型computeIntArraycomputeBoolArray 的定义是相同的,唯一的区别在于类型签名 (type signature) 。假如我们定义一个相似的函数 computeStringArray 来支持 String 类型,其函数体将会与先前两个函数完全一致。事实上,相同部分的代码可以用于 任何 类型。我们真正想做的是写一个能够适用于每种可能类型的泛型函数

private func genericComputeArray(xs: [Int], transform: (Int) -> T) -> [T] {
    var result: [T] = []
    for x in xs {
        result.append(transform(x))
    }
    return result
}

关于这段代码,最有意思的是他的类型签名。理解这个类型签名有助于你将 genericComputeArray 理解为一个函数族。类型参数 T 的每个选择都会确定一个新函数。该函数接受一个整型数组和一个 Int -> T 类型的函数作为参数,并返回一个 [T] 类型的数组。

7、我们仍能进一步将这个函数一般化。没有理由让它仅能对类型为 [Int] 的输入数组进行处理。将数组类型进行抽象,能得到下面这样的类型签名:

private func map(xs:[Element], transform: (Element) -> T) -> [T] {
    var result: [T] = []
    for x in xs {
        result.append(transform(x))
    }
    return result
}

这里我们写了一个 map 函数,它在两个维度都是通用的:对于任何 Element 的数组和 transform: (Element) -> T 函数,它都会生成一个 T 的新数组。这个 map 函数甚至比我们之前看到的 genericComputeArray 函数更通用。事实上,我们可以通过 map 来定义 genericComputeArray:

private func genericComputeArray(xs: [Int], transform: (Int) -> T) -> [T] {
    return map(xs: xs, transform: transform)
}

同样的,上述函数的定义并没有什么太过特别之处:函数接受 xstransform 两个参数之后,将它们传递给 map 函数,然后返回结果。关于这个定义,最有意思非类型莫属。 genericComputeArray(_: transform:)map 函数的一个实例,只是它有一个更具体的类型。实际上,比起定义一个顶层 map 函数,按照 Swift 的惯例将 map 定义为 Array 的扩展会更合适:

extension Array {
    func map(transform: (Element) -> T) -> [T] {
        var result: [T] = []
        for x in self {
            result.append(transform(x))
        }
        return result
    }
}

我们在函数的 transform 参数中所使用的 Element 类型源自于 SwiftArray 中对 Element 所进行的泛型定义。

作为 map(xs, transform) 的替代,我们现在可以通过 xs.map(transform) 来调用 Arraymap 函数:

private func genericComputeArray(xs: [Int], transform: (Int) -> T) -> [T] {
    return xs.map(transform)
}

8、想必你会很乐意听到其实并不需要自己像这样来定义 map 函数,因为它已经是 Swift 标准库的一部分了(实际上,它基于 SequenceType 协议被定义)。本文章的重点并不是说你应该自己定义 map ;我们只是想要告诉你 map 的定义中并没有什么复杂难懂的魔法--你能够轻松地自己定义它!。

你可能感兴趣的:(Swift 泛型,将复杂计算运用于数组)