Genel Liste - listedeki bir öğeyi taşıma


155

Yani genel bir liste ve bir oldIndexve bir newIndexdeğer var.

Ben de öğeyi taşımak istediğiniz oldIndexiçin, newIndex... basitçe mümkün olduğunca.

Baska öneri?

Not

Öğe , kaldırılmadan önceki(newIndex - 1) ve kaldırılmadan newIndex önce öğeler arasında olmalıdır .


1
İşaretlediğiniz yanıtı değiştirmeniz gerekir. İle bir newIndex--sen istediğini söyledi davranışa neden olmaz.
Miral

1
@Miral - Sizce hangi cevap kabul edilmeli?
Richard Ev

4
jpierson en. Bu, hareketten önce eskiden oldIndex değerinde olan nesnenin, hareketten sonra newIndex değerinde olmasına neden olur. Bu en az şaşırtıcı davranış (ve bazı drag'n'drop yeniden sıralama kodu yazarken ihtiyaç duyduğum şey). Genelden ObservableCollectiondeğil List<T>, hakkında konuştuğunu kabul etti , ancak aynı sonucu elde etmek için yöntem çağrılarını değiştirmek çok basit.
Miral

Talep edilen (ve doğru bu cevap uygulanan ) davranış ürünler arasında öğeyi taşımak için [newIndex - 1]ve [newIndex]ters çevrilebilir değildir. Move(1, 3); Move(3, 1);listeyi başlangıç ​​durumuna döndürmez. Bu arada , tersine çevrilebilir olan bu cevapta belirtilenObservableCollection ve bahsedilen farklı davranışlar vardır .
Lightman

Yanıtlar:


138

"Genel liste" dediğini biliyorum ama burada farklı bir şeye bir atış yani List (T) sınıfını kullanmak için gerekli olduğunu belirtmediniz .

ObservableCollection (T) sınıfı bir sahiptir Taşı yöntemini tam olarak ne istediğinizi yapar.

public void Move(int oldIndex, int newIndex)

Altında temelde böyle uygulanır.

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

Gördüğünüz gibi, başkalarının önerdiği takas yöntemi esasen ObservableCollection'ın kendi Move yönteminde yaptığı şeydir.

GÜNCELLEME 2015-12-30: .NET açık kaynaklı olduğu için Reflector / ILSpy kullanmadan corefx'te Move ve MoveItem yöntemlerinin kaynak kodunu şimdi kendiniz görebilirsiniz.


28
Bunun neden <T> Listesinde uygulanmadığını merak ediyorum, buna ışık tutan herhangi biri var mı?
Andreas

