Estensione della matrice per rimuovere l'oggetto per valore


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

Tuttavia, viene visualizzato un errore var index = find(self, object)

'T' non è convertibile in 'T'

Ho anche provato con questa firma del metodo:, func removeObject(object: AnyObject)tuttavia, ottengo lo stesso errore:

'AnyObject' non è convertibile in 'T'

Qual è il modo corretto per farlo?


Prova a rimuovere il T wheredalla tua dichiarazione del metodo. Quindi solo func removeObject<T: Equatable>. Questa domanda è correlata: stackoverflow.com/questions/24091046/...
ahruss

Risposte:


165

A partire da Swift 2 , ciò può essere ottenuto con un metodo di estensione del protocollo . removeObject()è definito come un metodo su tutti i tipi conformi RangeReplaceableCollectionType(in particolare su Array) se gli elementi della raccolta sono 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)
        }
    }
}

Esempio:

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

Aggiornamento per Swift 2 / Xcode 7 beta 2: come notato da Airspeed Velocity nei commenti, è ora possibile scrivere un metodo su un tipo generico che è più restrittivo sul modello, quindi il metodo potrebbe ora essere definito come un'estensione di Array:

extension Array where Element : Equatable {

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

L'estensione del protocollo ha ancora il vantaggio di essere applicabile a un set più ampio di tipi.

Aggiornamento per 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
Perfetto, devi amare Swift (2). Mi piace molto come nel tempo più cose diventano possibili e le cose vengono semplificate
Kametrixom,

1
Un buon punto, in molti modi il fatto che la risposta sia ancora tecnicamente corretta, ma non più idiomatica, è anche peggio - le persone verranno, leggeranno la risposta, pensano che una funzione gratuita sia il modo giusto per risolverlo poiché è una risposta molto valutata . Scenario piuttosto brutto. Pubblica su meta.
Velocità

1
@AirspeedVelocity: Wow, mi sono perso. È coperto nelle note di rilascio?
Martin R,

1
Se si desidera la stessa funzionalità di ObjC (ovvero rimuove tutti gli oggetti corrispondenti anziché solo il 1 °), è possibile modificare "if" in "while"
powertoold

2
La versione di Swift 3 è fantastica, ma rinominerei leggermente la sua dichiarazione remove(object: Element)in modo da rispettare le linee guida di progettazione dell'API Swift ed evitare la verbosità. Ho inviato una modifica che riflette questo.
codice swift

66

Non è possibile scrivere un metodo su un tipo generico più restrittivo sul modello.

NOTA : come di Swift 2.0, è ora possibile scrivere metodi che sono più restrittive sul modello. Se hai aggiornato il tuo codice a 2.0, vedi altre risposte più in basso per nuove opzioni per implementarlo usando le estensioni.

Il motivo per cui si ottiene l'errore 'T' is not convertible to 'T'è che si sta effettivamente definendo una nuova T nel metodo che non è affatto correlata al T. originale Se si desidera utilizzare T nel metodo, è possibile farlo senza specificarlo sul metodo.

Il motivo per cui si ottiene il secondo errore 'AnyObject' is not convertible to 'T'è che tutti i possibili valori per T non sono tutte le classi. Perché un'istanza sia convertita in AnyObject, deve essere una classe (non può essere una struttura, un enum, ecc.).

La soluzione migliore è renderlo una funzione che accetta l'array come argomento:

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

O invece di modificare l'array originale, puoi rendere il tuo metodo più sicuro e riutilizzabile tramite il thread restituendo una copia:

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

In alternativa che non consiglio, puoi fare in modo che il tuo metodo fallisca silenziosamente se il tipo memorizzato nell'array non può essere convertito nel modello dei metodi (che è equabile). (Per chiarezza, sto usando U invece di T per il modello del metodo):

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]

Modifica Per superare il fallimento silenzioso puoi restituire il successo come 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

Controlla la mia risposta qui: stackoverflow.com/a/24939242/458960 Perché sono in grado di farlo in questo modo e non usando il findmetodo?
Pupazzo di neve,

Il metodo è suscettibile agli arresti anomali del runtime. Con la mia funzione, il compilatore eviterà che ciò accada affatto.
Drewag,

