Swift 里的数组去重方案

栏目: Swift · 发布时间: 5年前

内容简介:最近更新:5th 三月, 2019在使用 Swift 进行开发落格输入法时,我遇到了一个很有意思的问题——众所周知,输入法的候选在计算出来后总会有可能是重复的选项(比如码表和词库中都有某个词,也许他们编码不同,但字是一样的之类),这时候就需要去重,但又要保持候选的先后顺序不变。

最近更新:5th 三月, 2019

在使用 Swift 进行开发落格输入法时,我遇到了一个很有意思的问题—— 去重

众所周知,输入法的候选在计算出来后总会有可能是重复的选项(比如码表和词库中都有某个词,也许他们编码不同,但字是一样的之类),这时候就需要去重,但又要保持候选的先后顺序不变。

别人的解决方案

如果你去网上找,那么你可能找到的是这样的:

extension Array where Element : Hashable {
    var unique: [Element] {
        return Array(Set(self))
    }
}

来源: https://stackoverflow.com/questions/27624331/unique-values-of-array-in-swift

这样的:

extension Array where Element: Hashable {
    func removingDuplicates() -> [Element] {
        var addedDict = [Element: Bool]()
 
        return filter {
            addedDict.updateValue(true, forKey: $0) == nil
        }
    }
 
    mutating func removeDuplicates() {
        self = self.removingDuplicates()
    }
}

来源: https://www.hackingwithswift.com/example-code/language/how-to-remove-duplicate-items-from-an-array

或者是这样的:

extension Array where Element: Equatable {
    mutating func removeDuplicates() {
        var result = [Element]()
        for value in self {
            if !result.contains(value) {
                result.append(value)
            }
        }
        self = result
    }
}

来源: https://medium.com/if-let-swift-programming/swift-array-removing-duplicate-elements-128a9d0ab8be

问题

我们先说上文中的第一个代码块,他直接用了 Set ,众所周知,这东西和字典( Dictionary )一个样,实际上是 没有顺序 的,所以你把 Array 转换为 Set 再转换回 Array 确实达到了去重的目的,但极有可能让原本的顺序错乱。

在 Swift 中, Set 一定程度上 是能够保持顺序的,比如上文来源网页中的例子实际上就是保持了原本的顺序的,这只能是一个 巧合 ,因为它并 不保证 任何内容都是原来的顺序的。

第二个代码块的实现实际上已经非常不错了,事实上它是我在写这篇文章的时候意外发现的一种实现方式,利用的就是 Array filter 方法并使用一个 Dictionary 进行过滤,它占用了一点点额外的空间,但无可厚非。

第三个代码块问题在于直接用 Array contains 方法来判断存在,这是一种非常不好的方式,实际上我在在字符串中 快速查找一文中曾讨论过,Swift 中几乎所有 contains 都没有同等类型的 index 或者 range 来的快,何况 Array 的查找……

更高端的解决方案

实际上,你还有更高级的解决方案,比如传说中的“ 有序字典 ”,不过很遗憾的是 Swift 基础框架中并没有给出这样高端的算法,比如一个 红黑树 实现。不过,在我找到几个 红黑树 进行比较之后,实际上这种高级算法更慢(也许是因为我的具体场景无法体现高级算法的优势,比如需要去重的数量不够多?)

最终实现

总之,我最后还是根据我的经验实现了我自己的版本,实际上上文中的第二个代码块(用字典实现的那个)已经很不错了,不过,这里还是贴出我的代码,并对其讨论:

extension Array where Element:Hashable {
    var unique:[Element] {
        var uniq = Set<Element>()
        uniq.reserveCapacity(self.count)
        return self.filter {
            return uniq.insert($0).inserted
        }
    }
}

我的实现中,使用了 Set 作为过滤器,因为它主要就是干这个的 :)

也有人说 Set 实际上就是红黑树实现,这里我是要打个问号的,Swift 中的 Set 具体是用什么算法实现的我并没有查到,但似乎并不是红黑树。

这里重要的地方有两点,首先是 . reserveCapacity ,实际上对于 Swift 来说, Array Dictionary Set 这类集合类型的空间是动态分配的,绝大部分时间你不需要手动去为它设定容量大小,但这里我们追求速度,所以我手动为它设定了 Array 的长度,因为这里目的是去重,所以最大长度绝对 不会 超出原本数据的大小,这样就避免了 Set 在去重过程中添加元素导致扩容,这个扩容的过程,实际上也是有很大的时间成本的。

另外一点是 Set . insert ,通常我们会忽略掉 . insert 的返回值,毕竟它的声明就是包含了 @ discardableResult 的,但如果你仔细观察,会发现这个方法返回一个元组,类型是 ( inserted : Bool , memberAfterInsert : Set < Element > . Element ) ,它告诉了你,这个元素是否成功插入,以及这个元素本身——也就是说,如果已经存在,那么插入失败。

这样,我们就得到了一个快速、精简的 Swift unique 方法。

讨论

实际上,乍一看我的这个实现似乎也没什么了不起的地方,毕竟,也就是个集合罢了,那么,如果是直接一点的实现,那么大多数人可能会想到类似的,比如这样:

var unique:[Element] {
        var uniq = Set<Element>()
        var result:[Element] = []
        
        for v in self {
            guard uniq.firstIndex(of: v) == nil else {continue}
            uniq.insert(v)
            result.append(v)
        }
        
        return result
    }

这个算是上边实现的繁琐版本,但它很直观,这里我使用了 firstIndex ( of : 来判断存在,要比 contains 快那么一点点,具体效果如何呢?我们来用这样的的代码测试一下:

let data = [1,2,3,4,5,6,7,8,9,0,9,8,7,6,5,4,3,2,1,4,2,3,5,6,7,45,3,43,99,687,8,5,3]
var date = Date()
 
for _ in 0..<9999 {
    let a = data.unique
}
print(Date().timeIntervalSince(date)*100)

我们对一个随手写的数组进行去重 9999 次,计算时间,得到的结果单位是毫秒(ms): 16 . 754305362701416

看起来挺快的,对吗?那么我们来用同样的测试代码,试试上文中提到的用字典实现去重,我把代码再贴过来:

var unique:[Element] {
        var addedDict = [Element: Bool]()
        return filter {
            addedDict.updateValue(true, forKey: $0) == nil
        }
    }

同样去重 9999 次,得到时间: 14 . 261806011199951 看起来不错呢,快了 2 毫秒!

最后,是我的实现:

var unique:[Element] {
        var uniq = Set<Element>()
        uniq.reserveCapacity(self.count)
        return self.filter {
            return uniq.insert($0).inserted
        }
    }

结果: 10 . 364902019500732 ms


以上所述就是小编给大家介绍的《Swift 里的数组去重方案》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

OpenCV3编程入门

OpenCV3编程入门

毛星云 / 电子工业出版社 / 2015-2 / 79

OpenCV在计算机视觉领域扮演着重要的角色。作为一个基于开源发行的跨平台计算机视觉库,OpenCV实现了图像处理和计算机视觉方面的很多通用算法。本书以当前最新版本的OpenCV最常用最核心的组件模块为索引,深入浅出地介绍了OpenCV2和OpenCV3中的强大功能、性能,以及新特性。书本配套的OpenCV2和OpenCV3双版本的示例代码包中,含有总计两百多个详细注释的程序源代码与思路说明。读者......一起来看看 《OpenCV3编程入门》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

html转js在线工具
html转js在线工具

html转js在线工具