Genel liste ve List (T) sınıfı arasındaki fark nedir? Aynı olduklarını düşündüm :(
BKSpurgeon

"Genel liste", .NET'te ObservableCollection (T) veya IList / ICollection / IEnumerable gibi listyeli bir arabirim uygulayabilen diğer sınıfları içerebilen veri yapısı gibi herhangi bir liste veya koleksiyon anlamına gelebilir .
jpierson

6
Birisi gerçekten neden hedef endeks kayması olmadığını (kaynak endeksinden daha büyük olması durumunda) açıklayabilir mi?
Vladius

@vladius Fikir, belirtilen newIndex değerinin, öğenin taşıma işleminden sonra olması gereken dizini belirtmesi gerektiğine ve bir insert kullanıldığından, ayarlama için bir neden olmadığına inanıyorum. NewIndex, orijinal dizine göre başka bir hikaye olacak bir konum olsaydı, sanırım bu böyle değil.
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
Listede iki öğe kopyası varsa çözümünüz bozulur ve biri oldIndex'ten önce gerçekleşir. Doğru olanı aldığınızdan emin olmak için RemoveAt'ı kullanmalısınız.
Aaron Maenpaa

6
Gerçekten, sinsi kenar çantası
Garry Shutler

1
@GarryShutler Tek bir öğeyi kaldırıp eklersek dizinin nasıl değişebileceğini göremiyorum. newIndexAslında azaltmak testimi bozar (aşağıdaki cevabıma bakın).
Ben Foster

1
Not: iplik güvenliği önemliyse, bunların hepsi bir lockifadenin içinde olmalıdır .
rory.ap

1
Bunu birkaç nedenden dolayı kafa karıştırıcı olduğu için kullanmam. Listede bir Move (oldIndex, newIndex) yöntemi tanımlamak ve Move (15,25) ve ardından Move (25,15) öğelerini çağırmak bir kimlik değil takastır. Ayrıca Taşı (15,25) öğeyi beklediğim 25 değil, 24 dizinine taşımak yapar. Ayrıca takas, temp = item [oldindex]; et [oldindex] = maddesi [newindex]; et [newindex] = temp; büyük dizilerde daha verimli görünüyor. Ayrıca Move (0,0) ve Move (0,1) de aynı olur, bu da tuhaftır. Ayrıca Taşı (0, Sayı -1) öğeyi sonuna kadar taşımaz.
Wouter

12

Bu sorunun eski olduğunu biliyorum ama C # javascript kodu BU yanıtı adapte . Umarım yardımcı olur

 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

Liste <T> .Remove () ve Liste <T> .RemoveAt () kaldırılmakta olan öğeyi döndürmez.

Bu nedenle bunu kullanmalısınız:

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

5

Öğe yerleştirin şu anda oldIndexolmak newIndexve daha sonra orijinal örneğini kaldırın.

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

Kaldırmak istediğiniz öğenin dizininin ekleme nedeniyle değişebileceğini dikkate almanız gerekir.


1
Eklemeden önce kaldırmanız gerekir ... siparişiniz listenin ayırma yapmasına neden olabilir.
Jim Balter

4

Bir listedeki öğeleri taşımak için bir uzantı yöntemi oluşturdum.

Bir öğeyi listedeki mevcut bir dizin konumuna taşıdığımızdan, mevcut bir öğeyi taşıdığımızda dizin değişmemelidir.

@Oliver'ın aşağıda belirttiği kenar durumu (bir öğeyi listenin sonuna taşımak) aslında testlerin başarısız olmasına neden olur, ancak bu tasarım gereğidir. Listenin sonuna yeni bir öğe eklemek için aramamız yeterli List<T>.Add. taşıma işleminden önce bu dizin konumu olmadığından başarısız list.Move(predicate, list.Count) olmalıdır .

Her durumda, iki ek uzantı yöntemi oluşturdum MoveToEndve MoveToBeginningkaynağı burada bulunabilir .

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

Nerede Ensure.Argumenttanımlı?
Oliver

1
Normalde , listenin sonuna bir şey yerleştirmek için List<T>arayabilirsiniz Insert(list.Count, element). Yani aslında başarısız olanı When_new_index_is_higheraramalısınız list.Move(x => x == 3, 5).
Oliver

3
@Oliver normalde List<T>sadece listenin sonuna yeni bir öğe .Addeklemek için çağırırdım . Ne zaman hareketli tek tek öğeleri biz sadece tek bir öğe çıkarıp yeniden takmayı konum beri dizinin orijinal boyutunu artırmak asla. Cevabımdaki bağlantıya tıklarsanız, kodunu bulacaksınız . Ensure.Argument
Ben Foster

Çözüm, hedef dizinin iki öğe arasında değil, bir konum olmasını bekler. Bu, bazı kullanım durumları için iyi çalışmasına rağmen, diğerleri için çalışmaz. Ayrıca, hamleniz sonuna kadar taşınmayı desteklemiyor (Oliver tarafından belirtildiği gibi), ancak kodunuzda bu kısıtlamayı belirtmiyorsunuz. Aynı zamanda mantıksız, eğer 20 elemanlı bir listem varsa ve eleman 10'u sonuna taşımak istiyorsanız, nesne referansını kaydetmek için bulmak yerine nesneyi listeden kaldırmak yerine Taşı yönteminin bunu işlemesini beklerdim. ve nesneyi ekleyin.
Trisped

1
Sonuna kadar öğe taşımak, benim cevap okursanız aslında @Trisped / listenin başında olduğu destekledi. Teknik özellikleri burada görebilirsiniz . Evet kodum, dizinin listede geçerli (mevcut) bir konum olmasını bekliyor. Biz edilir hareketli koymadan değil, öğeleri.
Ben Foster

1

Ben de beklenir:

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

... veya:

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

... hile yapacaktı, ama kontrol etmek için bu makinede VS yok.


1
@GarryShutler Duruma göre değişir. Arayüzünüz kullanıcının listedeki konumu dizine göre belirlemesine izin veriyorsa, 15. maddeye 20'ye geçmesini söylediklerinde karıştırılır, bunun yerine 19'a taşınır. Arayüzünüz kullanıcının bir öğeyi diğerlerine sürüklemesine izin verirse karışacaktır. listede daha sonra olması newIndexhalinde azaltılması mantıklı olacaktır oldIndex.
Trisped

-1

En basit yol:

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

DÜZENLE

Soru çok net değil ... Maddenin nereye list[newIndex]gittiğini umursamadığımız için bunu yapmanın en basit yolunun aşağıdaki gibi olduğunu düşünüyorum (bir uzantı yöntemi ile veya bir uzantı yöntemi olmadan):

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

Bu çözüm en hızlı çözümdür, çünkü liste ekleme / kaldırma işlemlerini içermez.


4
Bu, ekleme değil, newIndex öğesinin üzerine yazılacaktır.
Garry Shutler

@Garry Sonuç aynı olmayacak mı?
Ozgur Ozcitak

4
Hayır, yerleştirdiğinizde gerçekleşmeyecek olan newIndex'deki değeri kaybedersiniz.
Garry Shutler

-2

Daha basit çocuklar bunu yapar mı

    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 için de aynısını yapın ancak "if (ind! = Concepts.Count ())" ve Concepts.Insert (ind + 1, item.ToString ());


-3

Bir taşıma elemanı uzantısı yöntemini bu şekilde uyguladım. Oldukça iyi elemanlar için önce / sonra ve aşırı hareket eder.

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
Bu Francisco'nun cevabının bir kopyası.
nivs1978
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.