Swap two items in List<T>
Asked Answered
G

6

101

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

Gerdes answered 19/1, 2010 at 14:46 Comment(3)
Why does it matter why he wants to do this. People who are googling for "swap list items c#" are going to want a straight answer to this specific question.Limbourg
@DanielMacias This is so true. These answers that are like 'but why are you doing this?' are so annoying. I think that one should provide at least a viable answer before trying to argue the whys.Thou
why do you want to use LINQ to do this? if LINQ specific why not change title to add LINQMyronmyrrh
T
137

Check the answer from Marc from 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);
Thibodeau answered 19/1, 2010 at 14:54 Comment(5)
Why does this extension method need to return a list? You're modifying the list in-place.Flimsy
Then you can chain the methods.Thibodeau
don't forget to make the extension method public if you're tyring to use this in Unity3D! (Unity can't find it if you don't)Whaleboat
@col000r: Didn't every method must be public to be acessible outside the class?Sollie
Warning: The "LINQified" version still alters the original List.Charlot
P
33

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;
    }
}
Petras answered 19/1, 2010 at 14:48 Comment(4)
Side effects? Can you elaborate?Gerdes
By Side Effects he mean they alter the list and possibly the items in the list - as opposed to just querying data which would not modify anythingJailer
does "T temp = list[firstIndex];" create a deep-copy of the object in the list?Bower
@PaulMcCarthy No, it creates a new pointer to the original object, no copying at all.Tacnode
S
22

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

Sapwood answered 25/9, 2017 at 14:21 Comment(2)
The .Reverse() function is not Linq and not called Swap, but it seems like the intent of the question is to find a "built in" function that performs a swap. This is the only answer provides that.Burrows
But only useful to swap adjacent elements, so not really an answer.Tacnode
C
21

Starting with C# 7 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;
}
Corrupt answered 12/11, 2021 at 9:14 Comment(1)
This should be the newest answer and my IDE(rider) gives me hint to do this.Backdrop
A
10

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.

Aquiculture answered 21/4, 2013 at 21:38 Comment(0)
S
0

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)

Samuelsamuela answered 19/1, 2010 at 14:51 Comment(4)
That's if you can conceptually agree that your T has an inherent "order", but not if you want it to be sorted in an arbitrary fashion without inherent "order", such as in a UI.Buckle
@DaveVandenEynde, I'm a fairly novice programmer, so please forgive me if this is not a great question: what is the meaning of swapping items in a list if the situation does not involve a concept of order?Zagazig
@MathieuK.well what I meant to say is that this answer proposes that the order is derived from a property on the objects on which the sequence can be sorted. That's usually not the case.Buckle
I'm (perhaps mis)understanding this answer to be saying, "Number your objects, storing the numbers in a property. Then, to swap two items, swap their numbers. Use the list sorted by this property."Zagazig

© 2022 - 2024 — McMap. All rights reserved.