Removing duplicate elements from an array in Swift

后端 未结 30 1995
遥遥无期
遥遥无期 2020-11-22 00:07

I might have an array that looks like the following:

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

Or, reall

相关标签:
30条回答
  • 2020-11-22 00:58

    In Swift 5

     var array: [String] =  ["Aman", "Sumit", "Aman", "Sumit", "Mohan", "Mohan", "Amit"]
    
     let uniq = Array(Set(array))
     print(uniq)
    

    Output Will be

     ["Sumit", "Mohan", "Amit", "Aman"]
    
    0 讨论(0)
  • 2020-11-22 00:59

    Here's a category on SequenceType which preserves the original order of the array, but uses a Set to do the contains lookups to avoid the O(n) cost on Array's contains(_:) method.

    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 }
        }
    }
    

    If you aren't Hashable or Equatable, you can pass in a predicate to do the equality check:

    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
        }
    }
    

    Now, if you don't have Hashable, but are Equatable, you can use this method:

    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: ==)
        }
    }
    

    Finally, you can add a key path version of uniqued like this:

    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] }
        }
    }
    

    You can stick both of these into your app, Swift will choose the right one depending on your sequence's Iterator.Element type.

    0 讨论(0)
  • 2020-11-22 00:59

    I used @Jean-Philippe Pellet's answer and made an Array extension that does set-like operations on arrays, while maintaining the order of elements.

    /// 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) })
      }
    }
    
    0 讨论(0)
  • 2020-11-22 01:01

    Use a Set or NSOrderedSet to remove duplicates, then convert back to an Array:

    let uniqueUnordered = Array(Set(array))
    let uniqueOrdered = Array(NSOrderedSet(array: array))
    
    0 讨论(0)
  • 2020-11-22 01:02

    For arrays where the elements are neither Hashable nor Comparable (e.g. complex objects, dictionaries or structs), this extension provides a generalized way to remove duplicates:

    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))" }
    

    You don't have to bother with making values Hashable and it allows you to use different combinations of fields for uniqueness.

    Note: for a more robust approach, please see the solution proposed by Coeur in the comments below.

    stackoverflow.com/a/55684308/1033581

    [EDIT] Swift 4 alternative

    With Swift 4.2 you can use the Hasher class to build a hash much easier. The above extension could be changed to leverage this :

    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}     
        }
    }
    

    The calling syntax is a little different because the closure receives an additional parameter containing a function to hash a variable number of values (which must be Hashable individually)

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

    It will also work with a single uniqueness value (using $1 and ignoring $0).

    peopleArray = peopleArray.filterDuplicate{ $1.name } 
    
    0 讨论(0)
  • 2020-11-22 01:02

    Swift 3/ Swift 4/ Swift 5

    Just one line code to omit Array duplicates without effecting order:

    let filteredArr = Array(NSOrderedSet(array: yourArray))
    
    0 讨论(0)
提交回复
热议问题