Generic List - moving an item within the list

C#.NetGenericsList

C# Problem Overview


So I have a generic list, and an oldIndex and a newIndex value.

I want to move the item at oldIndex, to newIndex...as simply as possible.

Any suggestions?

Note

The item should be end up between the items at (newIndex - 1) and newIndex before it was removed.

C# Solutions


Solution 1 - C#

I know you said "generic list" but you didn't specify that you needed to use the List(T) class so here is a shot at something different.

The ObservableCollection(T) class has a Move method that does exactly what you want.

public void Move(int oldIndex, int newIndex)

Underneath it is basically implemented like this.

T item = base[oldIndex];
base.RemoveItem(oldIndex);
base.InsertItem(newIndex, item);

So as you can see the swap method that others have suggested is essentially what the ObservableCollection does in it's own Move method.

UPDATE 2015-12-30: You can see the source code for the Move and MoveItem methods in corefx now for yourself without using Reflector/ILSpy since .NET is open source.

Solution 2 - C#

var item = list[oldIndex];

list.RemoveAt(oldIndex);

if (newIndex > oldIndex) newIndex--; 
// the actual index could have shifted due to the removal

list.Insert(newIndex, item);

Put into Extension methods they look like:

    public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
    {
        var item = list[oldIndex];

        list.RemoveAt(oldIndex);

        if (newIndex > oldIndex) newIndex--;
        // the actual index could have shifted due to the removal

        list.Insert(newIndex, item);
    }

    public static void Move<T>(this List<T> list, T item, int newIndex)
    {
        if (item != null)
        {
            var oldIndex = list.IndexOf(item);
            if (oldIndex > -1)
            {
                list.RemoveAt(oldIndex);

                if (newIndex > oldIndex) newIndex--;
                // the actual index could have shifted due to the removal

                list.Insert(newIndex, item);
            }
        }

    }

Solution 3 - C#

I know this question is old but I adapted THIS response of javascript code to C#. Hope it helps

public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
{
    // exit if positions are equal or outside array
    if ((oldIndex == newIndex) || (0 > oldIndex) || (oldIndex >= list.Count) || (0 > newIndex) ||
        (newIndex >= list.Count)) return;
    // local variables
    var i = 0;
    T tmp = list[oldIndex];
    // move element down and shift other elements up
    if (oldIndex < newIndex)
    {
        for (i = oldIndex; i < newIndex; i++)
        {
            list[i] = list[i + 1];
        }
    }
        // move element up and shift other elements down
    else
    {
        for (i = oldIndex; i > newIndex; i--)
        {
            list[i] = list[i - 1];
        }
    }
    // put element from position 1 to destination
    list[newIndex] = tmp;
}

Solution 4 - C#

List<T>.Remove() and List<T>.RemoveAt() do not return the item that is being removed.

Therefore you have to use this:

var item = list[oldIndex];
list.RemoveAt(oldIndex);
list.Insert(newIndex, item);

Solution 5 - C#

I created an extension method for moving items in a list.

An index should not shift if we are moving an existing item since we are moving an item to an existing index position in the list.

The edge case that @Oliver refers to below (moving an item to the end of the list) would actually cause the tests to fail, but this is by design. To insert a new item at the end of the list we would just call List<T>.Add. list.Move(predicate, list.Count) should fail since this index position does not exist before the move.

In any case, I've created two additional extension methods, MoveToEnd and MoveToBeginning, the source of which can be found here.

/// <summary>
/// Extension methods for <see cref="System.Collections.Generic.List{T}"/>
/// </summary>
public static class ListExtensions
{
    /// <summary>
    /// Moves the item matching the <paramref name="itemSelector"/> to the <paramref name="newIndex"/> in a list.
    /// </summary>
    public static void Move<T>(this List<T> list, Predicate<T> itemSelector, int newIndex)
    {
        Ensure.Argument.NotNull(list, "list");
        Ensure.Argument.NotNull(itemSelector, "itemSelector");
        Ensure.Argument.Is(newIndex >= 0, "New index must be greater than or equal to zero.");
        
        var currentIndex = list.FindIndex(itemSelector);
        Ensure.That<ArgumentException>(currentIndex >= 0, "No item was found that matches the specified selector.");

        // Copy the current item
        var item = list[currentIndex];

        // Remove the item
        list.RemoveAt(currentIndex);

        // Finally add the item at the new index
        list.Insert(newIndex, item);
    }
}

[Subject(typeof(ListExtensions), "Move")]
public class List_Move
{
    static List<int> list;

    public class When_no_matching_item_is_found
    {
        static Exception exception;

        Establish ctx = () => {
            list = new List<int>();
        };

