在Swift中从数组中删除重复的元素


252

如今,在Swift中,您只需键入Set( yourArray )以使数组唯一。(或根据需要订购)。

在那之前,它是怎么做的?


我可能有一个看起来像下面的数组:

[1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]

或者,实际上,是数据的相似类型部分的任何序列。我要做的是确保每个相同元素中只有一个。例如,上面的数组将变为:

[1, 4, 2, 6, 24, 15, 60]

请注意,删除了2、6和15的重复项以确保每个相同元素中只有一个。Swift是否提供一种轻松实现此目的的方法,还是我必须自己做?


11
最简单的方法是将数组转换为NSSet,如果需要保持顺序NSOrderedSet,则NSSet是对象的无序集合。
Andrea

您可以使用交集函数,如在此类中使用的数组函数:github.com/pNre/ExSwift/blob/master/ExSwift/Array.swift
Edwin Vermeer

不是Swift的一部分,但我使用Dollar。$.uniq(array) github.com/ankurp/Dollar#uniq---uniq
Andy

以下mxcl的答案可能是最优雅,最聪明和最快的答案。这也有助于维持秩序
亲爱的

1
您为什么不只Set从Swift 使用?您将能够提供无序和唯一元素的列表。
TibiaZ

Answers:


133

您可以自己滚动,例如这样(已针对Swift 1.2的Set进行了更新):

func uniq<S : SequenceType, T : Hashable where S.Generator.Element == T>(source: S) -> [T] {
    var buffer = [T]()
    var added = Set<T>()
    for elem in source {
        if !added.contains(elem) {
            buffer.append(elem)
            added.insert(elem)
        }
    }
    return buffer
}

let vals = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let uniqueVals = uniq(vals) // [1, 4, 2, 6, 24, 15, 60]

Swift 3版本:

func uniq<S : Sequence, T : Hashable>(source: S) -> [T] where S.Iterator.Element == T {
    var buffer = [T]()
    var added = Set<T>()
    for elem in source {
        if !added.contains(elem) {
            buffer.append(elem)
            added.insert(elem)
        }
    }
    return buffer
}

并作为扩展Array

extension Array where Element: Hashable {
    var uniques: Array {
        var buffer = Array()
        var added = Set<Element>()
        for elem in self {
            if !added.contains(elem) {
                buffer.append(elem)
                added.insert(elem)
            }
        }
        return buffer
    }
}

12
您也可以将功能的主体实现为var addedDict = [T:Bool](); return filter(source) { addedDict(true, forKey: $0) == nil }
空速Velocity

1
@AirspeedVelocity:您的意思updateValue(true, forKey: $0)...不是addedDict(true, forKey: $0)...
Jawwad 2015年

1
糟糕,很抱歉,我不小心使用了该方法!应该return filter(source) { addedDict.updateValue(true, forKey: $0) == nil }如你所说。
空速速度

21
提醒您一点:避免像这样的简单功能讨论性能,直到您完全依赖它们的性能为止,此时您唯一要做的就是基准测试。由于做出假设,我经常看到无法维护的代码或性能更低的代码。:)而且,这可能更容易理解:let uniques = Array(Set(vals))
Blixt

11
@Blixt同意。再一次,这里的优势在于尊重原始数组元素的顺序。
Jean-Philippe Pellet '02

493

您可以很容易地转换为集合然后再次返回数组:

let unique = Array(Set(originals))

这不能保证保持阵列的原始顺序。


37
有一种在保留数组原始顺序的同时使用集合的方法吗?
Crashalot

6
@Crashalot看到我的答案。
Jean-Philippe Pellet'July

5
如果您需要通过特定属性使对象保持唯一性,则可以在该类上实现Hashable和Equatable协议,而不是仅使用Array-> Set-> Array转换
Fawkes

2
不错!请问这个解决方案的时间复杂度是多少?
JW.ZG '16

2
如果中的元素originals不是则失败Hashable; 只能将Hashable数据类型添加到集合,而可以将任何数据类型添加到数组。
梅基'19

69

这里有很多答案,但是我错过了这个简单的扩展,适用于Swift 2及更高版本:

extension Array where Element:Equatable {
    func removeDuplicates() -> [Element] {
        var result = [Element]()

        for value in self {
            if result.contains(value) == false {
                result.append(value)
            }
        }

        return result
    }
}

使它超级简单。可以这样称呼:

let arrayOfInts = [2, 2, 4, 4]
print(arrayOfInts.removeDuplicates()) // Prints: [2, 4]

根据属性过滤

要基于属性过滤数组,可以使用以下方法:

extension Array {

    func filterDuplicates(@noescape includeElement: (lhs:Element, rhs:Element) -> Bool) -> [Element]{
        var results = [Element]()

        forEach { (element) in
            let existingElements = results.filter {
                return includeElement(lhs: element, rhs: $0)
            }
            if existingElements.count == 0 {
                results.append(element)
            }
        }

        return results
    }
}

您可以如下调用:

let filteredElements = myElements.filterDuplicates { $0.PropertyOne == $1.PropertyOne && $0.PropertyTwo == $1.PropertyTwo }

@Antoine感谢您基于属性的扩展。真的很有用 但是,请您解释一下它是如何工作的。对我来说太难理解了。谢谢
Mostafa Mohamed Raafat

