Generische Liste - Verschieben eines Elements innerhalb der Liste

155

Ich habe also eine generische Liste und einen oldIndexund einen newIndexWert.

Ich möchte den Gegenstand verschieben oldIndex, um newIndex... so einfach wie möglich.

Irgendwelche Vorschläge?

Hinweis

Der Gegenstand sollte zwischen den Gegenständen am (newIndex - 1)und newIndex vor dem Entfernen liegen.

Richard Ev
quelle
1
Sie sollten die Antwort ändern, die Sie angekreuzt haben. Der mit newIndex--führt nicht zu dem Verhalten, von dem Sie sagten, dass Sie es wollten.
Miral
1
@Miral - welche Antwort sollte Ihrer Meinung nach akzeptiert werden?
Richard Ev
4
jpiersons. Dies führt dazu, dass sich das Objekt, das sich vor dem Verschieben bei oldIndex befand, nach dem Verschieben bei newIndex befand. Dies ist das am wenigsten überraschende Verhalten (und es ist das, was ich brauchte, als ich einen Drag'n'Drop-Neuordnungscode schrieb). Zugegeben, er spricht davon ObservableCollectionund ist kein Generikum List<T>, aber es ist trivial, einfach die Methodenaufrufe auszutauschen, um das gleiche Ergebnis zu erzielen.
Miral
Das angeforderte (und in dieser Antwort korrekt implementierte ) Verhalten zum Verschieben des Elements zwischen den Elementen um [newIndex - 1]und [newIndex]ist nicht invertierbar. Move(1, 3); Move(3, 1);bringt die Liste nicht in den Ausgangszustand zurück. Mittlerweile gibt es ein anderes Verhalten zur Verfügung gestellt in ObservableCollectionund in dieser Antwort erwähnt , das ist umkehrbar .
Lightman

Antworten:

138

Ich weiß, dass Sie "generische Liste" gesagt haben, aber Sie haben nicht angegeben, dass Sie die List (T) -Klasse verwenden müssen. Hier ist ein Schuss auf etwas anderes.

Die ObservableCollection (T) -Klasse verfügt über eine Move-Methode , die genau das tut, was Sie wollen.

public void Move(int oldIndex, int newIndex)

Darunter ist es grundsätzlich so implementiert.

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

Wie Sie sehen können, ist die von anderen vorgeschlagene Swap-Methode im Wesentlichen das, was die ObservableCollection in ihrer eigenen Move-Methode tut.

UPDATE 30.12.2015: Sie können den Quellcode für die Methoden Move und MoveItem in corefx jetzt selbst anzeigen, ohne Reflector / ILSpy zu verwenden, da .NET Open Source ist.

jpierson
quelle
28
Ich frage mich, warum dies nicht auch in der Liste <T> implementiert ist, jemand, der etwas Licht ins Dunkel bringt?
Andreas
Was ist der Unterschied zwischen einer generischen Liste und einer List (T) -Klasse? Ich dachte, sie
wären
Ein „generische Liste“ könnte jede Art von Liste oder Sammlung wie Datenstruktur in .NET bedeuten , die ObservableCollection (T) oder anderer Klassen enthalten könnte , die eine Implementierung kann listy Schnittstelle wie IList / ICollection / IEnumerable.
Jpierson
6
Könnte jemand bitte erklären, warum es tatsächlich keine Verschiebung des Zielindex gibt (falls dieser größer als der Quellindex ist)?
Vladius
@vladius Ich glaube, die Idee ist, dass der angegebene newIndex-Wert einfach den gewünschten Index angeben sollte, den das Element nach dem Verschieben haben soll, und da eine Einfügung verwendet wird, gibt es keinen Grund für eine Anpassung. Wenn newIndex eine Position relativ zum ursprünglichen Index wäre, wäre das eine andere Geschichte, aber so funktioniert es nicht.
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);
Garry Shutler
quelle
9
Ihre Lösung bricht zusammen, wenn die Liste zwei Kopien des Elements enthält, von denen eine vor oldIndex auftritt. Sie sollten RemoveAt verwenden, um sicherzustellen, dass Sie das richtige erhalten.
Aaron Maenpaa
6
In der Tat, hinterhältiger Fall
Garry Shutler
1
@GarryShutler Ich kann nicht sehen, wie sich der Index verschieben könnte, wenn wir ein einzelnes Element entfernen und dann einfügen. Das Dekrementieren newIndexbricht meinen Test tatsächlich ab (siehe meine Antwort unten).
Ben Foster
1
Hinweis: Wenn die Thread-Sicherheit wichtig ist, sollte dies alles in einer lockAnweisung enthalten sein.
rory.ap
1
Ich würde dies nicht verwenden, da es aus mehreren Gründen verwirrend ist. Definieren einer Methode Move (oldIndex, newIndex) in einer Liste und Aufrufen von Move (15,25) und dann Move (25,15) ist keine Identität, sondern ein Swap. Durch Verschieben (15,25) wird das Element auf Index 24 verschoben und nicht auf 25, was ich erwarten würde. Außerdem kann das Austauschen durch temp = item [oldindex] implementiert werden; item [oldindex] = item [newindex]; item [newindex] = temp; Das scheint auf großen Arrays effizienter zu sein. Auch Move (0,0) und Move (0,1) wären gleich, was ebenfalls ungerade ist. Und auch Verschieben (0, Anzahl -1) verschiebt den Gegenstand nicht zum Ende.
Wouter
12

Ich weiß, dass diese Frage alt ist, aber ich habe DIESE Antwort des Javascript-Codes an C # angepasst . Ich hoffe es hilft

 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;
}
Francisco
quelle
9

