9

我发现这比我想象的要难得多。如何在列表中移动部分项目?

例如,如果我有以下列表:

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

该列表将包含{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }.

如何移动列表的各个部分?假设我想移动{ 7, 8, 9 }到第 4 个索引,使其成为:

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

或者,假设我想进入{ 1, 2 }{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }8 个索引,使其成为:

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

谁能提供一些代码?采用以下 3 个值的东西会很棒。

MoveSection(insertionPoint, startIndex, endIndex)

请注意,当您从开头删除部分时,插入位置已更改。这使它变得更加困难。

4

5 回答 5

5

对于任何IEnumerable使用迭代器块的人,您都可以相对简单地执行此操作。我总是发现使用yield return构造以清晰简洁的方式解决了这类问题。在这里,为了方便使用,我还把方法做成了扩展方法:

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;
          }
      }
   }
}

这里我使用了 Linq 方法SkipTake,它们通常很有用。此外,您可能对该方法感兴趣,该方法可以像使用循环Enumerable.Range一样轻松创建范围。for

然后,您可以像这样调用该方法:

myList.MoveSection(8, 1, 3);
于 2013-06-17T23:08:50.987 回答
1

如果您可以创建另一个列表,请使用GetRange/AddRange一些简单的更正。
如果您想就地执行它,它将是这样的(似乎适用于您的用例,但我建议进行一些适当的单元测试):

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;            
    }
}

请注意,我没有添加任何验证(与插入点的范围相交、源范围在列表之外等)。如果您在实际项目中使用扩展方法,我建议您验证所有这些。

于 2013-06-17T23:14:20.097 回答
1

好的,详细说明我上面的评论,让我们尝试一个实现作为扩展方法LinkedList<T>

我无法以任何方式对其进行测试,我只是在记事本中对其进行了编码。
startIndex= 要移动的部分的开始索引 = 要移动
endIndex的部分的结束索引(包括)
moveIndex= 要移动的部分的索引。0= 列表开头,list.Count= 列表结尾。

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;
}

尽管@jmyns 已经发布了带有链接列表的答案,但我认为我的解决方案更好地服务于链接列表的想法。

于 2013-06-18T10:30:51.630 回答
0

链表可能是理想的,但这取决于您的初始列表有多大。每个节点都有一个指向下一个节点的指针,这使得移动变得简单。有额外的费用,许多人更喜欢使用常规列表。

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

声明要在数组中移动的项目并将它们从 myList 中删除

int [] itemsToMove = { 1, 2 };

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

现在链接将是

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

识别目标节点

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

然后迭代项目,在目标节点之后添加项目。如果顺序很重要,请先反转数组。

Array.Reverse(itemsToMove);

for (int i = 0; i < itemsToMove.Length - 1; i++)
{
linked.AddAfter(targetNode , itemsToMove[i]);
}
于 2013-06-17T23:39:17.710 回答
0

在你自己的方法中包装这样的东西怎么样:

List<int> subList = myList.GetRange(startIndex, count);
myList.RemoveRange(startIndex, count);
myList.InsertRange(insertionPoint, subList);
于 2013-06-17T23:08:19.243 回答