2010-01-19 26 views
51

¿Hay alguna forma LINQ de cambiar la posición de dos elementos dentro de un list<T>?Intercambiar dos elementos en la lista <T>

+42

¿Por qué es importante por qué quiere hacer esto? Las personas que buscan en Google "artículos de la lista de intercambio C#" van a querer una respuesta directa a esta pregunta específica. –

+3

@DanielMacias Esto es tan cierto. Estas respuestas son como '¿pero por qué estás haciendo esto?' son tan molestos Creo que uno debe proporcionar al menos una respuesta viable antes de intentar argumentar los por qué. – julealgon

+0

¿Por qué quieres usar LINQ para hacer esto? si LINQ específico, ¿por qué no cambiar el título para agregar LINQ – ina

Respuesta

77

Compruebe la respuesta de Marc de C#: Good/best implementation of Swap method.

public static void Swap<T>(IList<T> list, int indexA, int indexB) 
{ 
    T tmp = list[indexA]; 
    list[indexA] = list[indexB]; 
    list[indexB] = tmp; 
} 

que puede ser LINQ-i-ficado como

public static IList<T> Swap<T>(this IList<T> list, int indexA, int indexB) 
{ 
    T tmp = list[indexA]; 
    list[indexA] = list[indexB]; 
    list[indexB] = tmp; 
    return list; 
} 

var lst = new List<int>() { 8, 3, 2, 4 }; 
lst = lst.Swap(1, 2); 
+1

¿Razón de abandono? –

+13

Esto no es LINQified. – jason

+6

¿Por qué este método de extensión necesita devolver una lista? Estás modificando la lista en el lugar. – vargonian

29

Tal vez alguien piense en una forma inteligente de hacerlo, pero no debería. El intercambio de dos elementos en una lista es inherentemente cargado con efectos secundarios, pero las operaciones LINQ deben ser libres de efectos secundarios. Por lo tanto, sólo tiene que utilizar un simple método de extensión:

static class IListExtensions { 
    public static void Swap<T>(
     this IList<T> list, 
     int firstIndex, 
     int secondIndex 
    ) { 
     Contract.Requires(list != null); 
     Contract.Requires(firstIndex >= 0 && firstIndex < list.Count); 
     Contract.Requires(secondIndex >= 0 && secondIndex < list.Count); 
     if (firstIndex == secondIndex) { 
      return; 
     } 
     T temp = list[firstIndex]; 
     list[firstIndex] = list[secondIndex]; 
     list[secondIndex] = temp; 
    } 
} 
+0

efectos secundarios? ¿Puedes elaborar? –

+0

+1 para la comprobación de argumentos y un método de extensión – plinth

+9

Por efectos secundarios, quiere decir que alteran la lista y posiblemente los elementos de la lista, en lugar de solo consultar datos que no modificarían nada. – saret

0

Si el orden es importante, usted debe tener una propiedad de los objetos "T" en su lista que denota secuencia. Con el fin de intercambiarlos, sólo cambio el valor de esa propiedad y, a continuación, utilizar que en el .Sort (comparación con la secuencia de la propiedad)

+0

Eso es si puede aceptar conceptualmente que su T tiene un "orden" inherente, pero no si desea que se clasifique de forma arbitraria sin "orden" inherente, como en una UI. –

+0

@DaveVandenEynde, soy un programador novato, así que, por favor, discúlpeme si esta no es una gran pregunta: ¿cuál es el significado de intercambiar elementos en una lista si la situación no implica un concepto de orden? –

+0

@ MathieuK.well lo que quise decir es que esta respuesta propone que el orden se deriva de una propiedad en los objetos en los que la secuencia puede * ordenarse *. Ese no suele ser el caso. –

8

No hay Swap-método existente, por lo que tiene que crear una. Por supuesto, puede linqify, pero eso tiene que hacerse con una reglas (¿no escrita?) En mente: ¡las operaciones LINQ no cambian los parámetros de entrada!

En las otras respuestas "linqify", la lista (entrada) se modifica y se devuelve, pero esta acción frena esa regla. Si sería extraño si tiene una lista con elementos no ordenados, haga una operación LINQ "OrderBy" y descubra que la lista de entrada también está ordenada (igual que el resultado). ¡Esto no está permitido!

Entonces ... ¿cómo hacemos esto?

Mi primer pensamiento fue simplemente restaurar la colección después de que se terminó de iterar. Pero esta es una solución sucia, por lo que no lo use:

static public IEnumerable<T> Swap1<T>(this IList<T> source, int index1, int index2) 
{ 
    // Parameter checking is skipped in this example. 

    // Swap the items. 
    T temp = source[index1]; 
    source[index1] = source[index2]; 
    source[index2] = temp; 

    // Return the items in the new order. 
    foreach (T item in source) 
     yield return item; 

    // Restore the collection. 
    source[index2] = source[index1]; 
    source[index1] = temp; 
} 

Esta solución está sucia porque no modificar la lista de entrada, incluso si se restaura a su estado original. Esto podría causar varios problemas:

  1. La lista podría ser de solo lectura, lo que generará una excepción.
  2. Si la lista es compartida por varios hilos, la lista cambiará para los otros hilos durante la duración de esta función.
  3. Si se produce una excepción durante la iteración, la lista no se restaurará. (Esto podría resolverse para escribir una try-finally dentro de la función Swap, y colocar el código de restauración dentro del bloque finally).

Existe una solución mejor (y más corta): simplemente haga una copia de la lista original. (Esto también hace que sea posible utilizar un IEnumerable como un parámetro, en lugar de un IList):

static public IEnumerable<T> Swap2<T>(this IList<T> source, int index1, int index2) 
{ 
    // Parameter checking is skipped in this example. 

    // If nothing needs to be swapped, just return the original collection. 
    if (index1 == index2) 
     return source; 

    // Make a copy. 
    List<T> copy = source.ToList(); 

    // Swap the items. 
    T temp = copy[index1]; 
    copy[index1] = copy[index2]; 
    copy[index2] = temp; 

    // Return the copy with the swapped items. 
    return copy; 
} 

Una desventaja de esta solución es que se copia toda la lista que consumirá memoria y que hace que la solución más bien lento .

usted podría considerar la siguiente solución:

static public IEnumerable<T> Swap3<T>(this IList<T> source, int index1, int index2) 
{ 
    // Parameter checking is skipped in this example. 
    // It is assumed that index1 < index2. Otherwise a check should be build in and both indexes should be swapped. 

    using (IEnumerator<T> e = source.GetEnumerator()) 
    { 
     // Iterate to the first index. 
     for (int i = 0; i < index1; i++) 
      yield return source[i]; 

     // Return the item at the second index. 
     yield return source[index2]; 

     if (index1 != index2) 
     { 
      // Return the items between the first and second index. 
      for (int i = index1 + 1; i < index2; i++) 
       yield return source[i]; 

      // Return the item at the first index. 
      yield return source[index1]; 
     } 

     // Return the remaining items. 
     for (int i = index2 + 1; i < source.Count; i++) 
      yield return source[i]; 
    } 
} 

Y si quieres parámetro de entrada para ser IEnumerable:

static public IEnumerable<T> Swap4<T>(this IEnumerable<T> source, int index1, int index2) 
{ 
    // Parameter checking is skipped in this example. 
    // It is assumed that index1 < index2. Otherwise a check should be build in and both indexes should be swapped. 

    using(IEnumerator<T> e = source.GetEnumerator()) 
    { 
     // Iterate to the first index. 
     for(int i = 0; i < index1; i++) 
     { 
      if (!e.MoveNext()) 
       yield break; 
      yield return e.Current; 
     } 

     if (index1 != index2) 
     { 
      // Remember the item at the first position. 
      if (!e.MoveNext()) 
       yield break; 
      T rememberedItem = e.Current; 

      // Store the items between the first and second index in a temporary list. 
      List<T> subset = new List<T>(index2 - index1 - 1); 
      for (int i = index1 + 1; i < index2; i++) 
      { 
       if (!e.MoveNext()) 
        break; 
       subset.Add(e.Current); 
      } 

      // Return the item at the second index. 
      if (e.MoveNext()) 
       yield return e.Current; 

      // Return the items in the subset. 
      foreach (T item in subset) 
       yield return item; 

      // Return the first (remembered) item. 
      yield return rememberedItem; 
     } 

     // Return the remaining items in the list. 
     while (e.MoveNext()) 
      yield return e.Current; 
    } 
} 

Swap4 también hace una copia de (un subconjunto de) la fuente. En el peor de los casos, es tan lento y consume tanto memoria como la función Swap2.

+0

Esfuerzo limpio de LINQ. – Kryptos

Cuestiones relacionadas