文章翻译自 Avoiding near-duplicates in sets, 做者Paul Hudson @twostraws是一名优秀的Swifter。
这是我第一次翻译,可能有翻译不到位的地方,若是有任何问题,欢迎反馈。学习学习再学习,加油💪!算法
Julian Schiavo写道:我想用Set集合来保证个人Array中元素是惟一的,可是Set集合中每一个元素都包含一个Date类型的变量,当两个不一样元素仅仅是Date变量不一样的时候,实际上Set中能够同时保存这两个元素,这就出现了重复元素。这种问题该怎么解决呢?swift
这是个好问题,实际上Swift的协议给咱们提供了很聪明的解决方案。api
首先,咱们先看下下面示例代码。结构体NewsStory
有三个属性:id、title、date:数组
struct NewsStory { var id: Int var title: String var date = Date() }
如上代码所示,结构体实例初始化时候会自动将当前时间赋值给date属性。函数
咱们能够用上面的结构体建立三个对象,以下代码所示:学习
let story1 = NewsStory(id: 1, title: "What's new in Swift 5.1?") let story2 = NewsStory(id: 2, title: "What's new in Swift 6.0?") let story3 = NewsStory(id: 3, title: "What's new in Swift 6.1?")
Julian想要保存这些新的对象到一个Set集合而不是数组中,这是一个很明智的选择。所以咱们写下以下的代码:翻译
var stories = Set<NewsStory>() stories.insert(story1) stories.insert(story2) stories.insert(story3) print(stories)
如上代码所示,建立一个保存故事对象的Set,而后将咱们建立的对象添加到Set集合中,而后打印这个Set集合。然而上面的代码没法经过编译:为了每一个元素在Set中都有惟一的标识,咱们须要让NewStory
对象遵照Hashable
协议,Hashable协议可以产生惟一的hash值来标识惟一的一个对象。code
Swift语言这点作得很是好,咱们只须要让一个包含Hashable属性的类型遵照Hashable
协议便可,Hashable协议会自动帮咱们计算这个对象的哈希值。所以咱们须要更新NewStory
结构体以下:对象
struct NewsStory: Hashable { var id: Int var title: String var date = Date() }
到如今,咱们的代码终于可以正常的跑起来啦!blog
而后,Julian遇到的问题并无解决,以下代码因此:
let story4 = NewsStory(id: 1, title: "What's new in Swift 5.1?") stories.insert(story4) print(stories)
当咱们建立一个和已存在对象相同ID和title的NewStory对象,并添加到set集合中,而后打印集合的内容,你会发现如今集合中包含4个对象,而且其中有一个是重复的。
就像前面写的那样,当一个类型遵照Hashable协议而且其属性也都遵照Hashable协议的时候,Swift会帮咱们自动计算这个对象的hash值。计算方法是这样的:获取对象中全部属性的hash值并将它们结合在一块儿。
所以,咱们觉得两个对象是相同的,由于他们有相同的ID和title,可是在Swift看来他们是不一样的,由于他们的date并不相同。
咱们须要作的就是给Swift提供一个自定义的hash计算规则,告诉Swift说"若是两个stories对象的ID和title是相同的,那么他们就是相同的,请忽略date属性。"
为了自定义hash计算规则,咱们须要在NewStory
中实现两个方法:一个是自定义计算hash值,两一个是检查两个对象的惟一标识看是否相等。
第一个方法只使用ID来计算一个story对象的hash值,以下所示:
func hash(into hasher: inout Hasher) { hasher.combine(id) }
第二个方法使用运算符重载来实现一个自定义的==
方法来比较两个story对象是否相同。
static func ==(lhs: NewsStory, rhs: NewsStory) -> Bool { return lhs.id == rhs.id }
到此为止,完美解决问题!咱们实现Hashable版本比Swift自动生成的方法的版本更快,由于咱们的hash函数只计算了ID的hash值,而Swift的版本计算了全部属性的hash值。
示例中咱们只使用了id这个属性值,可是你在项目中也可使用更多的属性来保证你的对象是不一样的。
最终NewsStory
代码以下所示:
struct NewsStory: Hashable { var id: Int var title: String var date = Date() func hash(into hasher: inout Hasher) { hasher.combine(id) } static func ==(lhs: NewsStory, rhs: NewsStory) -> Bool { return lhs.id == rhs.id } }
在咱们的文章结束以前,须要提醒一点, 实际上是Rob Napier的提醒:相等意味着可替换——任何两个相等的对象在代码中均可以相互替换。若是你只比较了id
,那就意味着"若是两个对象有相同的id,可是其它属性是不一样的,我不关心其它属性是什么样的,算法能够自由的返回其中的任意一个。"
最后,也是最重要的一点:若是两个对象相等(由于自定义的==
返回true),那么Swift会自由选择。Swift可能老是选择第一个对象,也可能老是选择第二个对象,或者每次随机选择两个中的一个——这种表如今将来的Swift版本中可能会发生改变。记住这点,由于咱们告诉Swift两个对象是相同的,才会发生这个问题,若是关于对象的选择对你来讲很重要,你须要注意这个问题。