Eliminar elementos duplicados de una matriz en Swift


262

Podría tener una matriz similar a la siguiente:

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

O, en realidad, cualquier secuencia de porciones de datos del mismo tipo. Lo que quiero hacer es asegurarme de que solo haya uno de cada elemento idéntico. Por ejemplo, la matriz anterior se convertiría en:

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

Observe que se eliminaron los duplicados de 2, 6 y 15 para asegurarse de que solo hubiera uno de cada elemento idéntico. ¿Swift proporciona una manera de hacer esto fácilmente o tendré que hacerlo yo mismo?


11
La forma más fácil es convertir la matriz en un NSSetNSSet es una colección desordenada de objetos, si es necesario mantener el orden NSOrderedSet.
Andrea

Puede usar la función de intersección como puede encontrar en esta clase con funciones para matrices: github.com/pNre/ExSwift/blob/master/ExSwift/Array.swift
Edwin Vermeer

No es parte de Swift pero uso Dollar. $.uniq(array) github.com/ankurp/Dollar#uniq---uniq
Andrew

Probablemente, la respuesta más elegante, inteligente y rápida la proporciona mxcl a continuación. Lo que también ayuda a mantener el orden
Cariño

1
¿Por qué no usa SetSwift? Podrá proporcionar una lista de elementos únicos y desordenados.
TibiaZ

Respuestas:


140

Puede enrollar el suyo, por ejemplo, así:

func uniq<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]

Y como extensión para Array:

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

12
También puede implementar el cuerpo de esa función comovar addedDict = [T:Bool](); return filter(source) { addedDict(true, forKey: $0) == nil }
Velocidad del aire

1
@AirspeedVelocity: ¿Quiso decir en updateValue(true, forKey: $0)...lugar deaddedDict(true, forKey: $0)...
Jawwad

1
¡Vaya, sí, lo siento, accidentalmente el método! Debería ser return filter(source) { addedDict.updateValue(true, forKey: $0) == nil }como dices.
Velocidad aerodinámica

23
Solo una advertencia: evite hablar sobre el rendimiento de funciones simples como esta hasta que se demuestre que depende de su rendimiento, momento en el que lo único que debe hacer es comparar. Con demasiada frecuencia he visto código que no se puede mantener o incluso un código de menor rendimiento debido a suposiciones. :) Además, esto es probablemente más fácil de entender:let uniques = Array(Set(vals))
Blixt

13
@Blixt De acuerdo. Una vez más, aquí la ventaja radica en respetar el orden de los elementos del arreglo original.
Jean-Philippe Pellet

520

Puede convertir a ay Setvolver a an de Arraynuevo con bastante facilidad:

let unique = Array(Set(originals))

Esto se no garantiza para mantener el orden original de la matriz.


38
¿Hay alguna forma de utilizar un conjunto conservando el orden original de la matriz?
Crashalot

7
@Crashalot Vea mi respuesta.
Jean-Philippe Pellet

5
Si necesita mantener los objetos únicos por una propiedad específica, entonces implemente también el protocolo Hashable y Equatable en esa clase, en lugar de simplemente usar la transformación Array-> Set-> Array
Fawkes

2
¡¡Agradable!! ¿Cuál es la complejidad del tiempo de esta solución, por favor?
JW.ZG

5
Falla si los elementos de originalsno lo son Hashable; solo Hashablese pueden agregar tipos de datos a un conjunto, pero cualquier tipo de datos se puede agregar a una matriz.
Mecki

72

Hay muchas respuestas disponibles aquí, pero me perdí esta extensión simple, adecuada para Swift 2 en adelante:

extension Array where Element:Equatable {
    func removeDuplicates() -> [Element] {
        var result = [Element]()

        for value in self {
            if result.contains(value) == false {
                result.append(value)
            }
        }

        return result
    }
}

Lo hace súper simple. Puede llamarse así:

let arrayOfInts = [2, 2, 4, 4]
print(arrayOfInts.removeDuplicates()) // Prints: [2, 4]

Filtrado basado en propiedades

Para filtrar una matriz según las propiedades, puede utilizar este método:

extension Array {