1
@Isuru Questo metodo funziona con qualsiasi oggetto che implementa il Equatableprotocollo. UIView lo fa sì, funzionerà con UIViews
drewag

4
Wow, scrivendo un ciclo for per rimuovere un elemento, è tornato agli anni '90!
Zorayr,

5
Nell'ultima rapida. enumerate(self)devo aggiustare aself.enumerate()
TomSawyer il

29

brevemente e concisamente:

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

2
Questo è fico. Ovviamente si può fare anche senza inout. Anche con l' inoutintatto, si potrebbe usare, array = array.filter() { $0 != object }penso.
Dan Rosenstark,

11
Essere consapevoli dell'utilizzo di un indice non scartato forzato, che può essere nullo. Passare a "if let ind = index {array.removeAtIndex (ind)}"
HotJard

17

Dopo aver letto tutto quanto sopra, a mio avviso la risposta migliore è:

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

Campione:

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

Estensione dell'array Swift 2 (xcode 7b4):

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

Campione:

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

Aggiornamento di Swift 3.1

Sono tornato a questo ora che Swift 3.1 è uscito. Di seguito è un'estensione che fornisce varianti esaustive, veloci, mutanti e di creazione.

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

Campioni:

// 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"]

questo non restituisce un'istanza completamente nuova dell'array?
pxpgraphics,

Sì. È uno stile più funzionale. YMMV.
Dicembre

Tendo ad essere d'accordo con lo stile funzionale, tranne, in questo caso, quando la filterfunzione gestisce già quella funzionalità per te. Questo sembra duplicare la funzionalità. Ma una buona risposta comunque:]
pxpgraphics

13

Con le estensioni di protocollo puoi farlo,

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

Stessa funzionalità per le classi,

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

Ma se una classe implementa Equatable diventa ambigua e il compilatore genera un errore.


1
Mi sta venendoBinary operator '===' cannot be applied to two elements of type '_' and 'Element'
scarpa

6

Con l'utilizzo delle estensioni di protocollo in 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

che dire di utilizzare il filtro? il seguente funziona abbastanza bene anche con [AnyObject].

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

}

2

Esiste un'altra possibilità di rimuovere un elemento da un array senza che sia possibile un utilizzo non sicuro, poiché il tipo generico dell'oggetto da rimuovere non può essere uguale al tipo di array. L'uso degli optionals non è inoltre il modo perfetto di procedere poiché sono molto lenti. È quindi possibile utilizzare una chiusura come quella già utilizzata durante l'ordinamento di un array, ad esempio.

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

Quando estendi la Arrayclasse con questa funzione puoi rimuovere gli elementi nel modo seguente:

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

Tuttavia potresti anche rimuovere un elemento solo se ha lo stesso indirizzo di memoria ( AnyObjectovviamente solo per le classi conformi al protocollo):

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'

La cosa buona è che puoi specificare il parametro da confrontare. Ad esempio, quando si dispone di una matrice di matrici, è possibile specificare la chiusura dell'uguaglianza { $0.count == $1.count }e la prima matrice della stessa dimensione di quella da rimuovere viene rimossa dalla matrice.

È anche possibile abbreviare la chiamata della funzione disponendo della funzione as mutating func removeFirst(equality: (Element) -> Bool) -> Bool, quindi sostituire if-assessment con equality(item)e chiamare la funzione array.removeFirst({ $0 == "Banana" })ad esempio.


Poiché ==è una funzione, puoi anche chiamarla così per qualsiasi tipo che implementa ==(come String, Int ecc.):array.removeFirst("Banana", equality:==)
Aviel Gross

@AvielGross, questa è una novità di Swift 2, penso: sentiti libero di modificare la risposta di conseguenza se vuoi
borchero,

2

Non è necessario estendere:

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

Utilizzando al indexOfposto di un 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

Forse non ho capito la domanda.

Perché non dovrebbe funzionare?

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

Alla fine ho finito con il seguente codice.

extension Array where Element: Equatable {

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

}

0

Sono riuscito a rimuovere un [String:AnyObject]da un array [[String:AnyObject]]implementando un conteggio al di fuori di un ciclo for per rappresentare l'indice poiché .finde .filternon sono compatibili 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

Implementazione in 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

Sono stato in grado di farlo funzionare 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!)
        }
    }
}

Il confronto if(index)non è valido
juanjo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.