Extensión de matriz para eliminar objetos por valor


140
extension Array {
    func removeObject<T where T : Equatable>(object: T) {
        var index = find(self, object)
        self.removeAtIndex(index)
    }
}

Sin embargo, aparece un error en var index = find(self, object)

'T' no es convertible a 'T'

También probé con la firma de este método: func removeObject(object: AnyObject)sin embargo, obtengo el mismo error:

'AnyObject' no es convertible a 'T'

¿Cuál es la forma apropiada de hacer esto?


Intente eliminar el T wherede su declaración de método. Por lo que sólo func removeObject<T: Equatable>. Esta pregunta está relacionada: stackoverflow.com/questions/24091046/…
ahruss

Respuestas:


165

A partir de Swift 2 , esto se puede lograr con un método de extensión de protocolo . removeObject()se define como un método en todos los tipos que se ajustan RangeReplaceableCollectionType(en particular a Array) si los elementos de la colección son Equatable:

extension RangeReplaceableCollectionType where Generator.Element : Equatable {

    // Remove first collection element that is equal to the given `object`:
    mutating func removeObject(object : Generator.Element) {
        if let index = self.indexOf(object) {
            self.removeAtIndex(index)
        }
    }
}

Ejemplo:

var ar = [1, 2, 3, 2]
ar.removeObject(2)
print(ar) // [1, 3, 2]

Actualización para Swift 2 / Xcode 7 beta 2: como Airspeed Velocity notó en los comentarios, ahora es posible escribir un método en un tipo genérico que sea más restrictivo en la plantilla, por lo que el método ahora podría definirse como una extensión de Array:

extension Array where Element : Equatable {

    // ... same method as above ...
}

La extensión de protocolo todavía tiene la ventaja de ser aplicable a un conjunto de tipos más grande.

Actualización para Swift 3:

extension Array where Element: Equatable {

    // Remove first collection element that is equal to the given `object`:
    mutating func remove(object: Element) {
        if let index = index(of: object) {
            remove(at: index)
        }
    }
}

1
Perfecto, tienes que amar a Swift (2). Realmente me gusta cómo con el tiempo se hacen posibles más cosas y las cosas se simplifican
Kametrixom

1
Buen punto, en muchos sentidos el hecho de que la respuesta sigue siendo técnicamente correcta, simplemente ya no es idiomática, es aún peor: la gente vendrá, leerá la respuesta, pensará que una función libre es la forma correcta de resolverla, ya que es una respuesta altamente calificada . Escenario bastante feo. Publicará en meta.
Velocidad de velocidad

1
@AirspeedVelocity: Wow, me perdí eso. ¿Está cubierto en las notas de la versión?
Martin R

1
Si desea la misma funcionalidad que ObjC (es decir, elimina todos los objetos coincidentes en lugar de solo el primero), puede cambiar "if" a "while"
Powertoold

2
La versión Swift 3 es excelente, pero cambiaría el nombre de su declaración ligeramente remove(object: Element)para cumplir con las pautas de diseño de la API Swift y evitar la verbosidad. He enviado una edición que refleja esto.
swiftcode

66

No puede escribir un método en un tipo genérico que sea más restrictivo en la plantilla.

NOTA : a partir de Swift 2.0, ahora puede escribir métodos que son más restrictivos en la plantilla. Si ha actualizado su código a 2.0, vea otras respuestas más abajo para conocer nuevas opciones para implementar esto usando extensiones.

La razón por la que obtiene el error 'T' is not convertible to 'T'es que en realidad está definiendo un nuevo T en su método que no está relacionada en absoluto con la T. original. Si desea utilizar T en su método, puede hacerlo sin especificarlo en su método.

La razón por la que obtiene el segundo error 'AnyObject' is not convertible to 'T'es que todos los valores posibles para T no son todas las clases. Para que una instancia se convierta a AnyObject, debe ser una clase (no puede ser una estructura, enumeración, etc.).