List <T> .Remove () und List <T> .RemoveAt () geben das zu entfernende Element nicht zurück.

Deshalb müssen Sie dies verwenden:

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

Legen Sie das Element derzeit auf oldIndexseinen an newIndexund dann die ursprüngliche Instanz entfernen.

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

Sie müssen berücksichtigen, dass sich der Index des Elements, das Sie entfernen möchten, aufgrund des Einfügens ändern kann.

Megacan
quelle
1
Sie sollten vor dem Einfügen entfernen ... Ihre Bestellung kann dazu führen, dass die Liste eine Zuordnung vornimmt.
Jim Balter
4

Ich habe eine Erweiterungsmethode zum Verschieben von Elementen in einer Liste erstellt.

Ein Index sollte sich nicht verschieben, wenn wir ein vorhandenes Element verschieben, da wir ein Element an eine vorhandene Indexposition in der Liste verschieben.

Der Randfall, auf den sich @Oliver unten bezieht (Verschieben eines Elements an das Ende der Liste), würde tatsächlich dazu führen, dass die Tests fehlschlagen. Dies ist jedoch beabsichtigt. Um ein neues Element am Ende der Liste einzufügen, rufen wir einfach an List<T>.Add. list.Move(predicate, list.Count) sollte fehlschlagen, da diese Indexposition vor dem Verschieben nicht vorhanden ist.

Auf jeden Fall habe ich zwei zusätzliche Erweiterungsmethoden erstellt, MoveToEndund MoveToBeginningdie Quelle von denen gefunden werden kann hier .

/// <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);
        };
    }
}
Ben Foster
quelle
Wo ist Ensure.Argumentdefiniert?
Oliver
1
Normalerweise können List<T>Sie anrufen Insert(list.Count, element), um etwas am Ende der Liste zu platzieren. Sie When_new_index_is_highersollten also anrufen, list.Move(x => x == 3, 5)was tatsächlich fehlschlägt.
Oliver
3
@Oliver in einem normalen List<T>würde ich einfach anrufen .Add, um ein neues Element am Ende einer Liste einzufügen . Beim Verschieben einzelner Elemente wird die ursprüngliche Größe des Index niemals erhöht, da nur ein einzelnes Element entfernt und erneut eingefügt wird. Wenn Sie auf den Link in meiner Antwort klicken, finden Sie den Code für Ensure.Argument.
Ben Foster
Ihre Lösung erwartet, dass der Zielindex eine Position ist, nicht zwischen zwei Elementen. Während dies für einige Anwendungsfälle gut funktioniert, funktioniert es für andere nicht. Außerdem unterstützt Ihr Umzug nicht das Verschieben bis zum Ende (wie von Oliver angegeben), aber nirgends in Ihrem Code geben Sie diese Einschränkung an. Es ist auch nicht intuitiv, wenn ich eine Liste mit 20 Elementen habe und Element 10 an das Ende verschieben möchte, würde ich erwarten, dass die Verschiebungsmethode dies behandelt, anstatt zu suchen, um die Objektreferenz zu speichern, und das Objekt aus der Liste zu entfernen und fügen Sie das Objekt hinzu.
Trisped
1
@Trisped eigentlich , wenn Sie meine Antwort zu lesen, um ein Element zu Ende zu bewegen / Anfang der Liste wird nicht unterstützt. Sie können die Spezifikationen hier sehen . Ja, mein Code erwartet, dass der Index eine gültige (vorhandene) Position in der Liste ist. Wir verschieben Elemente und fügen sie nicht ein.
Ben Foster
1

Ich würde entweder erwarten:

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

... oder:

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

... würde den Trick machen, aber ich habe kein VS auf diesem Computer, um es zu überprüfen.

Aaron Maenpaa
quelle
1
@GarryShutler Das hängt von der Situation ab. Wenn Ihre Benutzeroberfläche es dem Benutzer ermöglicht, die Position in der Liste anhand des Index anzugeben, sind sie verwirrt, wenn sie Element 15 anweisen, auf 20 zu wechseln, stattdessen jedoch auf 19. Wenn Ihre Benutzeroberfläche es dem Benutzer ermöglicht, ein Element zwischen andere zu ziehen auf der Liste wäre es dann sinnvoll zu dekrementieren, newIndexwenn es danach ist oldIndex.
Trisped
-1

Einfachster Weg:

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

BEARBEITEN

Die Frage ist nicht sehr klar ... Da es uns egal ist, wohin der list[newIndex]Artikel geht, ist meiner Meinung nach der einfachste Weg, dies zu tun (mit oder ohne Erweiterungsmethode):

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

Diese Lösung ist die schnellste, da keine Listen eingefügt / entfernt werden müssen.

bruno conde
quelle
4
Dadurch wird das Element bei newIndex überschrieben und nicht eingefügt.
Garry Shutler
@Garry Wird das Endergebnis nicht dasselbe sein?
Ozgur Ozcitak
4
Nein, Sie verlieren am Ende den Wert bei newIndex, was beim Einfügen nicht passieren würde.
Garry Shutler
-2

Ist einfacher Jungs machen das einfach

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

Machen Sie dasselbe mit MoveDown, aber ändern Sie das if für "if (ind! = Concepts.Count ())" und das Concepts.Insert (ind + 1, item.ToString ());

Richard Aguirre
quelle
-3

So habe ich eine Erweiterungsmethode für Verschiebungselemente implementiert. Es handhabt das Bewegen vor / nach und bis zum Äußersten für Elemente ziemlich gut.

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);
  }
}
Allan Harper
quelle
2
Dies ist ein Duplikat der Antwort von Francisco.
nivs1978