        Because of = ()
            => exception = Catch.Exception(() => list.Move(x => x == 10, 10));

        It Should_throw_an_exception = ()
            => exception.ShouldBeOfType<ArgumentException>();
    }

    public class When_new_index_is_higher
    {
        Establish ctx = () => {
            list = new List<int> { 1, 2, 3, 4, 5 };
        };

        Because of = ()
            => list.Move(x => x == 3, 4); // move 3 to end of list (index 4)

        It Should_be_moved_to_the_specified_index = () =>
            {
                list[0].ShouldEqual(1);
                list[1].ShouldEqual(2);
                list[2].ShouldEqual(4);
                list[3].ShouldEqual(5);
                list[4].ShouldEqual(3);
            };
    }

    public class When_new_index_is_lower
    {
        Establish ctx = () => {
            list = new List<int> { 1, 2, 3, 4, 5 };
        };

        Because of = ()
            => list.Move(x => x == 4, 0); // move 4 to beginning of list (index 0)

        It Should_be_moved_to_the_specified_index = () =>
        {
            list[0].ShouldEqual(4);
            list[1].ShouldEqual(1);
            list[2].ShouldEqual(2);
            list[3].ShouldEqual(3);
            list[4].ShouldEqual(5);
        };
    }
}

Solution 6 - C#

Insert the item currently at oldIndex to be at newIndex and then remove the original instance.

list.Insert(newIndex, list[oldIndex]);
if (newIndex <= oldIndex) ++oldIndex;
list.RemoveAt(oldIndex);

You have to take into account that the index of the item you want to remove may change due to the insertion.

Solution 7 - C#

I would expect either:

// Makes sure item is at newIndex after the operation
T item = list[oldIndex];
list.RemoveAt(oldIndex);
list.Insert(newIndex, item);

... or:

// Makes sure relative ordering of newIndex is preserved after the operation, 
// meaning that the item may actually be inserted at newIndex - 1 
T item = list[oldIndex];
list.RemoveAt(oldIndex);
newIndex = (newIndex > oldIndex ? newIndex - 1, newIndex)
list.Insert(newIndex, item);

... would do the trick, but I don't have VS on this machine to check.

Solution 8 - C#

Simplest way:

list[newIndex] = list[oldIndex];
list.RemoveAt(oldIndex);

EDIT

The question isn't very clear ... Since we don't care where the list[newIndex] item goes I think the simplest way of doing this is as follows (with or without an extension method):

    public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
    {
        T aux = list[newIndex];
        list[newIndex] = list[oldIndex];
        list[oldIndex] = aux;
    }

This solution is the fastest because it doesn't involve list insertions/removals.

Solution 9 - C#

Is more simple guys just do this

    public void MoveUp(object item,List Concepts){

        int ind = Concepts.IndexOf(item.ToString());

        if (ind != 0)
        {
            Concepts.RemoveAt(ind);
            Concepts.Insert(ind-1,item.ToString());
            obtenernombres();
            NotifyPropertyChanged("Concepts");
        }}

Do the same with MoveDown but change the if for "if (ind !=Concepts.Count())" and the Concepts.Insert(ind+1,item.ToString());

Solution 10 - C#

This is how I implemented a move element extension method. It handles moving before/after and to the extremes for elements pretty well.

public static void MoveElement<T>(this IList<T> list, int fromIndex, int toIndex)
{
  if (!fromIndex.InRange(0, list.Count - 1))
  {
    throw new ArgumentException("From index is invalid");
  }
  if (!toIndex.InRange(0, list.Count - 1))
  {
    throw new ArgumentException("To index is invalid");
  }

  if (fromIndex == toIndex) return;

  var element = list[fromIndex];

  if (fromIndex > toIndex)
  {
    list.RemoveAt(fromIndex);
    list.Insert(toIndex, element);
  }
  else
  {
    list.Insert(toIndex + 1, element);
    list.RemoveAt(fromIndex);
  }
}

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
QuestionRichard EvView Question on Stackoverflow
Solution 1 - C#jpiersonView Answer on Stackoverflow
Solution 2 - C#Garry ShutlerView Answer on Stackoverflow
Solution 3 - C#FranciscoView Answer on Stackoverflow
Solution 4 - C#M4NView Answer on Stackoverflow
Solution 5 - C#Ben FosterView Answer on Stackoverflow
Solution 6 - C#MegacanView Answer on Stackoverflow
Solution 7 - C#Aaron MaenpaaView Answer on Stackoverflow
Solution 8 - C#bruno condeView Answer on Stackoverflow
Solution 9 - C#Richard AguirreView Answer on Stackoverflow
Solution 10 - C#Allan HarperView Answer on Stackoverflow