Su mejor opción es hacer que sea una función que acepte la matriz como argumento:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) {
}

O, en lugar de modificar la matriz original, puede hacer que su método sea más seguro y reutilizable devolviendo una copia:

func arrayRemovingObject<T : Equatable>(object: T, fromArray array: [T]) -> [T] {
}

Como alternativa que no recomiendo, puede hacer que su método falle silenciosamente si el tipo almacenado en la matriz no se puede convertir a la plantilla de métodos (eso es equiparable). (Para mayor claridad, estoy usando U en lugar de T para la plantilla del método):

extension Array {
    mutating func removeObject<U: Equatable>(object: U) {
        var index: Int?
        for (idx, objectToCompare) in enumerate(self) {
            if let to = objectToCompare as? U {
                if object == to {
                    index = idx
                }
            }
        }

        if(index != nil) {
            self.removeAtIndex(index!)
        }
    }
}

var list = [1,2,3]
list.removeObject(2) // Successfully removes 2 because types matched
list.removeObject("3") // fails silently to remove anything because the types don't match
list // [1, 3]

Editar Para superar la falla silenciosa, puede devolver el éxito como un bool:

extension Array {
  mutating func removeObject<U: Equatable>(object: U) -> Bool {
    for (idx, objectToCompare) in self.enumerate() {  //in old swift use enumerate(self) 
      if let to = objectToCompare as? U {
        if object == to {
          self.removeAtIndex(idx)
          return true
        }
      }
    }
    return false
  }
}
var list = [1,2,3,2]
list.removeObject(2)
list
list.removeObject(2)
list

Mira mi respuesta aquí: stackoverflow.com/a/24939242/458960 ¿Por qué puedo hacerlo de esta manera y no usar el findmétodo?
Muñeco de nieve

Su método es susceptible a fallas de tiempo de ejecución. Con mi función, el compilador evitará que eso suceda.
drewag

1
@Isuru Este método funciona con cualquier objeto que implemente el Equatableprotocolo. UIView lo hace, sí, funcionará con UIViews
drewag

44
¡Guau, escribir un bucle for para eliminar un elemento, de vuelta a los años 90!
Zorayr

55
En el último veloz. enumerate(self)tengo que arreglarloself.enumerate()
TomSawyer

29

breve y concisamente:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}

2
Esto es genial Por supuesto, se puede hacer sin el inout, también. Incluso con lo inoutintacto, uno podría usar, array = array.filter() { $0 != object }creo.
Dan Rosenstark

11
Tenga en cuenta el uso del índice forzado sin envolver, que puede ser nulo. Cambie a "if let ind = index {array.removeAtIndex (ind)}"
HotJard

17

Después de leer todo lo anterior, en mi opinión, la mejor respuesta es:

func arrayRemovingObject<U: Equatable>(object: U, # fromArray:[U]) -> [U] {
  return fromArray.filter { return $0 != object }
}

Muestra:

var myArray = ["Dog", "Cat", "Ant", "Fish", "Cat"]
myArray = arrayRemovingObject("Cat", fromArray:myArray )

Extensión de matriz Swift 2 (xcode 7b4):

extension Array where Element: Equatable {  
  func arrayRemovingObject(object: Element) -> [Element] {  
    return filter { $0 != object }  
  }  
}  

Muestra:

var myArray = ["Dog", "Cat", "Ant", "Fish", "Cat"]
myArray = myArray.arrayRemovingObject("Cat" )

Actualización Swift 3.1

Volví a esto ahora que Swift 3.1 está fuera. A continuación se muestra una extensión que proporciona variantes exhaustivas, rápidas, mutantes y creadoras.

extension Array where Element:Equatable {
    public mutating func remove(_ item:Element ) {
        var index = 0
        while index < self.count {
            if self[index] == item {
                self.remove(at: index)
            } else {
                index += 1
            }
        }
    }