    func filterDuplicates(@noescape includeElement: (lhs:Element, rhs:Element) -> Bool) -> [Element]{
        var results = [Element]()

        forEach { (element) in
            let existingElements = results.filter {
                return includeElement(lhs: element, rhs: $0)
            }
            if existingElements.count == 0 {
                results.append(element)
            }
        }

        return results
    }
}

Que puede llamar de la siguiente manera:

let filteredElements = myElements.filterDuplicates { $0.PropertyOne == $1.PropertyOne && $0.PropertyTwo == $1.PropertyTwo }

@Antoine Gracias por el filtrado basado en la extensión de propiedades. Es realmente útil. Pero, ¿podría explicar cómo funciona? Es demasiado difícil de entender para mí. Gracias
Mostafa Mohamed Raafat

Actualizaciones para swift 3: func filterDuplicates (_ includeElement: (_ lhs: Element, _ rhs: Element) -> Bool) -> [Element] {
cbartel

La primera parte de esta respuesta ( extension Array where Element: Equatable) está siendo reemplazada por stackoverflow.com/a/36048862/1033581 que ofrece una solución más poderosa ( extension Sequence where Iterator.Element: Equatable).
Cœur

10
Esto tendrá un O(n²)rendimiento de tiempo, lo cual es realmente malo para arreglos grandes.
Duncan C

1
Debería usar un conjunto para realizar un seguimiento de los elementos vistos hasta ahora, para volver a reducir esta terrible O(n²)complejidad aO(n)
Alexander: restablezca a Monica

67

Utilice Seto NSOrderedSetpara eliminar duplicados, luego conviértalo de nuevo a Array:

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

1
let uniqueOrderedNames = Array (NSOrderedSet (array: userNames)) como! [Cadena] si tienes una matriz de Cadena, no de Cualquiera
Zaporozhchenko Oleksandr

Falla si los elementos de arrayno lo son Hashable; solo Hashablese pueden agregar tipos de datos a un conjunto, pero cualquier tipo de datos se puede agregar a una matriz.
Mecki

Probado en Swift 5.1b5, dado que los elementos son Hashable y el deseo de mantener el orden, el NSOrderedSet (array: array) .array es ligeramente más rápido que el swift func uniqued () puro usando un conjunto con filtro. Probé con 5100 cadenas que dieron como resultado 13 valores únicos.
dlemex

63

Si pones ambas extensiones en tu código, se Hashableusará la versión más rápida cuando sea posible, y la Equatableversión se usará como alternativa.

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

Si el orden no es importante, siempre puede usar este inicializador de conjunto .


1
@DavidSeek así, uniqueArray = nonUniqueArray.uniqueElements
Mert Celik

1
sí, no te preocupes. lo hice funcionar justo después. Han pasado casi 2 años: P
David Seek

2
Esto tendrá un O(n²)rendimiento de tiempo, lo cual es realmente malo para arreglos grandes.
Duncan C

1
La versión hahsable tendrá un mejor rendimiento, pero no conservará el orden de los elementos en la matriz original. La respuesta de Leo proporcionará O(n)rendimiento Y preservará el orden de los objetos.
Duncan C

1
@Jessy Ya hay varias O(1)respuestas, pero tienen muchos menos votos que la mayoría de las O(n^2)soluciones ingenuas . Este es particularmente bueno por su simplicidad: stackoverflow.com/a/46354989/3141234
Alexander - Reinstale Monica

52

Rápido 4

public extension Array where Element: Hashable {
    func uniqued() -> [Element] {
        var seen = Set<Element>()
        return filter{ seen.insert($0).inserted }
    }
}

todo lo posible para inserttambién devolverá una tupla: (inserted: Bool, memberAfterInsert: Set.Element). Ver documentación .

Usar el valor devuelto nos ayuda a evitar bucles o realizar cualquier otra operación.


9
Después de un perfil simple, este método es realmente rápido. Es cientos de veces más rápido que usar reducir (_: _ :), o incluso reducir (a: _ :)
Kelvin

5
@Kelvin Porque todos esos otros algoritmos fueron O(n^2), y nadie se dio cuenta.
Alexander - Reincorpora a Monica

@Kelvin esta respuesta es idéntica a la respuesta de Eneko Alonso + mi comentario (16 de junio de 2017).
Cœur

45

editar / actualizar Swift 4 o posterior

También podemos extender el RangeReplaceableCollectionprotocolo para permitir que se use también con StringProtocoltipos:

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"

Método de mutación:

var string = "abcdefabcghi"
string.removeDuplicates() 
string  //  "abcdefghi"

var substring = "abcdefabcdefghi".dropFirst(3)  // "defabcdefghi"
substring.removeDuplicates()
substring   // "defabcghi"

Para Swift 3 haga clic aquí


1
Me gusta esto, ¡también funciona con una variedad de diccionarios!
DeyaEldeen

6
O (N ^ 2) es malo :(
Alexander - Reincorpora a Monica

2
@Alexander Leo Dabus ha reemplazado la reduceimplementación, por lo que ahora la complejidad es diferente.
Cœur

1
Los resultados son interesantes. Tanto para 1 millón de elementos únicos como para 8 millones, la versión de filtro es más rápida. Sin embargo, la versión basada en filtros tarda 8,38 veces más para 8 millones de elementos únicos (un pelo con el O(n)tiempo), mientras que la versión basada en mapas planos tarda 7,47 veces más en 8 millones de entradas únicas que 1 millón, lo que sugiere que la versión basada en mapas planos se adapta mejor . ¡De alguna manera, la versión basada en mapas planos funciona un poco mejor que el O(n)tiempo!
Duncan C

1
De hecho, cuando ejecuto la prueba con 64 veces más elementos en la matriz, la versión basada en mapa plano es más rápida.
Duncan C

30

Rápido 4

Garantizado para seguir ordenando.

extension Array where Element: Equatable {
    func removingDuplicates() -> Array {
        return reduce(into: []) { result, element in
            if !result.contains(element) {
                result.append(element)
            }
        }
    }
}

Lo uso ahora, solo cambié el nombre del método a removeDuplicates :)
J. Doe

Creo que esta solución es compacto, pero creo que la solución deanWombourne publicada un año antes puede ser ligeramente más eficiente que una reduce: en general, es sólo una línea más en todo el proyecto para escribir su función como: var unique: [Iterator.Element] = []; for element in self where !unique.contains(element) { unique.append(element) }; return unique. Admito que aún no he probado las actuaciones relativas.
Cœur

4
Esto tendrá un O(n²)rendimiento de tiempo, lo cual es realmente malo para arreglos grandes.
Duncan C

@NickGaens No, no lo es, lo es O(n²). No hay nada rápido en esto.
Alexander - Reincorpora a Monica

@ Cœur reduceo reduce(into:)no haría una diferencia crítica. Reescribir esto para no llamar repetidamente containsharía una diferencia MUCHO mayor.
Alexander - Reincorporación a Monica

17

Aquí hay una categoría en la SequenceTypeque conserva el orden original de la matriz, pero usa a Setpara realizar las containsbúsquedas para evitar el O(n)costo del contains(_:)método de Array .

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

Si no es Hashable o Equatable, puede pasar un predicado para hacer la verificación de igualdad:

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

Ahora, si no tiene Hashable, pero es Equatable, puede usar este método:

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

Finalmente, puede agregar una versión de ruta clave de uniqued como esta:

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

Puede pegar ambos en su aplicación, Swift elegirá el correcto según el Iterator.Elementtipo de secuencia .


Heyyy finalmente alguien con una O(n)solución. Por cierto, puede combinar las operaciones de conjunto "comprobar" e "insertar" en una sola. Ver stackoverflow.com/a/46354989/3141234
Alexander - Reincorporar a Monica

Oh, eso es inteligente :)
deanWombourne