swift 3更新:func filterDuplicates(_ includeElement:(_ lhs:Element,_ rhs:Element)-> Bool)-> [Element] {
cbartel

这个答案的第一部分(extension Array where Element: Equatable)已由stackoverflow.com/a/36048862/1033581取代,后者提供了更强大的解决方案(extension Sequence where Iterator.Element: Equatable)。
心教堂

7
这将具有O(n²)时间性能,这对于大型阵列确实很不利。
Duncan C

您应该使用一组来跟踪到目前为止所看到的元素,以将这种可怕的O(n²)复杂性归结为O(n)
亚历山大-恢复莫妮卡

63

斯威夫特3.0

let uniqueUnordered = Array(Set(array))
let uniqueOrdered = Array(NSOrderedSet(array: array))

1
让uniqueOrderedNames = Array(NSOrderedSet(array:userNames))为![String]如果您有String数组,而不是Any数组
Zaporozhchenko Oleksandr

如果中的元素array不是则失败Hashable; 只能将Hashable数据类型添加到集合,而可以将任何数据类型添加到数组。
梅基'19

在Swift 5.1b5中进行了测试,考虑到元素是可哈希的并且希望保留顺序,因此NSOrderedSet(array:array).array比使用带有set的set的纯swift func uniqued()快一点。我测试了5100个字符串,得出13个唯一值。
dlemex

62

如果将两个扩展名都放入代码中,Hashable则将在可能的情况下使用速度更快的版本,并将该Equatable版本用作备用。

public extension Sequence where Element: Hashable {
  var firstUniqueElements: [Element] {
    var set: Set<Element> = []
    return filter { set.insert($0).inserted }
  }
}

public extension Sequence where Element: Equatable {
  var firstUniqueElements: [Element] {
    reduce(into: []) { uniqueElements, element in
      if !uniqueElements.contains(element) {
        uniqueElements.append(element)
      }
    }
  }
}

如果顺序不重要,则始终可以使用此Set初始值设定项


好,知道了。我无法调用它,因为我的数组是一个结构数组...在我的情况下我将如何处理?20个不同的变量,字符串和[string]的结构
David Seek 2013年

@David Seek听起来您还没有使您的严格的哈希值或等于值成为可​​能。那是对的吗?
Jessy

1
@DavidSeek这样,uniqueArray = nonUniqueArray.uniqueElements
Mert Celik

是的,不用担心。之后就可以正常工作了。已经快两年了:P
David Seek

这将具有O(n²)时间性能,这对于大型阵列确实很不利。
邓肯C

44

编辑/更新Swift 4或更高版本

我们还可以扩展RangeReplaceableCollection协议以使其也可以与StringProtocol类型一起使用:

extension RangeReplaceableCollection where Element: Hashable {
    var orderedSet: Self {
        var set = Set<Element>()
        return filter { set.insert($0).inserted }
    }
    mutating func removeDuplicates() {
        var set = Set<Element>()
        removeAll { !set.insert($0).inserted }
    }
}

let integers = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let integersOrderedSet = integers.orderedSet // [1, 4, 2, 6, 24, 15, 60]

"abcdefabcghi".orderedSet  // "abcdefghi"
"abcdefabcghi".dropFirst(3).orderedSet // "defabcghi"

变异方法:

var string = "abcdefabcghi"
string.removeDuplicates() 
string  //  "abcdefghi"

var substring = "abcdefabcdefghi".dropFirst(3)  // "defabcdefghi"
substring.removeDuplicates()
substring   // "defabcghi"

对于Swift 3,请点击此处


1
我喜欢这个,它也可以和一系列字典一起使用!
DeyaEldeen '16

6
O(N ^ 2)不好:(
亚历山大-恢复莫妮卡

1
@Alexander Leo Dabus已替换了reduce实现,因此现在的复杂性有所不同。
心教堂

1
结果很有趣。对于100万个唯一商品和800万个唯一商品,过滤器版本都更快。但是,基于过滤器的版本花费800万个唯一项的时间长8.38倍(随着O(n)时间的流逝而发),其中基于平面图的版本花费800万个唯一项所花费的时间比7.47倍长于100万个,这表明基于平面图的版本可更好地缩放。基于平面图的版本以某种方式比O(n)时间做得更好!
邓肯C

1
实际上,当我在数组中有64多个项目的情况下运行测试时,基于平面图的版本会更快。
邓肯C

43

斯威夫特4

public extension Array where Element: Hashable {
    func uniqued() -> [Element] {
        var seen = Set<Element>()
        return filter{ seen.insert($0).inserted }
    }
}

的每次尝试insert也会返回一个元组:(inserted: Bool, memberAfterInsert: Set.Element)。请参阅文档

使用返回的值有助于我们避免循环或执行任何其他操作。


7
经过简单的分析后,此方法确实非常快。它比使用reduce(_:_ :),甚至使用reduce(into:_ :)快数百倍
Kelvin

3
@Kelvin因为所有其他算法都是O(n^2),而且没有人注意到。
亚历山大-恢复莫妮卡

@Kelvin这个答案与Eneko Alonso的答案 +我的评论相同(17年6月16日)。
心教堂

27

斯威夫特4

保证继续订购。

extension Array where Element: Equatable {
    func removingDuplicates() -> Array {
        return reduce(into: []) { result, element in
            if !result.contains(element) {
                result.append(element)
            }
        }
    }
}

我现在使用它,仅将方法名称更改为removeDuplicates :)
J. Doe

我猜这个解决方案很紧凑,但是我相信一年前发布的deanWombourne解决方案可能比:效率更高reduce:总体而言,将整个函数编写为:只是整个项目的另一行var unique: [Iterator.Element] = []; for element in self where !unique.contains(element) { unique.append(element) }; return unique。我承认我还没有测试相对表现。
心教堂

3
这将具有O(n²)时间性能,这对于大型阵列确实很不利。
邓肯C

@NickGaens不,不是O(n²)。这没什么大不了的。
亚历山大-恢复莫妮卡

@Cœur reduce还是reduce(into:)没什么大不同。将其重写为不重复调用contains将带来更大的不同。
亚历山大-恢复莫妮卡

16

这里的一个类别SequenceType,其保留了阵列的原始顺序,但使用的是Setcontains查询,以避免O(n)对阵列的成本contains(_:)的方法。

public extension Sequence where Element: Hashable {

    /// Return the sequence with all duplicates removed.
    ///
    /// i.e. `[ 1, 2, 3, 1, 2 ].uniqued() == [ 1, 2, 3 ]`
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234, as 
    ///         per @Alexander's comment.
    func uniqued() -> [Element] {
        var seen = Set<Element>()
        return self.filter { seen.insert($0).inserted }
    }
}

如果您不是Hashable或Equatable,则可以传入谓词以进行相等性检查:

extension Sequence {

    /// Return the sequence with all duplicates removed.
    ///
    /// Duplicate, in this case, is defined as returning `true` from `comparator`.
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234
    func uniqued(comparator: @escaping (Element, Element) throws -> Bool) rethrows -> [Element] {
        var buffer: [Element] = []

        for element in self {
            // If element is already in buffer, skip to the next element
            if try buffer.contains(where: { try comparator(element, $0) }) {
                continue
            }

            buffer.append(element)
        }

        return buffer
    }
}

现在,如果您没有Hashable,但是 Equatable,则可以使用以下方法:

extension Sequence where Element: Equatable {

    /// Return the sequence with all duplicates removed.
    ///
    /// i.e. `[ 1, 2, 3, 1, 2 ].uniqued() == [ 1, 2, 3 ]`
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234
    func uniqued() -> [Element] {
        return self.uniqued(comparator: ==)
    }
}

最后,您可以像这样添加唯一的键路径版本:

extension Sequence {

    /// Returns the sequence with duplicate elements removed, performing the comparison usinig the property at
    /// the supplied keypath.
    ///
    /// i.e.
    ///
    /// ```
    /// [
    ///   MyStruct(value: "Hello"),
    ///   MyStruct(value: "Hello"),
    ///   MyStruct(value: "World")
    ///  ].uniqued(\.value)
    /// ```
    /// would result in
    ///
    /// ```
    /// [
    ///   MyStruct(value: "Hello"),
    ///   MyStruct(value: "World")
    /// ]
    /// ```
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234
    ///
    func uniqued<T: Equatable>(_ keyPath: KeyPath<Element, T>) -> [Element] {
        self.uniqued { $0[keyPath: keyPath] == $1[keyPath: keyPath] }
    }
}

您可以将它们都粘贴到您的应用程序中,Swift会根据序列的Iterator.Element类型选择合适的应用程序。


嘿终于有人O(n)解决了。顺便说一下,您可以将“检查”和“插入”设置操作组合为一个。请参阅stackoverflow.com/a/46354989/3141234
亚历山大-恢复莫妮卡(Monica)

哦,那很聪明:)
deanWombourne

