Rimozione dall'array durante l'enumerazione in Swift?


87

Desidero enumerare un array in Swift e rimuovere determinati elementi. Mi chiedo se sia sicuro e, in caso contrario, come dovrei ottenerlo.

Attualmente, lo farei:

for (index, aString: String) in enumerate(array) {
    //Some of the strings...
    array.removeAtIndex(index)
}

Risposte:


72

In Swift 2 è abbastanza facile usare enumeratee reverse.

var a = [1,2,3,4,5,6]
for (i,num) in a.enumerate().reverse() {
    a.removeAtIndex(i)
}
print(a)

1
Funziona ma il filtro è davvero la strada da percorrere

13
@ Mayerz False. "Voglio enumerare un array in Swift e rimuovere determinati elementi." filterrestituisce un nuovo array. Non stai rimuovendo nulla dall'array. Non chiamerei nemmeno filterun'enumerazione. C'è sempre più di un modo per scuoiare un gatto.
Johnston

6
diritto, colpa mia! Non

56

Potresti considerare il filtermodo:

var theStrings = ["foo", "bar", "zxy"]

// Filter only strings that begins with "b"
theStrings = theStrings.filter { $0.hasPrefix("b") }

Il parametro di filterè solo una chiusura che accetta un'istanza di tipo array (in questo caso String) e restituisce un file Bool. Quando il risultato è truemantiene l'elemento, altrimenti l'elemento viene filtrato.


16
Renderei esplicito che filternon aggiorna l'array, ne restituisce solo uno nuovo
Antonio

Le parentesi dovrebbero essere eliminate; questa è una chiusura finale.
Jessy

@Antonio hai ragione. In effetti è per questo che l'ho pubblicato come una soluzione più sicura. Una soluzione diversa potrebbe essere presa in considerazione per array enormi.
Matteo Piombo

Hm, come dici tu restituisce un nuovo array. È possibile trasformare il filtermetodo in mutatinguno allora (poiché ho letto la mutatingparola chiave abilita invece funzioni come questa ad alterare self)?
Gee.E

@ Gee.E certamente puoi aggiungere un filtro sul posto come estensione per Arraycontrassegnarlo come mutatinge simile al codice della domanda. Considera comunque che questo potrebbe non essere sempre un vantaggio. Ad ogni modo, ogni volta che rimuovi un oggetto, il tuo array potrebbe essere riorganizzato in memoria. Quindi potrebbe essere più efficiente allocare un nuovo array e quindi effettuare una sostituzione atomica con il risultato della funzione di filtro. Il compilatore potrebbe fare ancora più ottimizzazioni, a seconda del codice.
Matteo Piombo

38

In Swift 3 e 4 , questo sarebbe:

Con i numeri, secondo la risposta di Johnston:

var a = [1,2,3,4,5,6]
for (i,num) in a.enumerated().reversed() {
   a.remove(at: i)
}
print(a)

Con stringhe come domanda dell'OP:

var b = ["a", "b", "c", "d", "e", "f"]

for (i,str) in b.enumerated().reversed()
{
    if str == "c"
    {
        b.remove(at: i)
    }
}
print(b)

Tuttavia, ora in Swift 4.2 o versioni successive, esiste anche un modo migliore e più veloce versioni consigliato da Apple nel WWDC2018:

var c = ["a", "b", "c", "d", "e", "f"]
c.removeAll(where: {$0 == "c"})
print(c)

Questo nuovo modo ha diversi vantaggi:

  1. È più veloce delle implementazioni con filter .
  2. Elimina la necessità di invertire gli array.
  3. Rimuove gli elementi sul posto e quindi aggiorna l'array originale invece di allocare e restituire un nuovo array.

cosa succede se l'oggetto è un oggetto e {$0 === Class.self}
devo

14

Quando un elemento a un certo indice viene rimosso da un array, tutti gli elementi successivi avranno la loro posizione (e indice) cambiati, perché tornano indietro di una posizione.

Quindi il modo migliore è navigare nell'array in ordine inverso, e in questo caso suggerisco di utilizzare un ciclo for tradizionale:

for var index = array.count - 1; index >= 0; --index {
    if condition {
        array.removeAtIndex(index)
    }
}