    public func array( removing item:Element ) -> [Element] {
        var result = self
        result.remove( item )
        return result
    }
}

Muestras:

// Mutation...
      var array1 = ["Cat", "Dog", "Turtle", "Cat", "Fish", "Cat"]
      array1.remove("Cat")
      print(array1) //  ["Dog", "Turtle", "Socks"]

// Creation...
      let array2 = ["Cat", "Dog", "Turtle", "Cat", "Fish", "Cat"]
      let array3 = array2.array(removing:"Cat")
      print(array3) // ["Dog", "Turtle", "Fish"]

¿No devuelve esto una instancia completamente nueva de la matriz?
pxpgraphics

Si. Es un estilo más funcional. YMMV.
Diciembre

Tiendo a estar de acuerdo con el estilo funcional, excepto, en este caso, cuando la filterfunción ya maneja esa funcionalidad por usted. Esto parece duplicar la funcionalidad. Pero una buena respuesta, no obstante:]
pxpgraphics

13

Con las extensiones de protocolo puedes hacer esto,

extension Array where Element: Equatable {
    mutating func remove(object: Element) {
        if let index = indexOf({ $0 == object }) {
            removeAtIndex(index)
        }
    }
}

Misma funcionalidad para las clases,

Swift 2

extension Array where Element: AnyObject {
    mutating func remove(object: Element) {
        if let index = indexOf({ $0 === object }) {
            removeAtIndex(index)
        }
    }
}

Swift 3

extension Array where Element: AnyObject {
    mutating func remove(object: Element) {
        if let index = index(where: { $0 === object }) {
             remove(at: index)
        }
    }
}

Pero si una clase implementa Equatable se vuelve ambigua y el compilador da un error de lanzamiento.


1
Me estoy poniendo unBinary operator '===' cannot be applied to two elements of type '_' and 'Element'
zapato

6

Con el uso de extensiones de protocolo en swift 2.0

extension _ArrayType where Generator.Element : Equatable{
    mutating func removeObject(object : Self.Generator.Element) {
        while let index = self.indexOf(object){
            self.removeAtIndex(index)
        }
    }
}

4

¿Qué hay de usar el filtrado? lo siguiente funciona bastante bien incluso con [AnyObject].

import Foundation
extension Array {
    mutating func removeObject<T where T : Equatable>(obj: T) {
        self = self.filter({$0 as? T != obj})
    }

}

2

Existe otra posibilidad de eliminar un elemento de una matriz sin tener un posible uso inseguro, ya que el tipo genérico del objeto a eliminar no puede ser el mismo que el tipo de la matriz. El uso de opcionales tampoco es la manera perfecta de hacerlo, ya que son muy lentos. Por lo tanto, podría usar un cierre como ya se usa al ordenar una matriz, por ejemplo.

//removes the first item that is equal to the specified element
mutating func removeFirst(element: Element, equality: (Element, Element) -> Bool) -> Bool {
    for (index, item) in enumerate(self) {
        if equality(item, element) {
            self.removeAtIndex(index)
            return true
        }
    }
    return false
}

Cuando extiende la Arrayclase con esta función, puede eliminar elementos haciendo lo siguiente:

var array = ["Apple", "Banana", "Strawberry"]
array.removeFirst("Banana") { $0 == $1 } //Banana is now removed

Sin embargo, incluso podría eliminar un elemento solo si tiene la misma dirección de memoria (solo para las clases que se ajustan al AnyObjectprotocolo, por supuesto):

let date1 = NSDate()
let date2 = NSDate()
var array = [date1, date2]
array.removeFirst(NSDate()) { $0 === $1 } //won't do anything
array.removeFirst(date1) { $0 === $1 } //array now contains only 'date2'

Lo bueno es que puede especificar el parámetro para comparar. Por ejemplo, cuando tiene una matriz de matrices, puede especificar el cierre de igualdad como { $0.count == $1.count }y la primera matriz que tiene el mismo tamaño que la que se elimina se elimina de la matriz.

