通用列表-在列表中移动项目


155

所以我有一个通用列表,以及oldIndexnewIndex值。

我想将项目从oldIndex移到newIndex...尽可能简单。

有什么建议?

注意

该项目应在删除之前(newIndex - 1)newIndex 之后在项目之间结束。


1
您应该更改您打勾的答案。与一起newIndex--使用不会导致您说想要的行为。
Miral 2014年

1
@Miral-您认为哪个答案应该被接受?
理查德·伊夫

4
jpierson的。结果是该对象以前在移动之前位于oldIndex,而在移动之后位于newIndex。这是最令人惊讶的行为(这是我编写一些拖放式重排序代码时需要的行为)。尽管他在谈论的ObservableCollection不是泛型List<T>,但简单地交换方法调用以获取相同的结果是微不足道的。
Miral 2014年

请求(和在这个答案正确实现的)行为移动项目中的项目之间的[newIndex - 1][newIndex]不可逆的。Move(1, 3); Move(3, 1);不会将列表返回到初始状态。同时,此答案中提供ObservableCollection提到了不同的行为,这是可逆的
莱特曼

Answers:


138

我知道您说过“通用列表”,但是您没有指定需要使用List(T)类,因此这里有一些不同之处。

的ObservableCollection(T)类有一个Move方法,你想要做什么。

public void Move(int oldIndex, int newIndex)

在其下面基本上是这样实现的。

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

因此,您可以看到其他人建议的swap方法实际上是ObservableCollection在其自己的Move方法中所做的事情。

UPDATE二○一五年十二月三十日:你可以看到在源代码中移动移动选项中corefx方法现在为自己没有使用反射/ ILSpy因为.NET是开源的。


28
我想知道为什么List <T>上也没有实现这一点,有人对此有所了解吗?
Andreas

通用列表和List(T)类之间有什么区别?我以为他们是一样的:(
BKSpurgeon

“通用列表”可以表示任何类型的列表或集合,例如.NET中的数据结构,其中可以包括ObservableCollection(T)或可以实现列表接口(例如IList / ICollection / IEnumerable )的其他类。
jpierson 2015年

6
有人可以解释一下,为什么实际上没有目的地索引偏移(如果它大于源索引)?
Vladius '16

@vladius我相信想法是,指定的newIndex值应仅指定该项目在移动后应位于的所需索引,并且由于使用了插入,因此没有调整的理由。如果newIndex是相对于原始索引的位置,那是我想的另一个故事,但这不是它的工作原理。
jpierson

129
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);

9
如果列表中有两个项目副本,则其中一个出现在oldIndex之前,则您的解决方案将失效。您应该使用RemoveAt来确保获得正确的一个。
亚伦·曼帕

6
事实上,偷偷摸摸的边缘情况
加里Shutler

1
@GarryShutler我看不到如果删除然后插入单个项目,索引将如何移动。减小newIndex实际值会破坏我的测试(请参见下面的答案)。
Ben Foster 2012年

1
注意:如果线程安全性很重要,则所有这些都应放在lock语句内。
rory.ap

1
我不会使用它,因为它有几个原因,令人困惑。在列表上定义方法Move(oldIndex,newIndex)并依次调用Move(15,25)和Move(25,15)不是身份而是交换。另外,Move(15,25)会使项目移动到索引24,而不是我期望的25。此外,交换可以通过temp = item [oldindex];来实现。item [oldindex] = item [newindex]; item [newindex] = temp; 在大型阵列上似乎更有效。Move(0,0)和Move(0,1)也将相同,这也是奇怪的。而且Move(0,Count -1)不会将项目移到末尾。
沃特

12

我知道这个问题很旧,但我将JavaScript代码的响应改编为C#。希望能帮助到你

 public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
{

    // exit if possitions 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;
}

9

List <T> .Remove()和List <T> .RemoveAt()不会返回要删除的项目。

因此,您必须使用此:

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

5

插入该项目目前oldIndex是在newIndex然后删除原始实例。

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

您必须考虑到要删除的项目的索引可能会由于插入而发生变化。


1
您应该在插入之前删除...您的订单可能会导致列表进行分配。
Jim Balter

4

我创建了一个扩展方法来移动列表中的项目。

如果我们要移动现有项目,则索引不应移动,因为我们要将项目移动到列表中的现有索引位置。

@Oliver在下面引用的极端情况(将项目移至列表的末尾)实际上会导致测试失败,但这是设计使然。要在列表末尾插入一个项,我们将调用List<T>.Addlist.Move(predicate, list.Count) 应该失败,因为该索引位置在移动之前不存在。

无论如何,我已经创建了两个附加的扩展方法,MoveToEnd并且MoveToBeginning,可以在此处找到其来源。

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

在哪里Ensure.Argument定义?
奥利弗(Oliver)

1
通常,List<T>您可以调用Insert(list.Count, element)将某些内容放置在列表的末尾。因此,您When_new_index_is_higher应该拨打list.Move(x => x == 3, 5)实际上失败的电话。
奥利弗(Oliver)

3
@Oliver在正常情况下,List<T>我会打电话.Add给列表的末尾插入一个项。当移动单个项目我们从来没有增加索引的原始大小,因为我们只删除单个项目并重新插入。如果您点击我的答案中的链接,则会找到的代码Ensure.Argument
Ben Foster

您的解决方案期望目标索引是一个位置,而不是两个元素之间。虽然这在某些用例中效果很好,但在其他用例中却不起作用。另外,您的举动不支持移至末尾(如Oliver所述),但是您在代码中的任何位置均未指出此约束。这也有悖常理,如果我有一个包含20个元素的列表,并且想要将元素10移到末尾,我希望使用Move方法来处理此问题,而不是必须找到保存对象引用,然后从列表中删除对象并添加对象。
2012年

1
如果你看过我的回答,移动的项目到最后居然@Trisped /列表的开始支持。您可以在此处查看规格。是的,我的代码希望索引在列表中是有效(现有)位置。我们正在移动项目,而不是插入它们。
Ben Foster

1

我期望:

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

... 要么:

// 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);

...可以解决问题,但是我没有在这台机器上检查VS。


1
@GarryShutler取决于情况。如果您的界面允许用户通过索引指定列表中的位置,那么当他们告诉第15项移至20时,他们会感到困惑,相反,它告诉第15项移至19。在清单上的话,newIndex如果在后面的话递减是有意义的oldIndex
2012年

-1

最简单的方法:

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

编辑

问题不是很清楚...由于我们不在乎list[newIndex]项目的去向,我认为最简单的方法如下(使用或不使用扩展方法):

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

该解决方案是最快的,因为它不涉及列表插入/删除。


4
这将覆盖newIndex处的项目,而不是插入项。
加里·舒特勒

@加里最终的结果不一样吗?
Ozgur Ozcitak,2009年

4
不,您最终将失去newIndex的值,如果您插入该值将不会发生。
加里·舒特勒

-2

更简单的家伙只是这样做

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

对MoveDown进行相同的操作,但将if的“ if(ind!= Concepts.Count())”和Concepts.Insert(ind + 1,item.ToString())更改;


-3

这就是我实现移动元素扩展方法的方式。它可以很好地处理元素之前/之后和极端的移动。

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

2
这是弗朗西斯科答案的重复。
nivs1978 '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.