ジェネリックリスト-リスト内のアイテムの移動


155

だから私は一般的なリストと、oldIndexそしてnewIndex値を持っています。

アイテムをoldIndexnewIndex...にできるだけ簡単に移動したいです。

助言がありますか?

注意

アイテムは、アイテム間終わるべきである(newIndex - 1)newIndex の前にそれが削除されました。


1
選択した答えを変更する必要があります。が付いnewIndex--ているものは、あなたが望んだ動作にはなりません。
ミラル2014年

1
@Miral-どの回答を受け入れるべきだと思いますか?
Richard Ev 2014年

4
jpiersonの。これにより、移動前はoldIndexにあったオブジェクトが、移動後はnewIndexになります。これは最も驚くべき動作ではありません(そして、ドラッグアンドドロップの並べ替えコードを記述していたときに必要でした)。彼が話してObservableCollectionいるのは総称List<T>ではなく、同じ結果を得るためにメソッド呼び出しを単純に交換するのは簡単です。
ミラル2014年

要求された(そして正しくこの答えで実装)行動で項目間でアイテムを移動する[newIndex - 1]と、[newIndex]可逆ではありません。Move(1, 3); Move(3, 1);リストを初期状態に戻しません。一方、この回答で提供されObservableCollection言及されている異なる動作があり、これは可逆的です。
ライトマン2017

回答:


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 2015-12-30: .NETはオープンソースなので、Reflector / ILSpyを使用しなくても、corefxでMoveメソッドとMoveItemメソッドのソースコードを確認できます。


28
なぜこれがList <T>にも実装されていないのか、これに光を当てる人はいますか?
Andreas 14年

ジェネリックリストとList(T)クラスの違いは何ですか?私はそれらが同じであると思った:(
BKSpurgeon 2015

「ジェネリックリスト」とは、ObservableCollection(T)またはIList / ICollection / IEnumerableなどのリストのようなインターフェイスを実装できる他のクラスを含むことができる、.NETのデータ構造のような任意のタイプのリストまたはコレクションを意味します。
jpierson

6
誰かが実際に宛先インデックスのシフトがない(ソースインデックスよりも大きい場合)理由を説明できますか?
Vladius、2016年

@vladius私が考えているのは、指定されたnewIndex値は、移動後のアイテムの望ましいインデックスを指定するだけであり、挿入が使用されているため、調整の理由がないということです。newIndexが元のインデックスに対する相対的な位置だったとしたら、それはまた別の話になると思いますが、そうではありません。
jpierson 2017

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
リストに項目のコピーが2つあり、1つがoldIndexの前にある場合、ソリューションは失敗します。RemoveAtを使用して、正しいものを取得する必要があります。
アーロンメンパー09年

6
実際、卑劣なエッジケース
Garry Shutler、

1
@GarryShutler単一の項目を削除してから挿入すると、インデックスがどのように変化するかわかりません。newIndex実際にデクリメントすると、テストが失敗します(以下の私の回答を参照してください)。
ベンフォスター

1
注:スレッドセーフが重要な場合、これはすべてlockステートメント内にある必要があります。
rory.ap

1
いくつかの理由で混乱を招くので、私はこれを使用しません。リストにメソッドMove(oldIndex、newIndex)を定義し、Move(15,25)を呼び出してからMove(25,15)を呼び出すことは、IDではなくスワップです。また、Move(15,25)は、アイテムを、予想される25ではなく、インデックス24に移動します。また、temp = item [oldindex];によってスワッピングを実装できます。item [oldindex] = item [newindex]; item [newindex] = temp; 大規模な配列ではより効率的に見えます。また、Move(0,0)とMove(0,1)は同じですが、これも奇数です。また、Move(0、Count -1)はアイテムを最後に移動しません。
Wouter

12

私はこの質問は古いです知っているが、私は適応THIS C#へのJavaScriptコードの応答を。それが役に立てば幸い

 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
挿入する前に削除する必要があります...注文によってリストが割り当てを行う可能性があります。
ジムBalter

4

リスト内のアイテムを移動するための拡張メソッドを作成しました。

リスト内の既存のインデックス位置にアイテムを移動しているので、既存のアイテムを移動している場合、インデックスはシフトしないはずです。

@Oliverが以下で参照するエッジケース(アイテムをリストの最後に移動する)は、実際にはテストが失敗する原因になりますが、これは仕様によるものです。リストの最後に新しいアイテムを挿入するには、を呼び出すだけList<T>.Addです。このインデックス位置は移動前に存在しないため、失敗するlist.Move(predicate, list.Count) はずです。

いずれにせよ、私は2つの追加の拡張メソッド、作成したMoveToEndMoveToBeginning見ることができるソースは、ここを

/// <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定義されていますか?
オリバー

1
通常は、リストの最後に何かをList<T>呼び出すInsert(list.Count, element)ために呼び出すことができます。したがって、実際に失敗するWhen_new_index_is_higherを呼び出す必要がlist.Move(x => x == 3, 5)あります。
オリバー

3
通常の@Oliverでは、リストの最後に新しいアイテムを挿入するためにList<T>呼び出すだけです。個々のアイテムを移動する場合、1つのアイテムを削除して再挿入するだけなので、インデックスの元のサイズを増やすことはありません。私の回答のリンクをクリックすると、のコードが見つかります。.AddEnsure.Argument
ベンフォスター

ソリューションは、宛先インデックスが2つの要素の間ではなく位置であることを期待しています。これは一部のユースケースではうまく機能しますが、他のユースケースでは機能しません。また、あなたの移動は最後への移動をサポートしていませんが(Oliverによって指摘されています)、コード内のどこにもこの制約を示していません。これも直観に反します。20個の要素を持つリストがあり、要素10を最後に移動したい場合、Moveメソッドがこれを処理することを期待します。オブジェクト参照を保存し、リストからオブジェクトを削除する必要はありません。オブジェクトを追加します。
2010年

1
@Trisped実際に私の答えを読んだ場合、アイテムをリストの最後/最初に移動することサポートされています。仕様はこちらでご覧いただけます。はい、私のコードでは、インデックスがリスト内の有効な(既存の)位置であると想定しています。私たちはされて移動する彼らを挿入ない、アイテムを。
ベンフォスター

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に移動するように指示すると混乱しますが、代わりに19に移動します。インターフェイスでユーザーがアイテムを他のアイテムにドラッグできる場合リスト上newIndexでは、それが後である場合は減分することは理にかなっていますoldIndex
2010年

-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のアイテムを上書きします。
Garry Shutler、

@ギャリー最終的な結果は同じになるのではないですか?
Ozgur Ozcitak、2009年

4
いいえ、挿入した場合には起こりえないnewIndexの値を失うことになります。
Garry Shutler、

-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
これは、Franciscoからの回答の複製です。
nivs1978 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.