14

https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift的启发,我们可以声明一个功能更强大的工具,该工具能够过滤任何keyPath的唯一性。感谢Alexander对复杂性的各种回答,以下解决方案应该是最佳的。

非变异解决方案

我们扩展了一个功能,该功能能够过滤任何keyPath上的唯一性:

extension RangeReplaceableCollection {
    /// Returns a collection containing, in order, the first instances of
    /// elements of the sequence that compare equally for the keyPath.
    func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> Self {
        var unique = Set<T>()
        return filter { unique.insert($0[keyPath: keyPath]).inserted }
    }
}

注意:如果您的对象不符合RangeReplaceableCollection,但符合Sequence,则可以使用此附加扩展名,但返回类型始终为Array:

extension Sequence {
    /// Returns an array containing, in order, the first instances of
    /// elements of the sequence that compare equally for the keyPath.
    func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> [Element] {
        var unique = Set<T>()
        return filter { unique.insert($0[keyPath: keyPath]).inserted }
    }
}

用法

如果我们希望元素本身具有唯一性,如问题所示,我们可以使用keyPath \.self

let a = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let b = a.unique(for: \.self)
/* b is [1, 4, 2, 6, 24, 15, 60] */

如果我们希望其他事物(例如id一组对象的)具有唯一性,则可以使用我们选择的keyPath:

let a = [CGPoint(x: 1, y: 1), CGPoint(x: 2, y: 1), CGPoint(x: 1, y: 2)]
let b = a.unique(for: \.y)
/* b is [{x 1 y 1}, {x 1 y 2}] */

