Swap two items in List<T>

C#LinqSwap

C# Problem Overview


Is there a LINQ way to swap the position of two items inside a List<T>?

C# Solutions


Solution 1 - C#

Check the answer from Marc from https://stackoverflow.com/questions/552731/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;
}

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

Solution 2 - C#

Maybe someone will think of a clever way to do this, but you shouldn't. Swapping two items in a list is inherently side-effect laden but LINQ operations should be side-effect free. Thus, 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;
    }
}

Solution 3 - C#

List<T> has a Reverse() method, however it only reverses the order of two (or more) consecutive items.

your_list.Reverse(index, 2);

Where the second parameter 2 indicates we are reversing the order of 2 items, starting with the item at the given index.

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

Solution 4 - C#

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

In the other "linqify" answers, the (input) list is modified and returned, but this action brakes that rule. If would be weird if you have a list with unsorted items, do a LINQ "OrderBy"-operation and than discover that the input list is also sorted (just like the result). This is not allowed to happen!

So... how do we do this?

My first thought was just to restore the collection after it was finished iterating. But this is a dirty solution, 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 does modify the input list, even if it restores it to the original state. This could cause several problems:

  1. The list could be readonly which will throw an exception.
  2. If the list is shared by multiple threads, the list will change for the other threads during the duration of this function.
  3. If an exception occurs during the iteration, the list will not be restored. (This could be resolved to write an try-finally inside the Swap-function, and put the restore-code inside the finally-block).

There is a better (and shorter) solution: just make a copy of the original list. (This also makes it possible to use an IEnumerable as a parameter, instead of an 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 disadvantage of this solution is that it copies the entire list which will consume memory and that makes the solution rather slow.

You might 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 input parameter to be 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 also makes a copy of (a subset of) the source. So worst case scenario, it is as slow and memory consuming as function Swap2.

Solution 5 - C#

With new versions of .net you can do

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

Solution 6 - C#

If order matters, you should keep a property on the "T" objects in your list that denotes sequence. In order to swap them, just swap the value of that property, and then use that in the .Sort(comparison with sequence property)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTony The LionView Question on Stackoverflow
Solution 1 - C#Jan JongboomView Answer on Stackoverflow
Solution 2 - C#jasonView Answer on Stackoverflow
Solution 3 - C#user1920925View Answer on Stackoverflow
Solution 4 - C#Martin MulderView Answer on Stackoverflow
Solution 5 - C#GMGView Answer on Stackoverflow
Solution 6 - C#CaffGeekView Answer on Stackoverflow