16

Inspirándonos en https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift , podemos declarar una herramienta más poderosa que es capaz de filtrar por unicidad en cualquier keyPath. Gracias a los comentarios de Alexander sobre varias respuestas con respecto a la complejidad, las siguientes soluciones deberían ser casi óptimas.

Solución no mutante

Extendemos con una función que puede filtrar la unicidad en cualquier 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 }
    }
}

Nota: en el caso de que su objeto no se ajuste a RangeReplaceableCollection, pero sí se ajuste a Sequence, puede tener esta extensión adicional, pero el tipo de retorno siempre será un 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 }
    }
}

Uso

Si queremos unicidad para los elementos en sí mismos, como en la pregunta, usamos el 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] */

Si queremos unicidad para otra cosa (como para el idde una colección de objetos), usamos el keyPath de nuestra elección:

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

Solución mutante

Extendemos con una función de mutación que puede filtrar la unicidad en cualquier 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 }
    }
}

Uso

Si queremos unicidad para los elementos en sí mismos, como en la pregunta, usamos el 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] */

Si queremos unicidad para otra cosa (como para el idde una colección de objetos), usamos el keyPath de nuestra elección:

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

1
¡Esa es una buena implementación! Solo con que las rutas clave eran convertibles en cierres, de modo que puede usar un argumento de cierre para admitir tanto código arbitrario (en cierres) como meras búsquedas de propiedades (a través de rutas clave). El único cambio que haría es establecer el keyPathvalor predeterminado \.self, porque probablemente esa sea la mayoría de los casos de uso.
Alexander - Reincorporación a Monica