变异溶液

我们扩展了一个变异函数,该函数能够对任何keyPath的唯一性进行过滤:

extension RangeReplaceableCollection {
    /// Keeps only, in order, the first instances of
    /// elements of the collection that compare equally for the keyPath.
    mutating func uniqueInPlace<T: Hashable>(for keyPath: KeyPath<Element, T>) {
        var unique = Set<T>()
        removeAll { !unique.insert($0[keyPath: keyPath]).inserted }
    }
}

用法

如果我们希望元素本身具有唯一性,如问题所示,我们可以使用keyPath \.self

var a = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
a.uniqueInPlace(for: \.self)
/* a is [1, 4, 2, 6, 24, 15, 60] */

如果我们希望其他事物(例如id一组对象的)具有唯一性,则可以使用我们选择的keyPath:

var a = [CGPoint(x: 1, y: 1), CGPoint(x: 2, y: 1), CGPoint(x: 1, y: 2)]
a.uniqueInPlace(for: \.y)
/* a is [{x 1 y 1}, {x 1 y 2}] */

1
现在,这是一个很好的实现!我仅将键路径转换为闭包,因此可以使用闭包arg支持任意代码(在闭包中)和仅属性查找(通过键路径)。我要做的唯一更改就是将keyPath默认设置为\.self,因为这可能是大多数用例。
亚历山大-恢复莫妮卡

1
@Alexander我尝试默认使用Self,但是我需要Element始终设置为self Hashable。:为默认值的替代方案是添加一个简单的过载而没有参数extension Sequence where Element: Hashable { func unique() { ... } }
心教堂

是的,很有道理!
亚历山大-恢复莫妮卡

1
辉煌……简单,最重要的是“灵活”。谢谢。
BonanzaDriver

12

这里开始,使用不可变类型而不是变量的替代(如果不是最优的)解决方案:

func deleteDuplicates<S: ExtensibleCollectionType where S.Generator.Element: Equatable>(seq:S)-> S {
    let s = reduce(seq, S()){
        ac, x in contains(ac,x) ? ac : ac + [x]
    }
    return s
}

包括了让-皮利佩的命令式方法和功能性方法的对比。

另外,该函数可用于字符串以及数组!

编辑:这个答案是在2014年为Swift 1.0编写的(之前Set在Swift中可用)。它不需要Hashable一致性并且可以在二次时间内运行。


8
当心,没有一种方法,但是有两种方法可以在二次时间内运行-两种方法都contains可以在O(n)中运行。尽管这样做的好处是只需要相等的,而不是可哈希的。
空速速度2014年

这是一种非常复杂的写作方式filter。它是O(n ^ 2)(如果您不想要求Hashable一致性,则是必需的),但是您至少应该明确指出这一点
亚历山大-恢复莫妮卡

10

迅捷2

uniq函数回答:

func uniq<S: SequenceType, E: Hashable where E==S.Generator.Element>(source: S) -> [E] {
    var seen: [E:Bool] = [:]
    return source.filter({ (v) -> Bool in
        return seen.updateValue(true, forKey: v) == nil
    })
}

用:

var test = [1,2,3,4,5,6,7,8,9,9,9,9,9,9]
print(uniq(test)) //1,2,3,4,5,6,7,8,9

Bool值显然是多余的,因为您的代码从不读取它。用a Set代替a Dictionary,您会得到我的认可。
Nikolai Ruhe

10

在Swift 5中

 var array: [String] =  ["Aman", "Sumit", "Aman", "Sumit", "Mohan", "Mohan", "Amit"]

 let uniq = Array(Set(array))
 print(uniq)

输出将是

 ["Sumit", "Mohan", "Amit", "Aman"]

2
这是此处已有许多答案的重复,并且不保留排序。
亚历山大-

9

另一种Swift 3.0解决方案,用于从数组中删除重复项。该解决方案对以下人员已经提出的许多其他解决方案进行了改进:

  • 保留输入数组中元素的顺序
  • 线性复杂度O(n):单通滤波器O(n)+集合插入O(1)

给定整数数组:

let numberArray = [10, 1, 2, 3, 2, 1, 15, 4, 5, 6, 7, 3, 2, 12, 2, 5, 5, 6, 10, 7, 8, 3, 3, 45, 5, 15, 6, 7, 8, 7]

功能代码:

func orderedSet<T: Hashable>(array: Array<T>) -> Array<T> {
    var unique = Set<T>()
    return array.filter { element in
        return unique.insert(element).inserted
    }
}

orderedSet(array: numberArray)  // [10, 1, 2, 3, 15, 4, 5, 6, 7, 12, 8, 45]

数组扩展代码:

extension Array where Element:Hashable {
    var orderedSet: Array {
        var unique = Set<Element>()
        return filter { element in
            return unique.insert(element).inserted
        }
    }
}

numberArray.orderedSet // [10, 1, 2, 3, 15, 4, 5, 6, 7, 12, 8, 45]

此代码利用inserton上Set执行的操作返回的结果O(1),并返回一个元组,指示是否已插入该项或该项是否已存在于集合中。

如果该项目在集合中,filter则将其从最终结果中排除。


