2012-03-28 18 views
6

Tengo una lista que contiene algunos elementos del tipo cadena.Eliminar elementos de la lista en los índices dados

List<string> lstOriginal; 

Tengo otra lista que contiene idices que deben eliminarse de la primera lista.

List<int> lstIndices; 

Había tratado de hacer el trabajo con RemoveAt() método,

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

pero se bloquea y me dijeron que "índice está fuera de rango."

+0

¿De dónde viene la lista de índices? porque elimina índices que no están en la lista – Frederiek

+3

Cuando elimina un artículo, cambia los índices de los elementos que vienen después. Si tiene los índices 1 y 3, cuando elimina uno, el índice 3 ya no apunta al mismo objeto. Ahora podría estar fuera de límites, por lo que obtienes esa excepción. – SomeWritesReserved

+0

Esto se bloquea porque cuando elimina el primer elemento de la lista, todos los índices cambian en consecuencia. – bporter

Respuesta

26

Debe ordenar los índices que desea devolver de mayor a menor para evitar eliminar algo en el índice incorrecto.

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

Aquí es por qué: digamos que tiene una lista de cinco elementos, y desea eliminar elementos en índices 2 y 4. Si elimina el artículo al 2 primero, el artículo que estaba en el índice 4 estaría en el índice 3, y el índice 4 ya no estaría en la lista (causando su excepción). Si retrocede, todos los índices estarán allí hasta el momento en que esté listo para eliminar el artículo correspondiente.

+1

Esto funcionaría pero sería más eficiente ir en un bucle for sobre los índices, y eliminar index-i – SimpleVar

+5

@YoryeNathan que asume que los índices en la lista ya están en orden ascendente. Si no están ordenadas, necesitas hacer algún tipo de ordenamiento. – Servy

+0

@Servy Si los índices para eliminar no están ordenados, no funcionará de otra manera. Debe ordenarlos de ambas maneras, a menos que sepa que están en orden. SI YA ESTÁN en orden descendente, entonces un bucle simple funcionará, pero entonces no tendría este problema así que este no es el caso anywyas. – SimpleVar

5

La razón por la que esto ocurre es porque cuando elimina un elemento de la lista, el índice de cada elemento disminuye efectivamente en uno, por lo que si los elimina en orden de índice creciente y algunos elementos cerca del final del original list was to remove, esos índices ahora no son válidos porque la lista se vuelve más corta a medida que se eliminan los elementos anteriores.

La solución más fácil es ordenar su lista de índice en orden decreciente (el índice más alto primero) y luego iterar a través de eso.

-2
lstIndices.OrderByDescending(p => p).ToList().ForEach(p => lstOriginal.RemoveAt((int)p)); 

Como una nota al margen, en las declaraciones de foreach, es mejor no modificar el Ienumerable en el que foreach se está ejecutando. El error fuera de rango es probablemente el resultado de esta situación.

+0

Esto es incorrecto: el error fuera de rango es el resultado del hecho de que quitar elementos cambia el índice de otros elementos, y esta solución no lo soluciona. Por ejemplo, suponga que desea eliminar los elementos en los índices 5 y 6. Puede llamar a 'RemoveAt (6)' y a 'RemoveAt (5)', en ese orden, o puede llamar a 'RemoveAt (5)' y ' RemoveAt (5) 'en ese orden. – phoog

+0

@phoog puede que tenga razón en este contexto, por otro lado, es mejor no modificar el enumerable en un bucle foreach en el que se enumera la misma lista. – daryal

+0

He actualizado. – daryal

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

Esto supone que la lista está ordenada en orden ascendente. En ese caso, sería más eficiente hacer 'for (int i = indices.Count - 1; i> = 0; i -)'. ¿Porqué es eso? Porque los elementos * después * del índice deben copiarse en la posición anterior. Hay menos copias si comienza al final que si comienza desde el principio, porque si comienza desde el principio, también está copiando elementos que está por eliminar. – phoog

+0

esta es una buena respuesta. Pero la lista debe ser ordenada. Gracias Yorye Nathan – meorfi

+0

La reasignación no será diferente entre estos enfoques, pero su ciclo es de hecho más legible. Y, por supuesto, haces un cálculo atómico menos, por lo que el perfeccionismo te llama a ganar. – 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

¿Cómo estás poblar la lista de índices? Existe un método RemoveAll mucho más eficiente que es posible que puedas usar. Por ejemplo, en lugar de esto:

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 

usted puede hacer esto:

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

Esto reduce al mínimo la copia de artículos a nuevas posiciones en la matriz; cada elemento se copia solo una vez.

También es posible usar una conversión de grupo Método en el ejemplo anterior, en lugar de un lambda:

data.RemoveAll(SomeFunction); 
0

eliminación Mi en el lugar de los índices dados como método de extensión práctica. Copia todos los elementos solo una vez, por lo que es mucho más eficaz si se elimina una gran cantidad de indicios.

También arroja ArgumentOutOfRangeException en caso de que el índice para eliminar esté fuera de los límites.

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); 
    } 
} 
Cuestiones relacionadas