1
@Alexander Traté de usar Self por defecto, pero luego tendría que hacerlo Elementsiempre Hashable. Una alternativa a un valor predeterminado es agregar una sobrecarga simple sin parámetros:extension Sequence where Element: Hashable { func unique() { ... } }
Cœur

¡Ah sí, tiene sentido!
Alexander - Reincorporación a Monica

1
Brillante ... simple y, lo mejor de todo, "flexible". Gracias.
BonanzaDriver

@ Alexander-ReinstateMonica: Esto se parece mucho a su propia solución de marzo de 2018: gist.github.com/amomchilov/fbba1e58c91fbd4b5b767bcf8586112b 👏👏👏
FizzBuzz

12

Una solución alternativa (si no óptima) a partir de aquí utilizando tipos inmutables en lugar de variables:

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
}

Incluido para contrastar el enfoque imperativo de Jean-Pillippe con un enfoque funcional.

Como beneficio adicional, esta función funciona tanto con cadenas como con matrices.

Editar: esta respuesta fue escrita en 2014 para Swift 1.0 (antes Setestaba disponible en Swift). No requiere conformidad con Hashable y se ejecuta en tiempo cuadrático.


8
Tenga cuidado, no hay una, sino dos formas en que esto se ejecuta en tiempo cuadrático: tanto el containsagregado como la matriz se ejecutan en O (n). Aunque tiene la ventaja de que solo requiere igualar, no mezclar.
Velocidad aerodinámica

esta es una forma de escribir realmente complicada filter. Es O (n ^ 2) (que es obligatorio si no desea exigir Hashableconformidad), pero al menos debería llamarlo explícitamente
Alexander - Reincorporar a Monica

11

Una solución más de Swift 3.0 para eliminar duplicados de una matriz. Esta solución mejora muchas otras soluciones ya propuestas por:

  • Preservando el orden de los elementos en la matriz de entrada
  • Complejidad lineal O (n): filtro de paso único O (n) + inserción de conjunto O (1)

Dada la matriz de enteros:

let numberArray = [10, 1, 2, 3, 2, 1, 15, 4, 5, 6, 7, 3, 2, 12, 2, 5, 5, 6, 10, 7, 8, 3, 3, 45, 5, 15, 6, 7, 8, 7]

Código funcional:

func orderedSet<T: Hashable>(array: Array<T>) -> Array<T> {
    var unique = Set<T>()
    return array.filter { element in
        return unique.insert(element).inserted
    }
}

orderedSet(array: numberArray)  // [10, 1, 2, 3, 15, 4, 5, 6, 7, 12, 8, 45]

Código de extensión de matriz:

extension Array where Element:Hashable {
    var orderedSet: Array {
        var unique = Set<Element>()
        return filter { element in
            return unique.insert(element).inserted
        }
    }
}

numberArray.orderedSet // [10, 1, 2, 3, 15, 4, 5, 6, 7, 12, 8, 45]