1
别挑剔,但是您将执行插入和成员资格测试的次数与元素一样多,因此您也应该将它们的成本算作O(n)。但是,这并不意味着3xO(n),因为这些O与过滤器的成本不相等,因此O(n)的添加是苹果与橙子的比较。如果我们将设置操作视为过滤器成本的O(1)一部分,则复杂度仅为O(n),尽管“ O”更大。将其推到极限,当元素已经在集合中时,您也可以避免插入。
阿兰T.17年

没错,使用defer代码将执行两次设置测试操作,一次使用contains,一次使用insert。进一步阅读Swift文档,我发现insert返回一个元组,指示是否插入了元素,因此我简化了删除contains检查的代码。
Eneko Alonso

2
真好 您可以在extension Sequence where Iterator.Element: Hashable { ... }
Cœur)

@AlainT。不。两者insertcontains都有O(1)复杂性。O(1) + O(1) = O(1)。然后完成这两个操作的n时间(每次调用传递给的闭包filter一次,每个元素调用一次),即,如果一个操作花费了恒定的时间而与输入大小无关,那么执行两次仍会使它花费恒定的时间这与输入大小无关。总的复杂度是O(n)
亚历山大-恢复莫妮卡

9

Swift 4.x:

extension Sequence where Iterator.Element: Hashable {
  func unique() -> [Iterator.Element] {
    return Array(Set<Iterator.Element>(self))
  }

  func uniqueOrdered() -> [Iterator.Element] {
    return reduce([Iterator.Element]()) { $0.contains($1) ? $0 : $0 + [$1] }
  }
}

用法:

["Ljubljana", "London", "Los Angeles", "Ljubljana"].unique()

要么

["Ljubljana", "London", "Los Angeles", "Ljubljana"].uniqueOrdered()

这是O(n^2)。不要这样
亚历山大-恢复莫妮卡

8

迅捷5

extension Sequence where Element: Hashable {
    func unique() -> [Element] {
        NSOrderedSet(array: self as! [Any]).array as! [Element]
    }
}

我做了一些改动,所以我可以选择一个按键进行比较。 extension Sequence { // Returns distinct elements based on a key value. func distinct<key: Hashable>(by: ((_ el: Iterator.Element) -> key)) -> [Iterator.Element] { var existing = Set<key>() return self.filter { existing.insert(by($0)).inserted } } }
Marcelo de Aguiar,

Bool当您使用的唯一值是时,无需使用a true。您正在寻求一种“单位类型”(一种只有一个可能值的类型)。Swift的单位类型为Void,其唯一值为()(aka空元组)。这样就可以使用了[T: Void]。尽管您不应该这样做,因为您基本上是刚刚发明的Set。使用Set代替。请参阅stackoverflow.com/a/55684308/3141234,请删除此答案。
亚历山大-

8

像函数程序员一样思考:)

要根据元素是否已经出现来过滤列表,需要索引。您可以enumerated用来获取索引并map返回到值列表。

let unique = myArray
    .enumerated()
    .filter{ myArray.firstIndex(of: $0.1) == $0.0 }
    .map{ $0.1 }

这保证了订单。如果您不介意顺序,则现有的答案Array(Set(myArray))会更简单,也可能更有效。


更新:关于效率和正确性的一些说明

一些人对效率发表了评论。我肯定是在学校中,首先要编写正确且简单的代码,然后再找出瓶颈,尽管我理解这是否比清晰有争议Array(Set(array))

这种方法比慢很多Array(Set(array))。正如评论中指出的那样,它确实保留了顺序并可以处理不可哈希的元素。

但是,@ Alain T的方法还保留了顺序,并且速度也更快。因此,除非您的元素类型不可散列,或者您只需要一个快速衬线,否则我建议使用他们的解决方案。

以下是在发布模式下在Xcode 11.3.1(Swift 5.1)上的MacBook Pro(2014)上进行的一些测试。

探查器功能和两种比较方法:

func printTimeElapsed(title:String, operation:()->()) {
    var totalTime = 0.0
    for _ in (0..<1000) {
        let startTime = CFAbsoluteTimeGetCurrent()
        operation()
        let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
        totalTime += timeElapsed
    }
    let meanTime = totalTime / 1000
    print("Mean time for \(title): \(meanTime) s")
}

func method1<T: Hashable>(_ array: Array<T>) -> Array<T> {
    return Array(Set(array))
}

func method2<T: Equatable>(_ array: Array<T>) -> Array<T>{
    return array
    .enumerated()
    .filter{ array.firstIndex(of: $0.1) == $0.0 }
    .map{ $0.1 }
}

// Alain T.'s answer (adapted)
func method3<T: Hashable>(_ array: Array<T>) -> Array<T> {
    var uniqueKeys = Set<T>()
    return array.filter{uniqueKeys.insert($0).inserted}
}

以及少量测试输入:

func randomString(_ length: Int) -> String {
  let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  return String((0..<length).map{ _ in letters.randomElement()! })
}

let shortIntList = (0..<100).map{_ in Int.random(in: 0..<100) }
let longIntList = (0..<10000).map{_ in Int.random(in: 0..<10000) }
let longIntListManyRepetitions = (0..<10000).map{_ in Int.random(in: 0..<100) }
let longStringList = (0..<10000).map{_ in randomString(1000)}
let longMegaStringList = (0..<10000).map{_ in randomString(10000)}

