Change the two items in the <T> list

Is there a LINQ way to replace the position of two elements inside list<T> ?

+50
c # swap linq
Jan 19
source share
5 answers

Check out the answer from Marc from C #: Good / best implementation of the 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; } 

which can be linq-i-fied like

 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); 
+74
19
source share

Maybe someone will think of a smart way to do this, but not worth it. Switching two items in a list is inherently a side effect, but LINQ operations should be free of side effects. So just use a simple extension method:

 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; } } 
+29
Jan 19 '10 at 14:48
source share

There is no existing Swap method, so you have to create it yourself. Of course, you can linqify it, but it needs to be done with one (unwritten?) Rules: LINQ operations do not change the input parameters!

In other answers of "linqify" the list (input) is changed and returned, but this action slows down this rule. If it would be strange if you have a list with unsorted items, do a LINQ "OrderBy" operation and find that the input list is also sorted (exactly the same as the result). This is not allowed!

So ... how do we do this?

My first thought was to restore the collection after it completed the iteration. But this solution is dirty , so do not use it:

 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; } 

This solution is dirty because it makes a change to the input list, even if it restores it to its original state. This can cause a number of problems:

  • The list may be read-only, which throws an exception.
  • If the list is shared by multiple threads, the list will change for other threads over the duration of this function.
  • If an exception occurs during the iteration, the list will not be restored. (This could be decided to write a try-finally inside the Swap function and put the recovery code inside the finally block).

There is a better (and shorter) solution: just make a copy of the original list. (This also allows you to use IEnumerable as a parameter instead of 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; } 

One of the drawbacks of this solution is that it copies the entire list that will consume memory, and this makes the solution rather slow.

You may consider the following solution:

 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]; } } 

And if you want to enter the IEnumerable parameter:

 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 also creates a copy (subset) of the source. In the worst case, it is slow and consumed memory, like a Swap2 function.

+7
Apr 21 '13 at 21:38
source share

If order matters, you must save the property on the T objects in your list, which denotes a sequence. To change them, just change the value of this property, and then use it in .Sort ( comparison with the sequence property )

0
Jan 19
source share

The list has an inverse method. your_list.Reverse (i, 2) will change items with indices i, i + 1. https://msdn.microsoft.com/en-us/library/hf2ay11y(v=vs.110).aspx

-one
Sep 25 '17 at 14:21
source share



All Articles