Howard
Howard

Reputation: 3758

C# Moving a section of items within the list

I'm finding this is a lot harder than I thought. How can I move a section of items within a List?

For example, if I have the following list:

List<int> myList = new List<int>();
for(int i=0; i<10; i++) {
    myList.Add(i);
}

This list would contain { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }.

How can I move sections of the List around? Say I want to move { 7, 8, 9 } to the the 4th index, making it:

{ 0, 1, 2, 3, 7, 8, 9, 4, 5, 6 }

Or, say I want to move { 1, 2 } in { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } to the 8th index, making it:

{ 0, 3, 4, 5, 6, 7, 1, 2, 8, 9 }

Can anyone provide some code? Something that takes 3 values like the following would be great.

MoveSection(insertionPoint, startIndex, endIndex)

Note when you remove sections from the beginning the insertion location has changed. This makes it quite a bit more difficult.

Upvotes: 9

Views: 2985

Answers (5)

Ben Reich
Ben Reich

Reputation: 16324

You can do this very generally for any IEnumerable using an iterator block relatively simply. I always find that using the yield return construct solves this type of problem in a clear and concise way. Here, I've also made the method into an extension method for ease of use:

public static class Extension
{
   public static IEnumerable<T> MoveSection<T>(this IEnumerable<T> @this, int insertionPoint, int startIndex, int endIndex)
   {
      var counter = 0;
      var numElements = endIndex - startIndex;
      var range = Enumerable.Range(startIndex, numElements);
      foreach(var i in @this)
      {
          if (counter == insertionPoint) {
              foreach(var j in @this.Skip(startIndex).Take(numElements)) {
                  yield return j;
              }
          }
          if (!range.Contains(counter)) {
              yield return i;
          }
          counter++;
      }             
      //The insertion point might have been after the entire list:
      if (counter++ == insertionPoint) {
          foreach(var j in @this.Skip(startIndex).Take(numElements)) {
              yield return j;
          }
      }
   }
}

Here I use the Linq methods Skip and Take, which are often useful. Also, you might be interested in the Enumerable.Range method, which allows for easy creation of the ranges like you do with a for loop instead.

You can then invoke the method like so:

myList.MoveSection(8, 1, 3);

Upvotes: 5

8192K
8192K

Reputation: 5290

OK, elaborating on my comment above, let's try an implementation as an extension method to LinkedList<T>:

I could not test it in any way, I just coded it in notepad.
startIndex = start index of section you want to move
endIndex = end index of section you want to move (inclusive)
moveIndex = index you want to move the section to. 0 = beginning of list, list.Count = end of list.

public static bool MoveSection<T>(this LinkedList<T> list, int startIndex, int endIndex, int moveIndex){
    //bounds checking
    if (startIndex < moveIndex && moveIndex < endIndex){
        return false;
    }
    if (list.Count <= startIndex || list.Count <= endIndex || list.Count+1 <= moveIndex){
            return false;
    }
    if (startIndex >= endIndex){
            return false;
    }

    LinkedListNode<T> startNode = list.ElementAt(startIndex);
    LinkedListNode<T> endNode = list.ElementAt(endIndex);

    LinkedListNode<T> restMoveNode = null;
    LinkedListNode<T> insertAfterNode;
    if (moveIndex < list.Count) {
            //when not inserting at the end of the list
            restMoveNode = list.ElementAt(moveIndex);
            insertAfterNode = restMoveNode.Previous;
    } else {
            //when inserting at the end of the list
            insertAfterNode = list.ElementAt(moveIndex - 1);
    }

    if (insertAfterNode == null){
            //when inserting at the beginning of the list 
            list.AddFirst(startNode);
    } else {    
            insertAfterNode.Next = startNode;
    }
    //restore previous list elements    
    endNode.next = restMoveNode;

    return true;
}

Although @jmyns already posted an answer with a linked list, I think my solution serves the idea of a linked list better.

Upvotes: 1

jmyns
jmyns

Reputation: 127

A linkedlist may be ideal, but it depends on how large your initial list is. Each node has a pointer to the next, which makes for simple moves. There is an added expense and many people prefer to use regular lists.

LinkedList<int> linked = new LinkedList<int>();
linked = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }

Declare the items you want to move in an array and remove them from myList

int [] itemsToMove = { 1, 2 };

for (int i = 0; i < itemsToMove -1; i++)
{
linked.Remove(itemsToMove[i])
}

Now linked will be

linked = { 0, 3, 4, 5, 6, 7, 8, 9 }

Identify target Node

LinkedListNode<int> targetNode = linked.Find("7"); 

Then iterate over the items adding the items after the target node. If order is important reverse the array first.

Array.Reverse(itemsToMove);

for (int i = 0; i < itemsToMove.Length - 1; i++)
{
linked.AddAfter(targetNode , itemsToMove[i]);
}

Upvotes: 0

Andrey Shchekin
Andrey Shchekin

Reputation: 21599

If you are ok with creating another list, use GetRange/AddRange with some simple correction.
If you want to do it inplace, it would be something like this (seems to work with your use cases, but I recommend some proper unit-testing):

public static void MoveRange<T>(this IList<T> list, int startIndex, int count, int targetIndex) {
    var correctedStartIndex = startIndex;
    var correctedTargetIndex = targetIndex;
    for (var i = count - 1; i >= 0; i--) {
        var item = list[correctedStartIndex + i];
        list.RemoveAt(correctedStartIndex + i);
        if (correctedTargetIndex > correctedStartIndex + i)
            correctedTargetIndex -= 1;

        list.Insert(correctedTargetIndex, item);            
        if (correctedStartIndex > correctedTargetIndex)
            correctedStartIndex += 1;            
    }
}

Note that I haven't added any validation whatsoever (range intersection with insertion point, source range being outside of the list, etc). If you are doing an extension method in a real project I advise validating all that.

Upvotes: 1

Chris Roberts
Chris Roberts

Reputation: 9

How about wrapping something like this in your own method:

List<int> subList = myList.GetRange(startIndex, count);
myList.RemoveRange(startIndex, count);
myList.InsertRange(insertionPoint, subList);

Upvotes: 0

Related Questions