给出作为输出:

Mean time for method1 on shortIntList: 2.7358531951904296e-06 s
Mean time for method2 on shortIntList: 4.910230636596679e-06 s
Mean time for method3 on shortIntList: 6.417632102966309e-06 s
Mean time for method1 on longIntList: 0.0002518167495727539 s
Mean time for method2 on longIntList: 0.021718120217323302 s
Mean time for method3 on longIntList: 0.0005312927961349487 s
Mean time for method1 on longIntListManyRepetitions: 0.00014377200603485108 s
Mean time for method2 on longIntListManyRepetitions: 0.0007293639183044434 s
Mean time for method3 on longIntListManyRepetitions: 0.0001843773126602173 s
Mean time for method1 on longStringList: 0.007168249964714051 s
Mean time for method2 on longStringList: 0.9114790915250778 s
Mean time for method3 on longStringList: 0.015888616919517515 s
Mean time for method1 on longMegaStringList: 0.0525397013425827 s
Mean time for method2 on longMegaStringList: 1.111266262292862 s
Mean time for method3 on longMegaStringList: 0.11214958941936493 s

1
与之不同的是Array(Set(myArray)),这适用于非Hashable
波特·

1
...与Array(Set(myArray))数组的顺序不同。
桑德·塞尔曼斯

至少在目前,Swift 5已经是当前版本,这似乎是对我的最佳答案。
oradyvan

这是一个非常优雅的解决方案。不幸的是,它也相当慢。
科林·斯塔克

1
@TimMB哦,我看错了你的帖子。我看到有人使用的改编lastIndex(of:)。在这种情况下,我对清晰度与优化点完全不同意。我认为这种实现方式不是特别清晰,特别是与简单的基于集合的解决方案相比。在任何情况下,都应将此类代码提取到扩展功能中。即使在很小的输入大小下(例如成千上万),该算法也变得基本无法使用。不难找到这样的数据集,人们可以有成千上万的歌曲,文件,联系人等
恢复莫妮卡-亚历山大

6

对于元素既不可散列也不可比较的数组(例如,复杂的对象,字典或结构),此扩展提供了一种通用方法来删除重复项:

extension Array
{
   func filterDuplicate<T:Hashable>(_ keyValue:(Element)->T) -> [Element]
   {
      var uniqueKeys = Set<T>()
      return filter{uniqueKeys.insert(keyValue($0)).inserted}
   }

   func filterDuplicate<T>(_ keyValue:(Element)->T) -> [Element]
   { 
      return filterDuplicate{"\(keyValue($0))"}
   }
}

// example usage: (for a unique combination of attributes):

peopleArray = peopleArray.filterDuplicate{ ($0.name, $0.age, $0.sex) }

or...

peopleArray = peopleArray.filterDuplicate{ "\(($0.name, $0.age, $0.sex))" }

您不必费心将值设为Hashable,它使您可以使用不同的字段组合来实现唯一性。

注意:有关更健壮的方法,请参阅以下评论中Coeur提出的解决方案。

stackoverflow.com/a/55684308/1033581

[编辑] Swift 4替代

使用Swift 4.2,您可以使用Hasher类轻松构建哈希。可以更改以上扩展名以利用此功能:

extension Array
{
    func filterDuplicate(_ keyValue:((AnyHashable...)->AnyHashable,Element)->AnyHashable) -> [Element]
    {
        func makeHash(_ params:AnyHashable ...) -> AnyHashable
        { 
           var hash = Hasher()
           params.forEach{ hash.combine($0) }
           return hash.finalize()
        }  
        var uniqueKeys = Set<AnyHashable>()
        return filter{uniqueKeys.insert(keyValue(makeHash,$0)).inserted}     
    }
}

调用语法略有不同,因为闭包会收到一个附加参数,该参数包含一个函数,该函数可对可变数量的值进行哈希处理(必须分别可哈希)

peopleArray = peopleArray.filterDuplicate{ $0($1.name, $1.age, $1.sex) } 

它还将使用单个唯一性值(使用$ 1并忽略$ 0)。

peopleArray = peopleArray.filterDuplicate{ $1.name } 

根据的行为"\()",这可能会得出随机结果,因为它可能不会给您带来如Hashable应有的唯一值。例如,如果您的元素都Printable通过返回全部来符合description,则您的过滤失败。
心教堂

同意 选择将产生所需唯一性模式的字段(或公式)时必须考虑到这一点。对于许多用例,这提供了一个简单的即席解决方案,不需要更改元素的类或结构。
Alain T.

2
@AlainT。真的不要这样做。String的目的不是成为某些贫民窟临时密钥生成机制。只是约束T自己Hashable
亚历山大-恢复莫妮卡

:@Alexander我在一个新的答案应用这个想法stackoverflow.com/a/55684308/1033581
心教堂

我想要的完美答案。非常感谢。
Hardik Thakkar

4

您可以直接使用set集合来删除重复项,然后将其转换回数组

var myArray = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
var mySet = Set<Int>(myArray)

myArray = Array(mySet) // [2, 4, 60, 6, 15, 24, 1]

然后,您可以根据需要订购阵列

myArray.sort{$0 < $1} // [1, 2, 4, 6, 15, 24, 60]