Este código aprovecha el resultado devuelto por la insertoperación en Set, que se ejecuta en O(1), y devuelve una tupla que indica si el elemento se insertó o si ya existía en el conjunto.

Si el artículo estaba en el conjunto, filterlo excluirá del resultado final.


1
No sea exigente, pero realizará el inserto y la prueba de membresía tantas veces como haya elementos, por lo que también debe contar su costo como O (n). Sin embargo, esto no significa 3xO (n) porque estas O y no tienen el mismo costo con el filtro, por lo que la adición de O (n) es manzanas a naranjas. Si consideramos que las operaciones de conjuntos son una parte O (1) del costo del filtro, la complejidad es simplemente O (n), aunque con una "O" mayor. Llevando esto al límite, también podría evitar las inserciones cuando el elemento ya está en el conjunto.
Alain T.

Tiene razón, usar deferel código haría la operación de prueba establecida dos veces, una con containsy otra con insert. Al leer más la documentación de Swift, descubrí que insertdevuelve una tupla que indica si el elemento se insertó o no, así que simplifiqué el código eliminando la containsmarca.
Eneko Alonso

3
Agradable. Su extensión podría ser óptima si la realiza elextension Sequence where Iterator.Element: Hashable { ... }
Cœur

@AlainT. No Ambos inserty containstienen O(1)complejidad. O(1) + O(1) = O(1). Estas dos operaciones luego se realizan nveces (una vez por llamada del cierre pasado filter, que se llama una vez por elemento) Es decir, si una operación toma una cantidad de tiempo constante independientemente del tamaño de entrada, hacerla dos veces aún hace que tome un tiempo constante eso es independientemente del tamaño de entrada. La complejidad total de esto es O(n).
Alexander - Reincorporación a Monica

11

En Swift 5

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

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

La salida será

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

2
Esta es una repetición de muchas de las respuestas que ya se encuentran aquí, y no conserva el orden.
Alexander - Reincorpora a Monica

10

veloz 2

con respuesta de función uniq :

func uniq<S: SequenceType, E: Hashable where E==S.Generator.Element>(source: S) -> [E] {
    var seen: [E:Bool] = [:]
    return source.filter({ (v) -> Bool in
        return seen.updateValue(true, forKey: v) == nil
    })
}

utilizar:

var test = [1,2,3,4,5,6,7,8,9,9,9,9,9,9]
print(uniq(test)) //1,2,3,4,5,6,7,8,9

El Boolvalor obviamente es redundante, ya que su código nunca lo lee. Use a en Setlugar de a Dictionaryy obtendrá mi voto a favor.
Nikolai Ruhe

9

Rápido 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] }
  }
}

uso:

["Ljubljana", "London", "Los Angeles", "Ljubljana"].unique()

o

["Ljubljana", "London", "Los Angeles", "Ljubljana"].uniqueOrdered()

Esto es O(n^2). No hagas esto.
Alexander - Reincorporación a Monica

9

Rápido 5

extension Sequence where Element: Hashable {
    func unique() -> [Element] {
        NSOrderedSet(array: self as! [Any]).array as! [Element]
    }
}

Hice algunas variaciones para poder seleccionar una clave para comparar. extension Sequence { // Returns distinct elements based on a key value. func distinct<key: Hashable>(by: ((_ el: Iterator.Element) -> key)) -> [Iterator.Element] { var existing = Set<key>() return self.filter { existing.insert(by($0)).inserted } } }
Marcelo de Aguiar

No es necesario utilizar a Bool, cuando el único valor que utiliza es true. Está buscando un "tipo de unidad" (un tipo con un solo valor posible). El tipo de unidad de Swift es Void, cuyo único valor es ()(también conocido como la tupla vacía). Entonces puedes usar [T: Void]. Aunque no debería hacer eso, porque básicamente acaba de inventar Set. Úselo en su Setlugar. Consulte stackoverflow.com/a/55684308/3141234 Elimine esta respuesta.
Alexander - Reincorpora a Monica

9

Piense como un programador funcional :)

