-1

I have two Lists, a primary list and a list of items to move, which is a subset of the primary list:

var primaryList = new List<foo> { f1, f2, f3, f4, f5, f6, f7 };
var itemsToMove = new List<foo> { f1, f4, f6, f7 };

I want to change the order of the items in primaryList so that each element that is also in itemsToMove moves one position closer to the start of the list, but I need for items to stay in order relative to other members of the itemsToMove collection.

The result I want is:

Iteration #1:
primaryList { f1, f2, f4, f3, f6, f7, f5 };

Iteration #2:
primaryList { f1, f4, f2, f6, f7, f3, f5 };

Iteration #2:
primaryList { f1, f4, f6, f7, f2, f3, f5 };

Here's the code I'm currently using:

// Catch items that are already in the last or first position
foreach (var item in primaryList)
{
  if (primaryList.IndexOf(item) < 1)
  {
    itemsToMove.Remove(item);
  }
}

foreach (var item in itemsToMove)
{
  int index = primaryList.IndexOf(item);

  if (index > 0)
  {
    primaryList.Remove(item);
    primaryList.Insert(index - 1, item);
  }
}

When I use this code, the result I get is:

primaryList { f1, f2, f4, f3, f5, f6, f7, };

I've looked at this question, but the answer there doesn't seem to actually do anything at all, so I changed the code to move closer to a solution.

What am I doing wrong here? Is there a more efficient way to handle this operation?

aforest-ccc
  • 85
  • 1
  • 11

1 Answers1

2

I think there are two issues here. (1) you need to keep track of an empty position at the beginning of the list. (2) you need to complete the swap of each item before proceeding to the next (this allows the f5 to move to the end of the list).

Here's how I would do it:

int[] indices = Enumerable.Range(-1, primaryList.Count + 1).ToArray();
foreach (var item in itemsToMove)
{
    var index = primaryList.IndexOf(item);
    (indices[index], indices[index + 1]) = (indices[index + 1], indices[index]);
}
primaryList = indices.Where(i => i != -1).Select(i => primaryList[i]).ToList();

indices is an array of integers with a -1 indicating the start of the list.

Then we loop through the itemsToMove, find each elements index in the original list and swap the index with the previous element. Remember that we added - to the start of the list so then we are swapping element index and index + 1 to swap the current and the previous indices.

Then it's a simple matter to filtering out the -1 and pulling out the values from the original list at their new index locations.

The result I get is:

f1, f2, f4, f3, f6, f7, f5

Here's some more explanation:

We're starting with this array:

0

We want to be able to swap these elements with the element to the left:

1

There's no element to the left of f1 so let's make one. We now have this array:

2

Now we do the four swaps:

3

Then we rebuild the original list at the new indices and drop the -1:

4


Here's how to reverse the operation:

var reversed =
    indices
        .Where(i => i != -1)
        .Select((i, n) => (i, n))
        .OrderBy(x => x.i)
        .Select(x => x.n)
        .Select(i => primaryList[i])
        .ToList();

This is done after primaryList has been modified.


Here's how to do the "move forward" operation:

int[] indices = Enumerable.Range(0, primaryList.Count + 1).ToArray();
foreach (var item in itemsToMove.AsEnumerable().Reverse())
{
    var index = primaryList.IndexOf(item);
    (indices[index], indices[index + 1]) = (indices[index + 1], indices[index]);
}
indices.Dump();
primaryList = indices.Where(i => i != primaryList.Count).Select(i => primaryList[i]).ToList();
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • This works, but can you walk me through the whys in a little more detail? I want to understand it so I can implement the reverse movement, that is, moving a subset in the other direction while maintaining order. – aforest-ccc Oct 30 '20 at 05:09
  • @aforest-ccc - Done. – Enigmativity Oct 30 '20 at 07:02
  • Thanks! I'm still trying to work out how to make the reverse movement work, but this does make things a little clearer. Right now I'm having trouble with the final step of the reverse movement, because it's giving me indexes above the range of the primary list. – aforest-ccc Nov 02 '20 at 04:43
  • From my understanding I should be able to simply do a Reverse() on the primaryList and the itemsToMove before I perform the operation, then do a Revers() on the primaryList afterwards, and get the reverse movement. But this doesn't seem to work for me after the first iteration. – aforest-ccc Nov 02 '20 at 05:38
  • @aforest-ccc - I've added the reversing method. – Enigmativity Nov 02 '20 at 05:48
  • I should have clarified: I'm trying to do the reverse movement, not to undo the operation. That is, instead of the index positions of the itemsToMove list decreasing, they should increase. – aforest-ccc Nov 02 '20 at 06:00
  • @aforest-ccc - No worries. I've put in a move forward operation. The `.AsEnumerable().Reverse()` appears to be needed, but you might decide otherwise. – Enigmativity Nov 02 '20 at 11:09
  • I discovered that the method of doing a Reverse() on the itemsToMove and primaryList before and after the move actually works fine. My problem was with another part of the code. – aforest-ccc Nov 02 '20 at 11:11