“然后您可以根据需要订购阵列”如果我想要与原始阵列相同的订购怎么办?那并没那么简单。
亚历山大-恢复莫妮卡

3

丹尼尔·克罗姆Daniel Krom)的Swift 2答案的语法版本更为简洁,使用了结尾的结尾和速记参数名称,该名称似乎基于空速Velocity的原始答案

func uniq<S: SequenceType, E: Hashable where E == S.Generator.Element>(source: S) -> [E] {
  var seen = [E: Bool]()
  return source.filter { seen.updateValue(true, forKey: $0) == nil }
}

实现可与之一起使用的自定义类型的示例uniq(_:)(该类型必须符合Hashable,因此必须符合Equatable,因为Hashableextends Equatable):

func ==(lhs: SomeCustomType, rhs: SomeCustomType) -> Bool {
  return lhs.id == rhs.id // && lhs.someOtherEquatableProperty == rhs.someOtherEquatableProperty
}

struct SomeCustomType {

  let id: Int

  // ...

}

extension SomeCustomType: Hashable {

  var hashValue: Int {
    return id
  }

}

在上面的代码中...

id在重载中使用==,可以是任何Equatable类型(或返回Equatable类型的方法,例如someMethodThatReturnsAnEquatableType())。注释掉的代码演示了扩展对相等性的检查,其中someOtherEquatablePropertyEquatable类型的另一个属性(但也可以是返回Equatable类型的方法)。

idhashValue计算属性(必须符合Hashable)中使用的,可以是任何Hashable(因此Equatable)属性(或返回Hashable类型的方法)。

使用示例uniq(_:)

var someCustomTypes = [SomeCustomType(id: 1), SomeCustomType(id: 2), SomeCustomType(id: 3), SomeCustomType(id: 1)]

print(someCustomTypes.count) // 4

someCustomTypes = uniq(someCustomTypes)

print(someCustomTypes.count) // 3

Bool当您使用的唯一值是时,无需使用a true。您正在寻求“单位类型”(一种只有一个可能值的类型)。Swift的单位类型为Void,其唯一值为()(aka空元组)。这样就可以使用了[T: Void]。尽管您不应该这样做,因为您基本上是刚刚发明的Set。使用Set代替。请参阅stackoverflow.com/a/55684308/3141234
亚历山大-

3

如果您需要对值进行排序,则可以使用此功能(Swift 4)

let sortedValues = Array(Set(array)).sorted()


2
在这种情况下,您失去元素顺序。
Shmidt

根本不是,这就是.sorted()最后的目的。问候。
毛里西奥·奇里诺

@MauricioChirino如果您的原始数组是[2, 1, 1]?它将出来[1, 2],没有命令:p
亚历山大–恢复莫妮卡

2
@MauricioChirino不,我不是。如果目标是从序列中删除重复的值,同时保留元素唯一显示的顺序,则不会这样做。非常清楚的反例是[2, 1, 1]。独特元素的首次出现是[2, 1]。那是正确的答案。但是,使用您(不正确的)算法,您会得到[1, 2],但排序,但顺序正确。
亚历山大-恢复莫妮卡

2
如果中的元素array不是则失败Hashable; 只能将Hashable数据类型添加到集合,而可以将任何数据类型添加到数组。
梅基'19

3

这是一个解决方案

  • 不使用旧版NS类型
  • 相当快 O(n)
  • 简洁
  • 保留元素顺序
extension Array where Element: Hashable {

    var uniqueValues: [Element] {
        var allowed = Set(self)
        return compactMap { allowed.remove($0) }
    }
}

2

在这里,我已经完成了一些对象的O(n)解决方案。不是几行解决方案,但是...

struct DistinctWrapper <T>: Hashable {
    var underlyingObject: T
    var distinctAttribute: String
    var hashValue: Int {
        return distinctAttribute.hashValue
    }
}
func distinct<S : SequenceType, T where S.Generator.Element == T>(source: S,
                                                                distinctAttribute: (T) -> String,
                                                                resolution: (T, T) -> T) -> [T] {
    let wrappers: [DistinctWrapper<T>] = source.map({
        return DistinctWrapper(underlyingObject: $0, distinctAttribute: distinctAttribute($0))
    })
    var added = Set<DistinctWrapper<T>>()
    for wrapper in wrappers {
        if let indexOfExisting = added.indexOf(wrapper) {
            let old = added[indexOfExisting]
            let winner = resolution(old.underlyingObject, wrapper.underlyingObject)
            added.insert(DistinctWrapper(underlyingObject: winner, distinctAttribute: distinctAttribute(winner)))
        } else {
            added.insert(wrapper)
        }
    }
    return Array(added).map( { return $0.underlyingObject } )
}
func == <T>(lhs: DistinctWrapper<T>, rhs: DistinctWrapper<T>) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

// tests
// case : perhaps we want to get distinct addressbook list which may contain duplicated contacts like Irma and Irma Burgess with same phone numbers
// solution : definitely we want to exclude Irma and keep Irma Burgess
class Person {
    var name: String
    var phoneNumber: String
    init(_ name: String, _ phoneNumber: String) {
        self.name = name
        self.phoneNumber = phoneNumber
    }
}

