Scambia due elementi nella lista

Esiste un modo LINQ per scambiare la posizione di due elementi all’interno di una list ?

Controlla la risposta di Marc da C #: buona / migliore implementazione del metodo Swap .

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

che può essere linq-i-fied come

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

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

Forse qualcuno penserà a un modo intelligente per farlo, ma non dovresti. Lo scambio di due elementi in un elenco è intrinsecamente carico di effetti collaterali, ma le operazioni LINQ dovrebbero essere prive di effetti collaterali. Quindi, basta usare un semplice metodo di estensione:

 static class IListExtensions { public static void Swap( this IList 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; } } 

Non esiste un metodo di swap esistente, quindi devi crearne uno tu stesso. Ovviamente è ansible eseguirne la linearizzazione, ma ciò deve essere fatto con una sola regola (non scritta?): Le operazioni LINQ non cambiano i parametri di input!

Nelle altre risposte “linqify”, l’elenco (input) viene modificato e restituito, ma questa azione frena tale regola. Se fosse strano se si ha una lista con articoli non ordinati, eseguire un’operazione LINQ “OrderBy” e scoprire che anche l’elenco di input è ordinato (proprio come il risultato). Questo non è permesso che accada!

Quindi … come lo facciamo?

Il mio primo pensiero è stato quello di ripristinare la raccolta dopo che è stata terminata l’iterazione. Ma questa è una soluzione sporca , quindi non usarla:

 static public IEnumerable Swap1(this IList 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; } 

Questa soluzione è sporca perché modifica l’elenco di input, anche se ripristina lo stato originale. Ciò potrebbe causare diversi problemi:

  1. L’elenco potrebbe essere di sola lettura che genererà un’eccezione.
  2. Se l’elenco è condiviso da più thread, l’elenco cambierà per gli altri thread durante la durata di questa funzione.
  3. Se si verifica un’eccezione durante l’iterazione, l’elenco non verrà ripristinato. (Questo potrebbe essere risolto per scrivere un try-finally all’interno della funzione Swap, e inserire il codice di ripristino all’interno del blocco finally).

C’è una soluzione migliore (e più breve): basta fare una copia della lista originale. (Ciò consente anche di utilizzare un object IEnumerable come parametro, anziché un IList):

 static public IEnumerable Swap2(this IList 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 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; } 

Uno svantaggio di questa soluzione è che copia l’intera lista che consuma memoria e che rende la soluzione piuttosto lenta.

Potresti prendere in considerazione la seguente soluzione:

 static public IEnumerable Swap3(this IList 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 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]; } } 

E se vuoi inserire parametri per essere IEnumerable:

 static public IEnumerable Swap4(this IEnumerable 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 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 subset = new List(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 crea anche una copia di (un sottoinsieme di) la fonte. Quindi, nel peggiore dei casi, è lento e consuma la memoria come funzione Swap2.

L’elenco ha il metodo inverso.

 your_list.Reverse(i, 2) // will swap elements with indexs i, i + 1. 

Fonte: https://msdn.microsoft.com/en-us/library/hf2ay11y(v=vs.110).aspx

Se l’ordine è importante, dovresti mantenere una proprietà sugli oggetti “T” nella tua lista che denota la sequenza. Per poterli scambiare, basta scambiare il valore di quella proprietà e quindi usarlo in .Sort ( confronto con la proprietà sequence )