Para filtrar la lista en función de si el elemento ya se ha producido, necesita el índice. Puede utilizar enumeratedpara obtener el índice y mapvolver a la lista de valores.

let unique = myArray
    .enumerated()
    .filter{ myArray.firstIndex(of: $0.1) == $0.0 }
    .map{ $0.1 }

Esto garantiza el pedido. Si no le importa el orden, la respuesta existente de Array(Set(myArray))es más simple y probablemente más eficiente.


ACTUALIZACIÓN: Algunas notas sobre eficiencia y corrección

Algunas personas han comentado sobre la eficiencia. Definitivamente estoy en la escuela de escribir código correcto y simple primero y luego descubrir cuellos de botella más tarde, aunque aprecio que sea discutible si esto es más claro que Array(Set(array)).

Este método es mucho más lento que Array(Set(array)). Como se señaló en los comentarios, conserva el orden y funciona con elementos que no se pueden usar con hash.

Sin embargo, el método de @Alain T también conserva el orden y también es mucho más rápido. Entonces, a menos que su tipo de elemento no sea codificable, o solo necesite un delineador rápido, le sugiero que vaya con su solución.

Aquí hay algunas pruebas en un MacBook Pro (2014) en Xcode 11.3.1 (Swift 5.1) en modo de lanzamiento.

La función del generador de perfiles y dos métodos para comparar:

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

Y una pequeña variedad de entradas de prueba:

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

Da como salida:

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

1
a diferencia Array(Set(myArray)), esto funciona para cosas que no lo sonHashable
Porter Child

1
... y a diferencia Array(Set(myArray))del orden de su matriz se mantiene.
Sander Saelmans

Me parece la mejor respuesta, al menos en este momento cuando Swift 5 ya es la versión actual.
oradyvan

Ésta es una solución muy elegante; desafortunadamente, también es bastante lento.
Colin Stark

1
@TimMB Oh, leí mal tu publicación. Vi la adaptación de alguien que usó el lastIndex(of:). Estoy totalmente en desacuerdo sobre el punto de claridad frente a optimización en este caso. No creo que esta implementación sea particularmente clara, especialmente en comparación con una solución simple basada en conjuntos. En cualquier caso, dicho código debe extraerse a una función de extensión. Este algoritmo se vuelve básicamente inutilizable incluso con un tamaño de entrada bajo, como de miles a decenas de miles. No es difícil encontrar tales conjuntos de datos, las personas pueden tener miles de canciones, archivos, contactos, etc.
Alexander - Reinstale Monica

6

Para matrices donde los elementos no son Hashable ni Comparable (por ejemplo, objetos complejos, diccionarios o estructuras), esta extensión proporciona una forma generalizada de eliminar duplicados:

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

No tiene que molestarse en hacer valores Hashable y le permite usar diferentes combinaciones de campos para lograr unicidad.

Nota: para un enfoque más sólido, consulte la solución propuesta por Coeur en los comentarios a continuación.

stackoverflow.com/a/55684308/1033581

[EDITAR] Alternativa a Swift 4

Con Swift 4.2 puedes usar la clase Hasher para construir un hash mucho más fácilmente. La extensión anterior podría cambiarse para aprovechar esto:

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

La sintaxis de la llamada es un poco diferente porque el cierre recibe un parámetro adicional que contiene una función para aplicar un hash a un número variable de valores (que deben ser hash individualmente)

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

También funcionará con un único valor de unicidad (usando $ 1 e ignorando $ 0).

peopleArray = peopleArray.filterDuplicate{ $1.name } 

Esto podría dar resultados aleatorios dependiendo del comportamiento de "\()", ya que es posible que no le brinde valores únicos como Hashabledebería. Por ejemplo, si sus elementos se ajustan a Printabletodos devolviendo lo mismo description, entonces su filtrado falla.
Cœur

Convenido. La selección de los campos (o fórmula) que producirán el patrón de unicidad deseado deberá tener esto en cuenta. Para muchos casos de uso, esto proporciona una solución ad-hoc simple que no requiere alteración de la clase o estructura del elemento.
Alain T.

