2012-03-28 17 views
6

Ho una lista che contiene alcuni elementi di tipo stringa.Rimuovi elementi di lista a determinati indici

List<string> lstOriginal; 

Ho un'altra lista che contiene gli identi che dovrebbero essere rimossi dal primo elenco.

List<int> lstIndices; 

avevo cercato di fare il lavoro con RemoveAt() metodo,

foreach(int indice in lstIndices) 
{ 
    lstOriginal.RemoveAt(indice); 
} 

ma si blocca e mi ha detto che "indice è fuori portata."

+0

da dove viene l'elenco degli indici? perché si eliminano gli indici non presenti nell'elenco – Frederiek

+3

Quando si rimuove un elemento, vengono modificati gli indici degli elementi che vengono dopo di esso. Se hai indici 1 e 3, quando ne rimuovi uno, l'indice 3 non punta più sullo stesso oggetto. Ora potrebbe essere fuori dai limiti, ed è per questo che ottieni questa eccezione. – SomeWritesReserved

+0

Arresto anomalo perché quando si rimuove il primo elemento dall'elenco, tutti gli indici cambiano di conseguenza. – bporter

risposta

26

È necessario ordinare gli indici che si desidera tornare dal più grande al più piccolo per evitare di rimuovere qualcosa nell'indice sbagliato.

foreach(int indice in lstIndices.OrderByDescending(v => v)) 
{ 
    lstOriginal.RemoveAt(indice); 
} 

Ecco perché: diciamo che hanno una lista di cinque elementi, e si desideri rimuovere gli elementi a indici 2 e 4. Se si rimuove l'elemento allo 2 per primo, l'elemento che era all'indice 4 si troverebbe nell'indice 3 e l'indice 4 non sarebbe più incluso nell'elenco (causando l'eccezione). Se vai indietro, tutti gli indici saranno lì fino al momento in cui sarai pronto a rimuovere l'oggetto corrispondente.

+1

Ciò funzionerebbe ma sarebbe mo efficiente andare in ciclo continuo sugli indici e rimuovere index-i – SimpleVar

+5

@YoryeNathan che presuppone che gli indici nell'elenco siano già in ordine crescente. Se non sono ordinati, devi fare un qualche tipo di ordinamento. – Servy

+0

@Servy Se gli indici da rimuovere non sono ordinati, non funzioneranno in nessun altro modo. Deve ordinarli in entrambi i modi, a meno che non sappia che sono in ordine. SE SONO GIÀ in ordine decrescente, allora farà un semplice ciclo, ma poi non avrebbe questo problema, quindi non è questo il caso. – SimpleVar

5

Il motivo per cui questo accade è che quando si rimuove un elemento dall'elenco, l'indice di ciascun elemento dopo diminuisce effettivamente di uno, quindi se li si rimuove in ordine crescente e alcuni elementi vicino alla fine dell'originale la lista doveva essere rimossa, quegli indici ora non sono validi perché la lista diventa più breve man mano che gli articoli precedenti vengono rimossi.

La soluzione più semplice consiste nell'ordinare l'elenco di indici in ordine decrescente (indice più alto prima) e quindi scorrere iterato su quello.

-2

Come nota a margine, nelle istruzioni foreach, è meglio non modificare l'Ienumerable su cui foreach è in esecuzione. L'errore fuori intervallo è probabilmente il risultato di questa situazione.

+0

Non è corretto: l'errore fuori intervallo è il risultato del fatto che la rimozione di elementi modifica l'indice di altri elementi e questa soluzione non risolve il problema. Ad esempio, supponiamo di volere rimuovere gli elementi agli indici 5 e 6. Potresti chiamare "RemoveAt (6)" e "RemoveAt (5)", in questo ordine, oppure puoi chiamare "RemoveAt (5)" e " RemoveAt (5) 'in questo ordine. – phoog

+0

@phoog potresti avere ragione su questo contesto, d'altra parte, è meglio non modificare l'enumerabile in un ciclo foreach su cui viene elencata la stessa lista. – daryal

+0

Ho aggiornato. – daryal

4
for (int i = 0; i < indices.Count; i++) 
{ 
    items.RemoveAt(indices[i] - i); 
} 
+1

Ciò presuppone che l'elenco sia ordinato in ordine crescente. In tal caso, sarebbe più efficiente fare 'for (int i = indices.Count - 1; i> = 0; i -)'. Perché? Perché gli articoli * dopo * l'indice devono essere copiati nella posizione precedente. C'è meno copia se inizi alla fine piuttosto che se inizi all'inizio, perché se inizi all'inizio copi anche gli elementi che stai per rimuovere. – phoog

+0

questa è una buona risposta. Ma l'elenco dovrebbe essere ordinato. Grazie Yorye Nathan – meorfi

+0

La riallocazione non sarà diversa tra questi approcci, ma il tuo ciclo è effettivamente più leggibile. E, naturalmente, fai un calcolo in meno atomico, quindi il perfezionismo ti chiama a vincere. – SimpleVar

1
 var array = lstOriginal.ConvertAll(item => new int?(item)).ToArray(); 
     lstIndices.ForEach(index => array[index] = null); 
     lstOriginal = array.Where(item => item.HasValue).Select(item => item.Value).ToList(); 
5

Come stai popolando la lista degli indici? C'è un metodo RemoveAll molto più efficiente che potresti essere in grado di utilizzare. Per esempio, invece di questo:

var indices = new List<int>(); 
int index = 0; 
foreach (var item in data) 
    if (SomeFunction(data)) 
     indices.Add(index++); 

//then some logic to remove the items 

Si potrebbe fare questo:

data.RemoveAll(item => SomeFunction(item)); 

Questo riduce al minimo la copia di elementi a nuove posizioni nella matrice; ogni elemento viene copiato solo una volta.

Si potrebbe anche usare una conversione metodo di gruppo nell'esempio di cui sopra, al posto di un lambda:

data.RemoveAll(SomeFunction); 
0

cancellazione My sul posto di determinati indici come metodo di estensione a portata di mano. Copia tutti gli elementi una sola volta, quindi è molto più performante se è necessario rimuovere una grande quantità di informazioni.

Getta anche ArgumentOutOfRangeException nel caso in cui l'indice da rimuovere sia fuori limite.

public static class ListExtensions 
{ 
    public static void RemoveAllIndices<T>(this List<T> list, IEnumerable<int> indices) 
    { 
     //do not remove Distinct() call here, it's important 
     var indicesOrdered = indices.Distinct().ToArray(); 
     if(indicesOrdered.Length == 0) 
      return; 

     Array.Sort(indicesOrdered); 

     if (indicesOrdered[0] < 0 || indicesOrdered[indicesOrdered.Length - 1] >= list.Count) 
      throw new ArgumentOutOfRangeException(); 

     int indexToRemove = 0; 
     int newIdx = 0; 

     for (int originalIdx = 0; originalIdx < list.Count; originalIdx++) 
     { 
      if(indexToRemove < indicesOrdered.Length && indicesOrdered[indexToRemove] == originalIdx) 
      { 
       indexToRemove++; 
      } 
      else 
      { 
       list[newIdx++] = list[originalIdx]; 
      } 
     } 

     list.RemoveRange(newIdx, list.Count - newIdx); 
    } 
} 
Problemi correlati