Removing duplicate elements from an array in Swift

后端 未结 30 2508
遥遥无期
遥遥无期 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条回答
  •  萌比男神i
    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(for keyPath: KeyPath) -> Self {
            var unique = Set()
            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(for keyPath: KeyPath) -> [Element] {
            var unique = Set()
            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(for keyPath: KeyPath) {
            var unique = Set()
            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}] */
    

提交回复
热议问题