2
@AlainT. No hagas esto, de verdad. El propósito de String no es ser un mecanismo de generación de claves ad-hoc del ghetto. Limítate Ta ser Hashable.
Alexander - Reincorpora a Monica el

@Alexander He aplicado esta idea en una nueva respuesta: stackoverflow.com/a/55684308/1033581
Cœur

Respuesta perfecta como yo quiera. Muchas gracias.
Hardik Thakkar

4

Puede usar directamente una colección de conjuntos para eliminar el duplicado y luego volver a convertirlo en una matriz

var myArray = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
var mySet = Set<Int>(myArray)

myArray = Array(mySet) // [2, 4, 60, 6, 15, 24, 1]

Entonces puedes ordenar tu matriz como quieras

myArray.sort{$0 < $1} // [1, 2, 4, 6, 15, 24, 60]

"Entonces puede ordenar su matriz como desee" ¿Qué pasa si quiero el mismo orden que la matriz original? No es así de fácil.
Alexander - Reincorpora a Monica el

3

Versión de sintaxis un poco más concisa de la respuesta Swift 2 de Daniel Krom , con un cierre final y un nombre de argumento abreviado, que parece estar basado en la respuesta original de Airspeed Velocity :

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

Ejemplo de implementación de un tipo personalizado que se puede usar con uniq(_:)(que debe ajustarse a Hashable, y por lo tanto Equatable, porque se Hashableextiende 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
  }

}

En el código anterior ...

id, como se usa en la sobrecarga de ==, podría ser cualquier Equatabletipo (o método que devuelva un Equatabletipo, por ejemplo, someMethodThatReturnsAnEquatableType()). El código comentado demuestra la extensión de la verificación de igualdad, donde someOtherEquatablePropertyhay otra propiedad de un Equatabletipo (pero también podría ser un método que devuelve un Equatabletipo).

id, como se usa en la hashValuepropiedad calculada (requerida para ajustarse a Hashable), podría ser cualquier Hashable(y por lo tanto Equatable) propiedad (o método que devuelva un Hashabletipo).

Ejemplo de uso 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

No es necesario utilizar a Bool, cuando el único valor que utiliza es true. Está buscando un "tipo de unidad" (un tipo con un solo valor posible). El tipo de unidad de Swift es Void, cuyo único valor es ()(también conocido como la tupla vacía). Entonces puedes usar [T: Void]. Aunque no deberías hacer eso, porque básicamente lo acabas de inventar Set. Úselo en su Setlugar. Ver stackoverflow.com/a/55684308/3141234
Alexander - Reincorporar a Monica

3

En caso de que necesite valores ordenados, esto funciona (Swift 4)

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


2
Perdiendo el orden de los elementos en este caso.
Shmidt

En absoluto, para eso está .sorted()el final. Saludos.
Mauricio Chirino

@MauricioChirino ¿Y si tu matriz original fuera [2, 1, 1]? Saldría [1, 2], eso no está ordenado: p
Alexander - Reincorporar a Monica

2
@MauricioChirino No, no lo soy. Si el objetivo es eliminar valores duplicados de una secuencia, mientras se conserva el orden en que los elementos aparecieron de forma única, esto no hace eso. El ejemplo contrario muy claro es [2, 1, 1]. La primera aparición de elementos únicos, en orden, es [2, 1]. Esa es la respuesta correcta. Pero usando su algoritmo (incorrecto), obtiene [1, 2], que está ordenado, pero no está en el orden original correcto.
Alexander - Reincorporación a Monica

2
Falla si los elementos de arrayno lo son Hashable; solo Hashablese pueden agregar tipos de datos a un conjunto, pero cualquier tipo de datos se puede agregar a una matriz.
Mecki

3
  1. Primero agregue todos los elementos de una matriz a NSOrderedSet.
  2. Esto eliminará todos los duplicados en su matriz.
  3. Vuelva a convertir este conjunto ordenado en una matriz.

Hecho....

Ejemplo

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

salida de arrayWithoutDuplicates - [1,2,4,6,8]


3

Aquí hay una solución que

  • No usa NStipos heredados
  • Es razonablemente rápido con O(n)
  • Es conciso
  • Conserva el orden de los elementos
