Daftar Generik - memindahkan item dalam daftar


155

Jadi saya punya daftar generik, dan oldIndexdan newIndexnilai.

Saya ingin memindahkan item di oldIndex, ke newIndex... sesederhana mungkin.

Ada saran?

Catatan

Item tersebut harus berada di antara item di (newIndex - 1)dan newIndex sebelum dihapus.


1
Anda harus mengubah jawaban yang sudah Anda tandai. Orang dengan newIndex--tidak menghasilkan perilaku yang Anda inginkan.
Miral

1
@ Miral - jawaban mana yang menurut Anda harus diterima?
Richard Ev

4
jpierson. Ini menghasilkan objek yang dulu di oldIndex sebelum pindah ke newIndex setelah pindah. Ini adalah perilaku yang paling tidak mengejutkan (dan itulah yang saya butuhkan ketika saya sedang menulis beberapa kode penataan ulang drag'n'drop). Memang dia berbicara tentang ObservableCollectiondan bukan generik List<T>, tapi itu sepele untuk hanya menukar panggilan metode untuk mendapatkan hasil yang sama.
Miral

Perilaku yang diminta (dan diterapkan dengan benar dalam jawaban ini ) untuk memindahkan item di antara item di [newIndex - 1]dan [newIndex]tidak dapat dibalik. Move(1, 3); Move(3, 1);tidak mengembalikan daftar ke keadaan awal. Sementara itu ada perilaku berbeda yang disediakan ObservableCollectiondan disebutkan dalam jawaban ini , yang tidak dapat dibalik .
Lightman

Jawaban:


138

Saya tahu Anda mengatakan "daftar generik" tetapi Anda tidak menentukan bahwa Anda perlu menggunakan kelas Daftar (T) jadi di sini ada kesempatan untuk sesuatu yang berbeda.

Kelas ObservableCollection (T) memiliki metode Pindahkan yang melakukan apa yang Anda inginkan.

public void Move(int oldIndex, int newIndex)

Di bawahnya pada dasarnya dilaksanakan seperti ini.

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

Sehingga Anda dapat melihat metode swap yang disarankan orang lain pada dasarnya adalah apa yang dilakukan ObservableCollection dalam metode Move itu sendiri.

UPDATE 2015-12-30: Anda dapat melihat kode sumber untuk metode Move dan MoveItem di corefx sekarang untuk Anda sendiri tanpa menggunakan Reflector / ILSpy karena .NET adalah open source.


28
Saya bertanya-tanya mengapa ini tidak diterapkan pada Daftar <T> juga, ada yang memberi penjelasan tentang ini?
Andreas

