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

Posted

技术标签:

【中文标题】在 Swift 中从数组中删除重复元素【英文标题】:Removing duplicate elements from an array in Swift 【发布时间】:2014-11-02 12:46:15 【问题描述】:

我可能有一个如下所示的数组:

[1, 4, <b>2</b>, <b>2</b>, <b>6</b>, 24, <b>15</b>, 2, 60, <b>15</b>, <b>6</b>]

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

[1, 4, <b>2</b>, <b>6</b>, 24, <b>15</b>, 60]

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

【问题讨论】:

最简单的方法是将数组转换成NSSet,NSSet是一个无序的对象集合,如果需要NSOrderedSet保持有序。 您可以像在此类中找到的用于数组的函数一样使用交集函数:github.com/pNre/ExSwift/blob/master/ExSwift/Array.swift 不是 Swift 的一部分,但我使用 Dollar。 $.uniq(array)github.com/ankurp/Dollar#uniq---uniq 下面的mxcl's answer 可能提供了最优雅、最聪明和最快的答案。这也有助于维持秩序 你为什么不直接使用 Swift 的 Set 呢?您将能够提供无序和独特元素的列表。 【参考方案1】:

从数组中删除重复项的简单方法

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

【讨论】:

【参考方案2】:

现在不需要写扩展了。

Apple 终于在其 Algorithms 包中引入了uniqued() 方法,可用于任何符合Sequence 协议的类型。

import Algorithms

let numbers = [1, 2, 3, 3, 2, 3, 3, 2, 2, 2, 1]
print(numbers.uniqued()) // prints [1, 2, 3]

更多信息https://github.com/apple/swift-algorithms/blob/main/Guides/Unique.md

【讨论】:

【参考方案3】:

在插入检查哈希时包含相等检查,最安全的检查方式如下:

extension Array where Element: Hashable 

    /// Big O(N) version. Updated since @Adrian's comment. 
    var uniques: Array 
        // Go front to back, add element to buffer if it isn't a repeat.
         var buffer: [Element] = []
         var dictionary: [Element: Int] = [:]
         for element in self where dictionary[element] == nil 
             buffer.append(element)
             dictionary[element] = 1
         
         return buffer
    

【讨论】:

这可能会在较小的数组上完成这项工作,但我在大型数据集上尝试过它并且速度非常慢。 感谢您的意见!啊,是的,包含的方法使它成为一个 O(N^2) 操作......很好。 如果hashValue 中存在冲突,这将不起作用。应该通过检查是否相等来处理碰撞。这就是Hashable 协议继承自Equatable 的原因。 刚刚更新到另一个尝试 @Adrian,你能检查一下这个吗?【参考方案4】:

这是SequenceType 上的一个类别,它保留了数组的原始顺序,但使用Set 进行contains 查找以避免Array 的contains(_:) 方法的O(n) 成本。

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 ***.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 ***.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 ***.com/a/46354989/3141234
    func uniqued() -> [Element] 
        return self.uniqued(comparator: ==)
    

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

extension Sequence 

    /// Returns the sequence with duplicate elements removed, performing the comparison using 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 ***.com/a/46354989/3141234
    ///
    func uniqued<T: Equatable>(_ keyPath: KeyPath<Element, T>) -> [Element] 
        self.uniqued  $0[keyPath: keyPath] == $1[keyPath: keyPath] 
    

您可以将这两个都粘贴到您的应用中,Swift 会根据您序列的 Iterator.Element 类型选择正确的。