extension Array where Element: Hashable {

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

2

aquí he hecho alguna solución O (n) para objetos. No es una solución de pocas líneas, pero ...

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

1
En lugar de usar un Setcon un personalizado DistinctWrapper, debe usar un Dictionaryatributo distinto a los objetos. Cuando sigas esa lógica, eventualmente terminarás implementando [ Dictionary.init(_:uniquingKeysWith:)] pastebin.com/w90pVe0p(https://developer.apple.com/documentation/… , que ahora está integrado en la biblioteca estándar. Mira lo simple que es esto pastebin.com/w90pVe0p
Alexander - Reincorporación a Monica

2

Usé la respuesta de @ Jean-Philippe Pellet e hice una extensión Array que realiza operaciones similares a conjuntos en arreglos, mientras mantiene el orden de los elementos.

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

No es necesario utilizar a Bool, cuando el único valor que utiliza es true. Está buscando un "tipo de unidad" (un tipo con un solo valor posible). El tipo de unidad de Swift es Void, cuyo único valor es ()(también conocido como la tupla vacía). Entonces puedes usar [T: Void]. Aunque no deberías hacer eso, porque básicamente lo acabas de inventar Set. Úselo en su Setlugar. Ver stackoverflow.com/a/55684308/3141234
Alexander - Reincorporar a Monica

2

Esta es una implementación muy simple y conveniente. Propiedad calculada en una extensión de una matriz que tiene elementos equivalentes.

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

1
Esto también lo es O(n^2).
Alexander - Reincorporación a Monica

2
func removeDublicate (ab: [Int]) -> [Int] {
var answer1:[Int] = []
for i in ab {
    if !answer1.contains(i) {
        answer1.append(i)
    }}
return answer1
}

Uso:

let f = removeDublicate(ab: [1,2,2])
print(f)

Creo que este es el más simple
Jack Rus

mantiene el orden y le da la matriz que desea
Jack Rus

Esto también lo es O(n²).
Alexander - Reincorpora a Monica el

2

Swift 3 / Swift 4 / Swift 5

Solo un código de línea para omitir los duplicados de Array sin afectar el orden:

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

Aquí estamos encasillando una matriz en Orderedset. Definición de "Conjuntos": los conjuntos permiten solo valores distintos (no permite duplicados). Por lo tanto, se omitirán los duplicados ya que estamos encasillando con NSOrderedSet, por lo tanto, el orden de la matriz no se verá alterado.
vilas deshmukh

1

Siempre puede usar un diccionario, porque un diccionario solo puede contener valores únicos. Por ejemplo:

var arrayOfDates: NSArray = ["15/04/01","15/04/01","15/04/02","15/04/02","15/04/03","15/04/03","15/04/03"]

var datesOnlyDict = NSMutableDictionary()
var x = Int()

for (x=0;x<(arrayOfDates.count);x++) {
    let date = arrayOfDates[x] as String
    datesOnlyDict.setValue("foo", forKey: date)
}

let uniqueDatesArray: NSArray = datesOnlyDict.allKeys // uniqueDatesArray = ["15/04/01", "15/04/03", "15/04/02"]

println(uniqueDatesArray.count)  // = 3

Como puede ver, la matriz resultante no siempre estará en 'orden'. Si desea ordenar / ordenar la matriz, agregue esto:

var sortedArray = sorted(datesOnlyArray) {
(obj1, obj2) in

    let p1 = obj1 as String
    let p2 = obj2 as String
    return p1 < p2
}

println(sortedArray) // = ["15/04/01", "15/04/02", "15/04/03"]

.


1

La forma más sencilla sería utilizar NSOrderedSet, que almacena elementos únicos y conserva el orden de los elementos. Me gusta:

func removeDuplicates(from items: [Int]) -> [Int] {
    let uniqueItems = NSOrderedSet(array: items)
    return (uniqueItems.array as? [Int]) ?? []
}

let arr = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
removeDuplicates(from: arr)

Me pregunto cómo se compara este rendimiento con las mejores respuestas aquí. ¿Has comparado?
Alexander - Reincorpora a Monica
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.