Reputation: 3758
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
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
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
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
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
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