对于 El Capitan,您可以扩展此方法以包含多个键路径,如下所示:

    /// Returns the sequence with duplicate elements removed, performing the comparison using the property at
    /// the supplied keypaths.
    ///
    /// i.e.
    ///
    /// ```
    /// [
    ///   MyStruct(value1: "Hello", value2: "Paula"),
    ///   MyStruct(value1: "Hello", value2: "Paula"),
    ///   MyStruct(value1: "Hello", value2: "Bean"),
    ///   MyStruct(value1: "World", value2: "Sigh")
    ///  ].uniqued(\.value1, \.value2)
    /// ```
    /// would result in
    ///
    /// ```
    /// [
    ///   MyStruct(value1: "Hello", value2: "Paula"),
    ///   MyStruct(value1: "Hello", value2: "Bean"),
    ///   MyStruct(value1: "World", value2: "Sigh")
    /// ]
    /// ```
    ///
    /// - note: Taken from ***.com/a/46354989/3141234
    ///
    func uniqued<T: Equatable, U: Equatable>(_ keyPath1: KeyPath<Element, T>, _ keyPath2: KeyPath<Element, U>) -> [Element] 
        self.uniqued 
            $0[keyPath: keyPath1] == $1[keyPath: keyPath1] && $0[keyPath: keyPath2] == $1[keyPath: keyPath2]
        
    

但是(恕我直言)您最好将自己的块传递给self.uniqued

【讨论】:

嘿,终于有人提出了O(n) 解决方案。顺便说一句,您可以将“检查”和“插入”集合操作合二为一。见***.com/a/46354989/3141234 哦,这很聪明:) @deanWombourne 如何通过多个键路径区分元素? @EICaptainv2.0 您可以扩展 uniqued 方法以获取两个通用参数并检查它们是否相等 - 查看我刚刚进行的编辑。仅当 两个 键路径指定的值相同时,这些项目才会重复。 酷。谢谢@deanWombourne【参考方案5】:

正如 WWDC 2021 所述,Swift 拥有社区开发的算法、集合和数值包。 Algorithms 包具有uniqued() 算法。

这些还不是 Swift 标准库的一部分。您目前可以从 Apple 的 Github 页面下载它们和/或通过 Swift Package Manager 安装它们。

WWDC 视频:

https://developer.apple.com/videos/play/wwdc2021/10256/

Github 页面:

https://github.com/apple/swift-algorithms

uniqued()uniqued(on:) 文档:

https://github.com/apple/swift-algorithms/blob/main/Guides/Unique.md

【讨论】:

这应该是最佳答案【参考方案6】:

斯威夫特 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)。见documentation。

使用返回值意味着我们可以避免多次循环,所以这是 O(n)。

【讨论】:

经过简单的profiling,这个方法确实很快。它比使用 reduce(_: _:) 快数百倍,甚至是 reduce(into: _:) @Kelvin 因为所有其他算法都是O(n^2),没有人注意到。 @Kelvin 这个答案与Eneko Alonso answer + 我的评论相同(2017 年 6 月 16 日)。【参考方案7】:

您可以自己滚动,例如像这样:

func unique<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


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

作为Array的扩展:

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

或者更优雅(Swift 4/5):

extension Sequence where Element: Hashable 
    func uniqued() -> [Element] 
        var set = Set<Element>()
        return filter  set.insert($0).inserted 
    

将使用哪个:

[1,2,4,2,1].uniqued()  // => [1,2,4]

【讨论】:

你也可以将该函数的主体实现为var addedDict = [T:Bool](); return filter(source) addedDict(true, forKey: $0) == nil @AirspeedVelocity:你的意思是updateValue(true, forKey: $0)...而不是addedDict(true, forKey: $0)... 哎呀是的对不起我的方法不小心!如你所说,应该是return filter(source) addedDict.updateValue(true, forKey: $0) == nil 请注意:避免讨论此类简单函数的性能,直到您可以证明依赖于它们的性能,此时您唯一应该做的就是基准测试。由于做出假设,我经常看到无法维护的代码甚至性能更低的代码。 :) 另外,这可能更容易掌握:let uniques = Array(Set(vals)) @Blixt 同意。再一次,这里的优势在于尊重原始数组的元素顺序。【参考方案8】:
var numbers = [1,2,3,4,5,10,10, 12, 12, 6,6,6,7,8,8, 8, 8, 8 , 7 , 1 , 1, 2 , 9]

var newArr : [Int] = []
for n in numbers 
    if !newArr.contains(n) 
        newArr.append(n)
    

输出 - [1, 2, 3, 4, 5, 10, 12, 6, 7, 8, 9]

上述解决方案保持秩序,但非常慢,因为 .contains 一次又一次地迭代。 因此使用有序集。