Tuttavia secondo me l'approccio migliore è usare il filtermetodo, come descritto da @perlfly nella sua risposta.


ma sfortunatamente è stato rimosso in swift 3
Sergey Brazhnik

4

No, non è sicuro modificare gli array durante l'enumarazione, il codice andrà in crash.

Se vuoi eliminare solo pochi oggetti puoi usare la filterfunzione.


3
Questo non è corretto per Swift. Gli array sono tipi di valore , quindi vengono "copiati" quando vengono passati a funzioni, assegnati a variabili o utilizzati nell'enumerazione. (Swift implementa la funzionalità di copia su scrittura per i tipi di valore, quindi la copia effettiva è ridotta al minimo). Prova quanto segue per verificare: var x = [1, 2, 3, 4, 5]; stampa (x); var i = 0; for v in x {if (v% 2 == 0) {x.remove (at: i)} else {i + = 1}}; print (x)
404compilernotrovato il

Sì, hai ragione, a condizione che tu sappia esattamente cosa stai facendo. Forse non ho espresso chiaramente la mia risposta. Avrei dovuto dire che è possibile ma non è sicuro . Non è sicuro perché stai modificando la dimensione del contenitore e se commetti un errore nel codice, la tua app andrà in crash. Swift è tutto incentrato sulla scrittura di codice sicuro che non si arresti in modo imprevisto in fase di esecuzione. Ecco perché l'utilizzo di funzioni di programmazione funzionale come filterè più sicuro . Ecco il mio stupido esempio:var y = [1, 2, 3, 4, 5]; print(y); for (index, value) in y.enumerated() { y.remove(at: index) } print(y)
Starscream

Volevo solo distinguere che è possibile modificare la raccolta enumerata in Swift, al contrario del comportamento di generazione di eccezioni di farlo durante l'iterazione attraverso NSArray con enumerazione rapida o anche i tipi di raccolta C #. Non è la modifica a generare un'eccezione qui, ma la possibilità di gestire in modo errato gli indici e uscire dai limiti (perché avevano ridotto le dimensioni). Ma sono assolutamente d'accordo con te sul fatto che di solito è più sicuro e più chiaro utilizzare il tipo di metodi di programmazione funzionale per manipolare le raccolte. Soprattutto in Swift.
404compilernot trovato il

2

Crea un array modificabile per memorizzare gli elementi da eliminare e quindi, dopo l'enumerazione, rimuovi quegli elementi dall'originale. Oppure creare una copia dell'array (immutabile), enumerarla e rimuovere gli oggetti (non per indice) dall'originale durante l'enumerazione.


2

Il tradizionale ciclo for potrebbe essere sostituito con un semplice ciclo while, utile se è necessario eseguire anche altre operazioni su ogni elemento prima della rimozione.

var index = array.count-1
while index >= 0 {

     let element = array[index]
     //any operations on element
     array.remove(at: index)

     index -= 1
}

1

Consiglio di impostare gli elementi su nil durante l'enumerazione e dopo aver completato rimuovere tutti gli elementi vuoti utilizzando il metodo arrays filter ().


1
Funziona solo se il tipo memorizzato è un opzionale. Nota anche che il filtermetodo non rimuove, ma genera un nuovo array.
Antonio

Essere d'accordo. L'ordine inverso è una soluzione migliore.
freele

0

Solo per aggiungere, se hai più array e ogni elemento nell'indice N dell'array A è correlato all'indice N dell'array B, puoi comunque utilizzare il metodo che inverte l'array enumerato (come le risposte precedenti). Ma ricorda che quando accedi ed elimini gli elementi degli altri array, non è necessario invertirli.

Like so, (one can copy and paste this on Playground)

var a = ["a", "b", "c", "d"]
var b = [1, 2, 3, 4]
var c = ["!", "@", "#", "$"]

// remove c, 3, #

for (index, ch) in a.enumerated().reversed() {
    print("CH: \(ch). INDEX: \(index) | b: \(b[index]) | c: \(c[index])")
    if ch == "c" {
        a.remove(at: index)
        b.remove(at: index)
        c.remove(at: index)
    }
}

print("-----")
print(a) // ["a", "b", "d"]
print(b) // [1, 2, 4]
print(c) // ["!", "@", "$"]
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.