Incluso podría acortar la llamada a la función teniendo la función como mutating func removeFirst(equality: (Element) -> Bool) -> Bool, luego reemplazar la evaluación if con equality(item)y llamar a la función por, array.removeFirst({ $0 == "Banana" })por ejemplo.


Como ==es una función, también puede llamarlo así para cualquier tipo que implemente ==(como String, Int, etc.):array.removeFirst("Banana", equality:==)
Aviel Gross

@AvielGross esto es nuevo en Swift 2, creo - siéntase libre de editar la respuesta en consecuencia si lo desea
borchero

2

No es necesario extender:

var ra = [7, 2, 5, 5, 4, 5, 3, 4, 2]

print(ra)                           // [7, 2, 5, 5, 4, 5, 3, 4, 2]

ra.removeAll(where: { $0 == 5 })

print(ra)                           // [7, 2, 4, 3, 4, 2]

if let i = ra.firstIndex(of: 4) {
    ra.remove(at: i)
}

print(ra)                           // [7, 2, 3, 4, 2]

if let j = ra.lastIndex(of: 2) {
    ra.remove(at: j)
}

print(ra)                           // [7, 2, 3, 4]

1

Usando en indexOflugar de a foro enumerate:

extension Array where Element: Equatable {

   mutating func removeElement(element: Element) -> Element? {
      if let index = indexOf(element) {
         return removeAtIndex(index)
      }
      return nil
   }

   mutating func removeAllOccurrencesOfElement(element: Element) -> Int {
       var occurrences = 0
       while true {
          if let index = indexOf(element) {
             removeAtIndex(index)
             occurrences++
          } else {
             return occurrences
          }
       }
   }   
}

1

Quizás no entendí la pregunta.

¿Por qué no funcionaría esto?

import Foundation
extension Array where Element: Equatable {
    mutating func removeObject(object: Element) {
        if let index = self.firstIndex(of: object) {
            self.remove(at: index)
        }
    }
}

var testArray = [1,2,3,4,5,6,7,8,9,0]
testArray.removeObject(object: 6)
let newArray = testArray

var testArray2 = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
testArray2.removeObject(object: "6")
let newArray2 = testArray2

0

Finalmente terminé con el siguiente código.

extension Array where Element: Equatable {

    mutating func remove<Element: Equatable>(item: Element) -> Array {
        self = self.filter { $0 as? Element != item }
        return self
    }

}

0

Logré eliminar a [String:AnyObject]de una matriz [[String:AnyObject]]implementando un conteo fuera de un bucle for para representar el índice desde entonces .findy .filterno son compatibles con [String:AnyObject].

let additionValue = productHarvestChoices[trueIndex]["name"] as! String
var count = 0
for productHarvestChoice in productHarvestChoices {
  if productHarvestChoice["name"] as! String == additionValue {
    productHarvestChoices.removeAtIndex(count)
  }
  count = count + 1
}

-1

Implementación en Swift 2:

extension Array {
  mutating func removeObject<T: Equatable>(object: T) -> Bool {
    var index: Int?
    for (idx, objectToCompare) in self.enumerate() {
      if let toCompare = objectToCompare as? T {
        if toCompare == object {
          index = idx
          break
        }
      }
    }
    if(index != nil) {
      self.removeAtIndex(index!)
      return true
    } else {
      return false
    }
  }
}

-4

Pude hacerlo funcionar con:

extension Array {
    mutating func removeObject<T: Equatable>(object: T) {
        var index: Int?
        for (idx, objectToCompare) in enumerate(self) {
            let to = objectToCompare as T
            if object == to {
                index = idx
            }
        }

        if(index) {
            self.removeAtIndex(index!)
        }
    }
}

La comparación if(index)no es válida
juanjo el
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.