使用泛型的测试驱动自定义 Swift Zip 函数

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 63w+ 字,讲解图 2808+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2200+ 小伙伴加入学习 ,欢迎点击围观

我最近的博文 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)]

我为这些小技术示例创建了一个新存储库,您可以 在此处找到本文的所有源代码


相关文章