Apa perbedaan antara daftar generik dan kelas Daftar (T)? Saya pikir mereka sama :(
BKSpurgeon

"Daftar generik" dapat berarti semua jenis daftar atau koleksi seperti struktur data di .NET yang dapat mencakup ObservableCollection (T) atau kelas-kelas lain yang dapat mengimplementasikan antarmuka listy seperti IList / ICollection / IEnumerable.
jpierson

6
Bisakah seseorang menjelaskan, tolong, mengapa tidak ada pergeseran indeks tujuan (kalau-kalau itu lebih besar dari indeks sumber)?
Vladius

@vladius Saya percaya idenya adalah bahwa nilai newIndex yang ditentukan harus cukup menentukan indeks yang diinginkan setelah item dipindahkan dan karena insert sedang digunakan, tidak ada alasan untuk penyesuaian. Jika newIndex adalah posisi relatif terhadap indeks asli yang akan menjadi cerita lain saya kira tapi itu bukan cara kerjanya.
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
Solusi Anda rusak jika ada dua salinan item dalam daftar, dengan satu terjadi sebelum oldIndex. Anda harus menggunakan RemoveAt untuk memastikan Anda mendapatkan yang benar.
Aaron Maenpaa

6
Memang, kasus tepi licik
Garry Shutler

1
@ GarryShutler Saya tidak bisa melihat bagaimana indeks bisa bergeser jika kita menghapus dan kemudian memasukkan satu item. Mengurangi newIndexujian saya yang sebenarnya rusak (lihat jawaban saya di bawah).
Ben Foster

1
Catatan: jika keamanan utas penting, ini semua harus di dalam lockpernyataan.
rory.ap

1
Saya tidak akan menggunakan ini karena membingungkan karena beberapa alasan. Menentukan metode Pindahkan (oldIndex, newIndex) pada daftar dan memanggil Pindahkan (15,25) dan kemudian Pindahkan (25,15) bukan identitas, tetapi tukar. Pindahkan Juga (15,25) membuat item pindah ke indeks 24 dan bukan pada 25 yang saya harapkan. Selain itu swapping dapat diimplementasikan oleh temp = item [oldindex]; item [oldindex] = item [newindex]; item [newindex] = temp; yang tampaknya lebih efisien pada array besar. Move juga (0,0) dan Move (0,1) akan sama yang juga aneh. Dan juga Pindahkan (0, Hitung -1) tidak memindahkan item ke akhir.
Wouter

12

Saya tahu pertanyaan ini sudah lama tetapi saya mengadaptasi respons kode javascript INI ke C #. Semoga ini bisa membantu

 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

Daftar <T> .Remove () dan List <T> .RemoveAt () tidak mengembalikan item yang sedang dihapus.

Karena itu Anda harus menggunakan ini:

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

5

Masukkan item saat ini di oldIndexberada di newIndexkemudian hapus instance asli.

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

Anda harus mempertimbangkan bahwa indeks item yang ingin Anda hapus dapat berubah karena penyisipan.


1
Anda harus menghapus sebelum memasukkan ... pesanan Anda dapat menyebabkan daftar melakukan alokasi.
Jim Balter

4

Saya membuat metode ekstensi untuk memindahkan item dalam daftar.

Indeks tidak boleh bergeser jika kami memindahkan item yang ada karena kami memindahkan item ke posisi indeks yang ada dalam daftar.

Huruf tepi yang @Oliver rujuk di bawah (memindahkan item ke akhir daftar) sebenarnya akan menyebabkan tes gagal, tetapi ini adalah desain. Untuk memasukkan item baru di akhir daftar kami hanya akan memanggil List<T>.Add. list.Move(predicate, list.Count) harus gagal karena posisi indeks ini tidak ada sebelum pindah.

Bagaimanapun, saya telah membuat dua metode ekstensi tambahan, MoveToEnddan MoveToBeginning, sumbernya dapat ditemukan di sini .

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

Di mana Ensure.Argumentdidefinisikan?
Oliver

1
Secara normal List<T>Anda dapat menelepon Insert(list.Count, element)untuk menempatkan sesuatu di akhir daftar. Jadi Anda When_new_index_is_higherharus menelepon list.Move(x => x == 3, 5)yang sebenarnya gagal.
Oliver

3
@Liver secara normal, List<T>saya hanya akan menelepon .Adduntuk memasukkan item baru ke akhir daftar. Saat memindahkan item individual, kami tidak pernah menambah ukuran asli indeks karena kami hanya menghapus satu item dan memasukkannya kembali. Jika Anda mengklik tautan dalam jawaban saya, Anda akan menemukan kode untuk Ensure.Argument.
Ben Foster

Solusi Anda mengharapkan bahwa indeks tujuan adalah posisi, bukan antara dua elemen. Meskipun ini berfungsi dengan baik untuk beberapa kasus penggunaan, ini tidak bekerja untuk orang lain. Juga, perpindahan Anda tidak mendukung pemindahan ke ujung (seperti dicatat oleh Oliver) tetapi tidak ada di mana pun dalam kode Anda Anda menunjukkan batasan ini. Ini juga berlawanan dengan intuisi, jika saya memiliki daftar dengan 20 elemen dan ingin memindahkan elemen 10 ke akhir, saya akan mengharapkan metode Pindahkan untuk menangani ini, daripada harus menemukan untuk menyimpan referensi objek, menghapus objek dari daftar dan tambahkan objek.
Dipotong

1
@Trisped sebenarnya jika Anda membaca jawaban saya, memindahkan item sampai akhir / awal daftar tersebut didukung. Anda dapat melihat spesifikasi di sini . Ya kode saya mengharapkan indeks menjadi posisi yang valid (ada) dalam daftar. Kami memindahkan barang, bukan menyisipkannya.
Ben Foster

1

Saya harapkan:

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

... atau:

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

... akan melakukan trik, tetapi saya tidak memiliki VS pada mesin ini untuk memeriksa.


1
@ GarryShutler Tergantung situasinya. Jika antarmuka Anda memungkinkan pengguna untuk menentukan posisi dalam daftar berdasarkan indeks, mereka akan bingung ketika mereka memberi tahu item 15 untuk pindah ke 20, tetapi alih-alih pindah ke 19. Jika antarmuka Anda memungkinkan pengguna untuk menyeret item antara yang lain pada daftar maka masuk akal untuk mengurangi newIndexjika setelah oldIndex.
Dipotong

-1

Cara termudah:

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

EDIT

Pertanyaannya tidak terlalu jelas ... Karena kita tidak peduli ke mana list[newIndex]barang tersebut saya pikir cara paling sederhana untuk melakukan ini adalah sebagai berikut (dengan atau tanpa metode ekstensi):

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

Solusi ini adalah yang tercepat karena tidak melibatkan penyisipan / pemindahan daftar.


4
Itu akan menimpa item di newIndex, bukan memasukkan.
Garry Shutler

@ Garry Bukankah hasil akhirnya akan sama?
Ozgur Ozcitak

4
Tidak, Anda pada akhirnya akan kehilangan nilainya di newIndex yang tidak akan terjadi jika Anda menyisipkan.
Garry Shutler

-2

Apakah lebih sederhana kawan lakukan saja ini

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

Lakukan hal yang sama dengan MoveDown tetapi ubah if untuk "if (ind! = Concepts.Count ())" dan Concepts.Insert (ind + 1, item.ToString ());


-3

Ini adalah bagaimana saya menerapkan metode ekstensi elemen bergerak. Ini menangani bergerak sebelum / sesudah dan ke ekstrem untuk elemen dengan cukup baik.

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
Ini adalah duplikat dari jawaban dari Francisco.
nivs1978
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.