我最近的博文 Zip、Map 和泛型 研究了 Swift 的新 zip() 函数,用于将两个数组压缩在一起。 zip 返回一个 SequenceType ,其项数与其最短输入序列一样多。如果我们想创建一个自定义 zip 函数来返回一个与最长输入长度相同的数组并用 nil 填充最短的数组怎么办?
该函数的工作方式类似于:
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
例如,如果输入数组都是非可选字符串 [String] ,我们的压缩结果将需要返回可选字符串 [String?] 以允许填充。因此,再次使用泛型, longZip 的签名将如下所示:
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
这一次,让我们采用 测试驱动的开发 方法。在编写任何代码之前,我们将编写一个测试。我创建了包含两个测试数组的 LongZipTests.swift :
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
我的测试将确保输出的计数为 4,然后遍历输出以确保输出项与输入项匹配,或者在没有输入的情况下为 nil :
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
longZip() 的第一个实现非常简单,使用 max() 找到最长的计数,使用 for 循环遍历两个数组,并使用这些数组中的项目填充返回对象,如果超过计数则使用 nil :
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
按 command-u 执行测试,如果你的手指交叉,这些测试往往会更好地工作:
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
您可以使用 debugDescription() 来加倍确定:
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
现在我们有了一个工作版本,是时候分离代码了。第二版使用 map() 将非可选转换为可选,并使用 extend() 在需要的地方添加 nils:
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
...添加了一个新的测试用例:
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
双手十指交叉...
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
呸!
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
第二版很好,但是有重复的代码和变量,我总是喜欢常量。遗憾的是,
map()
的返回值是不可变的,因此尽管我尽了最大努力,但我一直无法将
map
和
extend
链接在一起。但是,通过将该公共代码移到一个单独的函数
extendWithNil()
中,我已经设法缓解了这种情况。
extendWithNil()
接受一个数组和一个所需新长度的整数,并返回输入数组类型的可选数组:
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
同样,在编写代码之前,让我们编写一个测试来检查计数以及新添加的项目是否为 nil:
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
extendWithNil() 的内容取自 longZip() 的第二个版本:
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
最后, longZip() 的第三版使用 extendWithNil() 实现了一个非常简洁的实现:
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
在用一盒 Honeycomb Fingers 庆祝之前,进行最后的测试:
let arrayOne = [1, 2, 3, 4]
let arrayTwo: [String?] = ["AAA", "BBB"]
let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
我为这些小技术示例创建了一个新存储库,您可以 在此处找到本文的所有源代码 。