【问题标题】:Removing duplicate elements from an array in Swift在 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 呢?您将能够提供无序和独特元素的列表。

标签: arrays swift standard-library


【解决方案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 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 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 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 类型选择正确的。


      对于 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 stackoverflow.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) 解决方案。顺便说一句,您可以将“检查”和“插入”集合操作合二为一。见stackoverflow.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) 解决方案相比,它们的投票数要少得多。这个特别简单:stackoverflow.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.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
          

          【讨论】:

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

          【讨论】:

            【解决方案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 返回一个元组,告诉您该元素是否已经存在,或者是第一次添加。 stackoverflow.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。见stackoverflow.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 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 在下面的 cmets 中提出的解决方案。

            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 应该的唯一值。例如,如果您的元素都通过返回相同的description 符合Printable,那么您的过滤将失败。
            • 同意。选择将产生所需唯一性模式的字段(或公式)必须考虑到这一点。对于许多用例,这提供了一个简单的临时解决方案,不需要更改元素的类或结构。
            • @AlainT。不要这样做,真的。 String 的目的不是成为某种贫民窟的临时密钥生成机制。只需将 T 限制为 Hashable
            • @Alexander 我在一个新的答案中应用了这个想法:stackoverflow.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)
            猜你喜欢
            • 2013-05-20
            • 2011-07-03
            • 2017-03-21
            • 1970-01-01
            • 1970-01-01
            • 2023-03-27
            • 1970-01-01
            • 2017-08-12
            相关资源
            最近更新 更多