let persons: [Person] = [Person("Irma Burgess", "11-22-33"), Person("Lester Davidson", "44-66-22"), Person("Irma", "11-22-33")]
let distinctPersons = distinct(persons,
    distinctAttribute: { (person: Person) -> String in
        return person.phoneNumber
    },
    resolution:
    { (p1, p2) -> Person in
        return p1.name.characters.count > p2.name.characters.count ? p1 : p2
    }
)
// distinctPersons contains ("Irma Burgess", "11-22-33") and ("Lester Davidson", "44-66-22")

1
而不是使用Set与自定义DistinctWrapper,你应该使用一个Dictionary从distinctAttributes对象。当您遵循该逻辑时,最终将最终实现[ Dictionary.init(_:uniquingKeysWith:)] pastebin.com/w90pVe0p(https://developer.apple.com/documentation/…,现在已内置到标准库中。请查看这是多么简单。pastebin.com/w90pVe0p
亚历山大-恢复莫妮卡

2

我使用了@ Jean-Philippe Pellet的答案,并做了一个Array扩展,它在保持元素顺序的同时对数组进行了类似集合的操作。

/// Extensions for performing set-like operations on lists, maintaining order
extension Array where Element: Hashable {
  func unique() -> [Element] {
    var seen: [Element:Bool] = [:]
    return self.filter({ seen.updateValue(true, forKey: $0) == nil })
  }

  func subtract(takeAway: [Element]) -> [Element] {
    let set = Set(takeAway)
    return self.filter({ !set.contains($0) })
  }

  func intersect(with: [Element]) -> [Element] {
    let set = Set(with)
    return self.filter({ set.contains($0) })
  }
}

Bool当您使用的唯一值是时,无需使用a true。您正在寻求“单位类型”(一种只有一个可能值的类型)。Swift的单位类型为Void,其唯一值为()(aka空元组)。这样就可以使用了[T: Void]。尽管您不应该这样做,因为您基本上是刚刚发明的Set。使用Set代替。请参阅stackoverflow.com/a/55684308/3141234
亚历山大-

2

这只是一个非常简单和方便的实现。具有相等元素的Array扩展中的计算属性。

extension Array where Element: Equatable {
    /// Array containing only _unique_ elements.
    var unique: [Element] {
        var result: [Element] = []
        for element in self {
            if !result.contains(element) {
                result.append(element)
            }
        }

        return result
    }
}

1
这也是O(n^2)
亚历山大–恢复莫妮卡

2
func removeDublicate (ab: [Int]) -> [Int] {
var answer1:[Int] = []
for i in ab {
    if !answer1.contains(i) {
        answer1.append(i)
    }}
return answer1
}

用法:

let f = removeDublicate(ab: [1,2,2])
print(f)

我觉得这是最简单的
杰克·罗斯

它会保持顺序并为您提供所需的数组
Jack Rus

这也是O(n²)
亚历山大-恢复莫妮卡

2
  1. 首先将数组的所有元素添加到NSOrderedSet中。
  2. 这将删除阵列中的所有重复项。
  3. 再次将此有序集转换为数组。

完成...

let array = [1,1,1,1,2,2,2,2,4,6,8]

let orderedSet : NSOrderedSet = NSOrderedSet(array: array)

let arrayWithoutDuplicates : NSArray = orderedSet.array as NSArray

arrayWithoutDuplicates的输出-[1,2,4,6,8]


2

一个基于@ Jean-Philippe Pellet的数组扩展的简短版本:

extension Array where Element: Hashable {

    var uniques: Array {
        var added = Set<Element>()
        return filter { element in
            defer { added.insert(element) }
            return !added.contains(element)
        }
    }
}

每个元素执行两次哈希操作,这是不必要的。insert返回一个元组,告诉您该元素是否已经存在或是第一次添加。stackoverflow.com/a/55684308/3141234请删除此答案。
亚历山大-

1

您始终可以使用字典,因为字典只能包含唯一值。例如:

var arrayOfDates: NSArray = ["15/04/01","15/04/01","15/04/02","15/04/02","15/04/03","15/04/03","15/04/03"]

var datesOnlyDict = NSMutableDictionary()
var x = Int()

for (x=0;x<(arrayOfDates.count);x++) {
    let date = arrayOfDates[x] as String
    datesOnlyDict.setValue("foo", forKey: date)
}

let uniqueDatesArray: NSArray = datesOnlyDict.allKeys // uniqueDatesArray = ["15/04/01", "15/04/03", "15/04/02"]

println(uniqueDatesArray.count)  // = 3

如您所见,结果数组将不会总是处于“顺序”状态。如果您希望对数组进行排序/排序,请添加以下内容:

var sortedArray = sorted(datesOnlyArray) {
(obj1, obj2) in

    let p1 = obj1 as String
    let p2 = obj2 as String
    return p1 < p2
}

println(sortedArray) // = ["15/04/01", "15/04/02", "15/04/03"]


1

最简单的方法是使用NSOrderedSet,它存储唯一元素并保留元素顺序。喜欢:

func removeDuplicates(from items: [Int]) -> [Int] {
    let uniqueItems = NSOrderedSet(array: items)
    return (uniqueItems.array as? [Int]) ?? []
}

let arr = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
removeDuplicates(from: arr)

我不知道这种性能如何与此处的更好答案相提并论。你比较了吗?
亚历山大-恢复莫妮卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.