Removing duplicate elements from an array in Swift

后端 未结 30 2053
遥遥无期
遥遥无期 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:55

    Here is a solution that

    • Uses no legacy NS types
    • Is reasonably fast with O(n)
    • Is concise
    • Preserves element order
    extension Array where Element: Hashable {
    
        var uniqueValues: [Element] {
            var allowed = Set(self)
            return compactMap { allowed.remove($0) }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 00:56

    Slightly more succinct syntax version of Daniel Krom's Swift 2 answer, using a trailing closure and shorthand argument name, which appears to be based on Airspeed Velocity's original answer:

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

    Example of implementing a custom type that can be used with uniq(_:) (which must conform to Hashable, and thus Equatable, because Hashable extends 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
      }
    
    }
    

    In the above code...

    id, as used in the overload of ==, could be any Equatable type (or method that returns an Equatable type, e.g., someMethodThatReturnsAnEquatableType()). The commented-out code demonstrates extending the check for equality, where someOtherEquatableProperty is another property of an Equatable type (but could also be a method that returns an Equatable type).

    id, as used in the hashValue computed property (required to conform to Hashable), could be any Hashable (and thus Equatable) property (or method that returns a Hashable type).

    Example of using 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
    
    0 讨论(0)
  • 2020-11-22 00:56

    This is just a very simple and convenient implementation. A computed property in an extension of an Array that has equatable elements.

    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
        }
    }
    
    0 讨论(0)
  • 2020-11-22 00:56
    func removeDublicate (ab: [Int]) -> [Int] {
    var answer1:[Int] = []
    for i in ab {
        if !answer1.contains(i) {
            answer1.append(i)
        }}
    return answer1
    }
    

    Usage:

    let f = removeDublicate(ab: [1,2,2])
    print(f)
    
    0 讨论(0)
  • 2020-11-22 00:57

    here I've done some O(n) solution for objects. Not few-lines solution, but...

    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")
    
    0 讨论(0)
  • 2020-11-22 00:58

    Inspired by https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift, we can declare a more powerful tool that is able to filter for unicity on any keyPath. Thanks to Alexander comments on various answers regarding complexity, the below solutions should be near optimal.

    Non-mutating solution

    We extend with a function that is able to filter for unicity on any 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 }
        }
    }
    

    Note: in the case where your object doesn't conform to RangeReplaceableCollection, but does conform to Sequence, you can have this additional extension, but the return type will always be an 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 }
        }
    }
    

    Usage

    If we want unicity for elements themselves, as in the question, we use the 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] */
    

    If we want unicity for something else (like for the id of a collection of objects) then we use the keyPath of our choice:

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

    Mutating solution

    We extend with a mutating function that is able to filter for unicity on any 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 }
        }
    }
    

    Usage

    If we want unicity for elements themselves, as in the question, we use the 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] */
    

    If we want unicity for something else (like for the id of a collection of objects) then we use the keyPath of our choice:

    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}] */
    
    0 讨论(0)
提交回复
热议问题