这将打印有序数组。

Array(NSOrderedSet.init(array: numbers))

输出 - [1, 2, 3, 4, 5, 10, 12, 6, 7, 8, 9]

这将打印一个无序数组。

let uniqueUnordered = Array(Set(numbers))

输出 - [4, 2, 1, 9, 10, 3, 5, 6, 8, 12, 7]

【讨论】:

【参考方案9】:

如果你也想保持订单,那么使用这个

let fruits = ["apple", "pear", "pear", "banana", "apple"] 
let orderedNoDuplicates = Array(NSOrderedSet(array: fruits).map( $0 as! String ))

【讨论】:

【参考方案10】:

您可以很容易地转换为Set 并再次转换回Array

let unique = Array(Set(originals))

保证保持数组的原始顺序。

【讨论】:

有没有办法在保留数组原始顺序的同时使用集合? @Crashalot 看看我的回答。 如果您需要通过特定属性保持对象的唯一性,而不是在该类上实现 Hashable 和 Equatable 协议,而不是仅使用 Array->Set->Array 转换 如果originals 中的元素不是Hashable 则失败; Set 中只能添加 Hashable 数据类型,而数组中可以添加任何数据类型。 我不明白为什么这个答案有这么多赞成票。似乎维护数组的顺序几乎可以肯定是一个要求。否则,您不妨一开始就使用 Set 而不是 Array。【参考方案11】:

如果您将这两个扩展都放在代码中,将尽可能使用更快的Hashable 版本,而Equatable 版本将用作备用。

public extension Sequence where Element: Hashable 
  /// The elements of the sequence, with duplicates removed.
  /// - Note: Has equivalent elements to `Set(self)`.
  @available(
  swift, deprecated: 5.4,
  message: "Doesn't compile without the constant in Swift 5.3."
  )
  var firstUniqueElements: [Element] 
    let getSelf: (Element) -> Element = \.self
    return firstUniqueElements(getSelf)
  


public extension Sequence where Element: Equatable 
  /// The elements of the sequence, with duplicates removed.
  /// - Note: Has equivalent elements to `Set(self)`.
  @available(
  swift, deprecated: 5.4,
  message: "Doesn't compile without the constant in Swift 5.3."
  )
  var firstUniqueElements: [Element] 
    let getSelf: (Element) -> Element = \.self
    return firstUniqueElements(getSelf)
  


public extension Sequence 
  /// The elements of the sequences, with "duplicates" removed
  /// based on a closure.
  func firstUniqueElements<Hashable: Swift.Hashable>(
    _ getHashable: (Element) -> Hashable
  ) -> [Element] 
    var set: Set<Hashable> = []
    return filter  set.insert(getHashable($0)).inserted 
  

  /// The elements of the sequence, with "duplicates" removed,
  /// based on a closure.
  func firstUniqueElements<Equatable: Swift.Equatable>(
    _ getEquatable: (Element) -> Equatable
  ) -> [Element] 
    reduce(into: [])  uniqueElements, element in
      if zip(
        uniqueElements.lazy.map(getEquatable),
        AnyIterator  [equatable = getEquatable(element)] in equatable 
      ).allSatisfy(!=) 
        uniqueElements.append(element)
      
    
  

如果顺序不重要,那么您可以随时使用this Set initializer。

【讨论】:

@DavidSeek 像这样,uniqueArray = nonUniqueArray.uniqueElements 是的,别担心。之后就让它工作了。已经快 2 年了:P 这将有O(n²)的时间性能,这对于大型数组来说真的很糟糕。 hahsable 版本会有更好的性能,但不会保留原始数组中元素的顺序。 Leo 的回答将同时提供 O(n) 性能并保留对象排序。 @Jessy 已经有多个O(1) 答案,但与大多数天真的O(n^2) 解决方案相比,它们的投票数要少得多。这个特别简单:***.com/a/46354989/3141234【参考方案12】:

使用SetNSOrderedSet 删除重复项,然后转换回Array

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

【讨论】:

让 uniqueOrderedNames = Array(NSOrderedSet(array: userNames)) 为! [String] 如果你有 String 数组,而不是 Any 如果array 中的元素不是Hashable 则失败; Set 中只能添加 Hashable 数据类型,而数组中可以添加任何数据类型。 在 Swift 5.1b5 中测试,考虑到元素是 Hashable 并且希望保留排序,NSOrderedSet(array: array).array 比使用集合的纯 swift func uniqued() 略快带过滤器。我测试了 5100 个字符串,产生了 13 个唯一值。 Array(NSOrderedSet(array: array)) 在 Swift 5 中不起作用。请改用 NSOrderedSet(array: array).array as! [String] 第二个只适用于“原始”类型【参考方案13】:

我认为这是了解逻辑本身的更好方法

var arrayOfInts = [2, 2, 4, 4]
var mainArray = [Int]()

for value in arrayOfInts 

if mainArray.contains(value) != true  
    
    mainArray.append(value)
    print("mainArray:\(mainArray)")

【讨论】:

这是二次行为。循环调用的每次迭代都包含,它本身对所有元素使用循环。真的很慢。 mainArray.contains(value) == false 可以简化为 mainArray.contains(value) != true【参考方案14】:

这是一个解决方案

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

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

【讨论】:

这很好,但只适用于 Hashable 元素【参考方案15】:

斯威夫特 3/斯威夫特 4/斯威夫特 5

只需一行代码即可省略重复数组而不影响顺序:

let filteredArr = Array(NSOrderedSet(array: yourArray))

【讨论】:

这里我们将一个数组类型转换为 Orderedset。 “集合”的定义 - 集合只允许不同的值(它不允许重复)。因此将省略重复项,因为我们使用 NSOrderedSet 进行类型转换,因此不会干扰数组顺序。【参考方案16】:

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

要根据元素是否已经出现过滤列表,您需要索引。您可以使用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))慢很多。正如 cmets 中所指出的,它确实保留了顺序并适用于不可哈希的元素。

不过,@Alain T 的方法也可以保持顺序,而且速度也快很多。因此,除非您的元素类型不可散列,或者您只需要一个快速的衬垫,否则我建议您使用他们的解决方案。

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

profiler函数和两种方法比较:

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.filteruniqueKeys.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

【讨论】:

不像Array(Set(myArray)),这适用于不是Hashable的东西 ... 与 Array(Set(myArray)) 不同的是,您的数组顺序保持不变。 这对我来说似乎是最好的答案,至少目前 Swift 5 已经是当前版本。 @TimMB 哦,我看错了你的帖子。我看到有人改编使用了lastIndex(of:)。在这种情况下,我完全不同意清晰度与优化点。我认为这个实现不是特别清楚,尤其是与简单的基于集合的解决方案相比。在任何情况下,都应该将此类代码提取到扩展函数中。即使输入量很小,例如几千到几万,该算法也基本上无法使用。找到这样的数据集并不难,人们可以拥有成千上万的歌曲、文件、联系人等。 查看我的基准测试结果:drive.google.com/a/ryerson.ca/file/d/… 完整代码:gist.github.com/amomchilov/299d012dccba375bf15880355684ebed【参考方案17】:

斯威夫特 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&lt;key: Hashable&gt;(by: ((_ el: Iterator.Element) -&gt; key)) -&gt; [Iterator.Element] var existing = Set&lt;key&gt;() return self.filter existing.insert(by($0)).inserted 当您使用的唯一值是true 时,无需使用Bool。您正在寻找一种“单位类型”(一种只有一个可能值的类型)。 Swift 的单元类型是Void,其唯一值是()(也就是空元组)。所以你可以使用[T: Void]。虽然你不应该那样做,因为你基本上只是发明了Set。请改用Set。见***.com/a/55684308/3141234请删除这个答案。 如果你的元素是Hasable,可以直接使用Array(Set(yourElements) 这会改变数组的顺序。【参考方案18】:

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

非变异解决方案

我们扩展了一个能够过滤任何 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,你可以有这个额外的扩展,但返回类型总是一个数组:

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] */

【讨论】:

现在这是一个很好的实现!我只是将关键路径转换为闭包,以便您可以使用闭包 arg 来支持任意代码(在闭包中)和单纯的属性查找(通过关键路径)。我要做的唯一更改是将keyPath 默认为\.self,因为这可能是大多数用例。 @Alexander 我尝试默认为 Self,但我需要将 Element 始终设为 Hashable。默认值的替代方法是添加一个不带参数的简单重载:extension Sequence where Element: Hashable func unique() ... 啊,是的,有道理! 很棒...简单,最重要的是“灵活”。谢谢。 @Alexander-ReinstateMonica:这看起来与您 2018 年 3 月的解决方案非常相似:gist.github.com/amomchilov/fbba1e58c91fbd4b5b767bcf8586112b???【参考方案19】:

在 Swift 5 中

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

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

输出将是

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

【讨论】:

这是对已经在这里的许多答案的重复,它不保留顺序。【参考方案20】:
    首先将数组的所有元素添加到 NSOrderedSet。 这将删除数组中的所有重复项。 再次将此有序集转换为数组。

完成....

例子

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]

【讨论】:

【参考方案21】:

我创建了一个时间复杂度为 o(n) 的高阶函数。此外,地图之类的功能可以返回您想要的任何类型。

extension Sequence 
    func distinct<T,U>(_ provider: (Element) -> (U, T)) -> [T] where U: Hashable 
        var uniqueKeys = Set<U>()
        var distintValues = [T]()
        for object in self 
            let transformed = provider(object)
            if !uniqueKeys.contains(transformed.0) 
                distintValues.append(transformed.1)
                uniqueKeys.insert(transformed.0)
            
        
        return distintValues
    

【讨论】:

这对每个元素执行两次散列操作,这是不必要的。 insert 返回一个元组,告诉您该元素是否已经存在,或者是第一次添加。 ***.com/a/55684308/3141234请删除此答案。【参考方案22】:

我的解决方案,它似乎可以在 O(n) 时间内,因为哈希映射访问是 O(1),过滤器是 O(n)。它还使用闭包来选择属性,通过该属性来区分元素的顺序。

extension Sequence 

    func distinct<T: Hashable>(by: (Element) -> T) -> [Element] 
        var seen: [T: Bool] = [:]
        return self.filter  seen.updateValue(true, forKey: by($0)) == nil 
    

【讨论】:

当您使用的唯一值是true 时,无需使用Bool。您正在寻找一种“单位类型”(一种只有一个可能值的类型)。 Swift 的单元类型是Void,其唯一值是()(也就是空元组)。所以你可以使用[T: Void]。虽然你不应该那样做,因为你基本上只是发明了Set。请改用Set。见***.com/a/55684308/3141234请删除这个答案。【参考方案23】:

编辑/更新 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,请点击 here

【讨论】:

我喜欢这个,它也适用于字典数组! O(N^2) 不好:( @Alexander Leo Dabus 已经替换了 reduce 实现,所以现在复杂性不同了。 结果很有趣。对于 100 万个独特的项目和 800 万个,过滤器版本更快。然而,基于过滤器的版本需要 8.38 倍的时间来处理 800 万个唯一项(比O(n) 时间还要长),而基于平面图的版本需要 7.47 倍的时间来处理 800 万个唯一项,而不是 100 万个,这表明基于平面图的版本版本扩展性更好。不知何故,基于平面图的版本比O(n) time 稍微好一点! 事实上,当我在数组中使用 64 倍以上的项目运行测试时,基于平面图的版本更快。【参考方案24】:

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

extension Array

   func filterDuplicate<T:Hashable>(_ keyValue:(Element)->T) -> [Element]
   
      var uniqueKeys = Set<T>()
      return filteruniqueKeys.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 在下面的 cmets 中提出的解决方案。

***.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 filteruniqueKeys.insert(keyValue(makeHash,$0)).inserted     
    

调用语法有点不同,因为闭包接收一个额外的参数,其中包含一个函数来散列可变数量的值(必须单独散列)

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

它也适用于单个唯一性值(使用 $1 并忽略 $0)。

peopleArray = peopleArray.filterDuplicate $1.name  

【讨论】:

这可能会根据"\()" 的行为给出随机结果,因为它可能不会为您提供符合Hashable 应该的唯一值。例如,如果您的元素都通过返回相同的description 符合Printable,那么您的过滤将失败。 同意。选择将产生所需唯一性模式的字段(或公式)必须考虑到这一点。对于许多用例,这提供了一个简单的临时解决方案,不需要更改元素的类或结构。 @AlainT。不要这样做,真的。 String 的目的不是成为某种贫民窟的临时密钥生成机制。只需将 T 限制为 Hashable @Alexander 我在一个新的答案中应用了这个想法:***.com/a/55684308/1033581 我想要的完美答案。非常感谢。【参考方案25】:

Xcode 10.1 - Swift 4.2 简单而强大的解决方案

func removeDuplicates(_ nums: inout [Int]) -> Int 
    nums = Set(nums).sorted()
    return nums.count

例子

var arr = [1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9]
removeDuplicates(&arr)

print(arr) // [1,2,3,4,5,6,7,8,9]

【讨论】:

这不会保留原始订单:它应用一个新订单,可能相同也可能不同。即使是相同的订单,它的性能也不如只保留现有订单而不添加额外sorted 的解决方案。 是的,sorted() 电话是完全错误的责任。来电者要求进行重复数据删除。如果他们也想要排序,他们已经可以自己完成了。我建议删除这个答案。【参考方案26】:

here 使用不可变类型而不是变量的替代(如果不是最佳)解决方案:

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

将 Jean-Pillippe 的命令式方法与函数式方法进行对比。

作为奖励,此函数适用于字符串和数组!

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

【讨论】:

当心,不是一种,而是两种在二次时间中运行的方式——contains 和数组追加都在 O(n) 中运行。尽管它确实具有只需要可等式而不是可散列的好处。 这是一个非常复杂的写filter的方式。它是 O(n^2)(如果您不想要求 Hashable 一致性,则需要),但您至少应该明确指出这一点【参考方案27】:

如果您需要对值进行排序,这可行 (Swift 4)

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

【讨论】:

在这种情况下你失去了元素顺序。 一点也不,这就是最后的.sorted() 的用途。问候。 @MauricioChirino 如果你的原始数组是[2, 1, 1]?它会出现[1, 2],这不是订购的:p @MauricioChirino 不,我不是。如果目标是从序列中删除重复值,同时保留元素唯一出现的顺序,这不会这样做。 非常明显的反例是 @ 987654325@。独特元素的首次出现,依次为[2, 1]。这才是正确答案。但是使用您的(不正确的)算法,您会得到[1, 2],它排序,但不是正确的原始顺序。 如果array 中的元素不是Hashable 则失败; Set 中只能添加 Hashable 数据类型,而数组中可以添加任何数据类型。【参考方案28】:

这是 swift 4.2 及以下代码中最简单的方法

let keyarray:NSMutableArray = NSMutableArray()

for  object in dataArr

    if !keysArray.contains(object)
        keysArray.add(object)
    


print(keysArray)

【讨论】:

哎呀。不要这样做。这是一个O(n^2) 算法(因为containsO(n),它本身运行n 次)。并且不要在 Swift 中使用 NSMutableArray【参考方案29】:

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)。不要这样做。【参考方案30】:

这适用于 Swift 4,如果您不想/不需要将结果转换为数组,但可以使用 Set。结果默认不排序,但您可以使用 sorted() 来做到这一点,它返回一个数组,如 print 语句所示。

let array = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]

var result = Set<Int>()
_ = array.map result.insert($0) 

print(result.sorted())  // [1, 2, 4, 6, 15, 24, 60]

【讨论】:

这将不可逆转地失去排序。仅当您的原始排序是排序顺序时,排序才有意义,而您的示例并非如此。另外,不要滥用mapforEach 会更有意义。即使这样,也可能只是let result = Set(array)

以上是关于在 Swift 中从数组中删除重复元素的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Swift 中从数组中删除一个元素

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

swift 删除排序数组中的重复项

如何在 Swift / iOS 中从 UITableView 中删除行并从 UserDefaults 中更新数组

Swift 数组删除元素

在 Swift 中从 UITableView 中删除一行?