Tauschen Sie zwei Elemente in Liste <T> aus

81

Gibt es eine LINQ-Möglichkeit, die Position von zwei Elementen innerhalb eines zu tauschen list<T>?

Tony der Löwe
quelle
57
Warum ist es wichtig, warum er das tun will? Leute, die nach "Swap List Items C #" googeln, wollen eine klare Antwort auf diese spezielle Frage.
Daniel Macias
10
@ DanielMacias Das ist so wahr. Diese Antworten lauten wie "Aber warum machst du das?" sind so nervig. Ich denke, dass man zumindest eine brauchbare Antwort geben sollte, bevor man versucht, das Warum zu argumentieren.
Julealgon
Warum möchten Sie dazu LINQ verwenden? Wenn LINQ spezifisch ist, warum nicht den Titel ändern, um LINQ hinzuzufügen
ina

Antworten:

118

Überprüfen Sie die Antwort von Marc aus C #: Gute / beste Implementierung der Swap-Methode .

public static void Swap<T>(IList<T> list, int indexA, int indexB)
{
    T tmp = list[indexA];
    list[indexA] = list[indexB];
    list[indexB] = tmp;
}

das kann wie linq-i-fied sein

public static IList<T> Swap<T>(this IList<T> list, int indexA, int indexB)
{
    T tmp = list[indexA];
    list[indexA] = list[indexB];
    list[indexB] = tmp;
    return list;
}

var lst = new List<int>() { 8, 3, 2, 4 };
lst = lst.Swap(1, 2);
Jan Jongboom
quelle
26
Dies ist nicht LINQified.
Jason
10
Warum muss diese Erweiterungsmethode eine Liste zurückgeben? Sie ändern die Liste direkt.
Vargonian
12
Dann können Sie die Methoden verketten.
Jan Jongboom
3
"Genericized" vielleicht?
Rhys van der Waerden
5
Warnung: Die "LINQified" -Version ändert weiterhin die ursprüngliche Liste.
Philipp Michalski
32

Vielleicht wird sich jemand einen cleveren Weg überlegen, dies zu tun, aber das sollten Sie nicht. Das Austauschen von zwei Elementen in einer Liste ist von Natur aus mit Nebenwirkungen verbunden, LINQ-Vorgänge sollten jedoch ohne Nebenwirkungen sein. Verwenden Sie daher einfach eine einfache Erweiterungsmethode:

static class IListExtensions {
    public static void Swap<T>(
        this IList<T> list,
        int firstIndex,
        int secondIndex
    ) {
        Contract.Requires(list != null);
        Contract.Requires(firstIndex >= 0 && firstIndex < list.Count);
        Contract.Requires(secondIndex >= 0 && secondIndex < list.Count);
        if (firstIndex == secondIndex) {
            return;
        }
        T temp = list[firstIndex];
        list[firstIndex] = list[secondIndex];
        list[secondIndex] = temp;
    }
}
Jason
quelle
Nebenwirkungen? Können Sie das näher erläutern?
Tony der Löwe
12
Mit Nebenwirkungen meint er, dass sie die Liste und möglicherweise die Elemente in der Liste ändern - im Gegensatz zum Abfragen von Daten, die nichts ändern würden
saret
tut "T temp = list [firstIndex];" Eine Deep-Copy des Objekts in der Liste erstellen?
Paul McCarthy
1
@PaulMcCarthy Nein, es wird ein neuer Zeiger auf das ursprüngliche Objekt erstellt, überhaupt kein Kopieren.
NetMage
12

List<T>hat eine Reverse()Methode, kehrt jedoch nur die Reihenfolge von zwei (oder mehr) aufeinanderfolgenden Elementen um.

your_list.Reverse(index, 2);

Wenn der zweite Parameter 2angibt, kehren wir die Reihenfolge von 2 Elementen um, beginnend mit dem angegebenen Element index.

Quelle: https://msdn.microsoft.com/en-us/library/hf2ay11y(v=vs.110).aspx

user1920925
quelle
1
Obwohl Google mich hierher gebracht hat, habe ich danach gesucht.
Jnr
1
Die .Reverse () -Funktion ist nicht Linq und heißt nicht Swap, aber es scheint, dass die Absicht der Frage darin besteht, eine "eingebaute" Funktion zu finden, die einen Swap ausführt. Dies ist die einzige Antwort, die dies liefert.
Americus
Aber nur nützlich, um benachbarte Elemente auszutauschen, also keine wirkliche Antwort.
NetMage
10

Es gibt keine Swap-Methode, daher müssen Sie selbst eine erstellen. Natürlich können Sie es linqifizieren, aber das muss unter Berücksichtigung einer (ungeschriebenen?) Regel erfolgen: LINQ-Operationen ändern die Eingabeparameter nicht!

In den anderen "linqify" -Antworten wird die (Eingabe-) Liste geändert und zurückgegeben, aber diese Aktion bremst diese Regel. Wenn es seltsam wäre, wenn Sie eine Liste mit unsortierten Elementen haben, führen Sie eine LINQ "OrderBy" -Operation durch und stellen Sie dann fest, dass die Eingabeliste ebenfalls sortiert ist (genau wie das Ergebnis). Das darf nicht passieren!

Also ... wie machen wir das?

Mein erster Gedanke war nur, die Sammlung wiederherzustellen, nachdem die Iteration abgeschlossen war. Dies ist jedoch eine schmutzige Lösung. Verwenden Sie sie daher nicht:

static public IEnumerable<T> Swap1<T>(this IList<T> source, int index1, int index2)
{
    // Parameter checking is skipped in this example.

    // Swap the items.
    T temp = source[index1];
    source[index1] = source[index2];
    source[index2] = temp;

    // Return the items in the new order.
    foreach (T item in source)
        yield return item;

    // Restore the collection.
    source[index2] = source[index1];
    source[index1] = temp;
}

Diese Lösung ist fehlerhaft, da sie die Eingabeliste ändert , auch wenn sie den ursprünglichen Zustand wiederherstellt. Dies kann verschiedene Probleme verursachen:

  1. Die Liste kann schreibgeschützt sein, wodurch eine Ausnahme ausgelöst wird.
  2. Wenn die Liste von mehreren Threads gemeinsam genutzt wird, ändert sich die Liste für die anderen Threads während der Dauer dieser Funktion.
  3. Wenn während der Iteration eine Ausnahme auftritt, wird die Liste nicht wiederhergestellt. (Dies könnte behoben werden, indem ein try-finally in die Swap-Funktion geschrieben und der Wiederherstellungscode in den finally-Block eingefügt wird.)

Es gibt eine bessere (und kürzere) Lösung: Erstellen Sie einfach eine Kopie der ursprünglichen Liste. (Dies ermöglicht auch die Verwendung einer IEnumerable als Parameter anstelle einer IList):

static public IEnumerable<T> Swap2<T>(this IList<T> source, int index1, int index2)
{
    // Parameter checking is skipped in this example.

    // If nothing needs to be swapped, just return the original collection.
    if (index1 == index2)
        return source;

    // Make a copy.
    List<T> copy = source.ToList();

    // Swap the items.
    T temp = copy[index1];
    copy[index1] = copy[index2];
    copy[index2] = temp;

    // Return the copy with the swapped items.
    return copy;
}

Ein Nachteil dieser Lösung besteht darin, dass sie die gesamte Liste kopiert, die Speicher verbraucht, und die Lösung dadurch ziemlich langsam wird.

Sie könnten die folgende Lösung in Betracht ziehen:

static public IEnumerable<T> Swap3<T>(this IList<T> source, int index1, int index2)
{
    // Parameter checking is skipped in this example.
    // It is assumed that index1 < index2. Otherwise a check should be build in and both indexes should be swapped.

    using (IEnumerator<T> e = source.GetEnumerator())
    {
        // Iterate to the first index.
        for (int i = 0; i < index1; i++)
            yield return source[i];

        // Return the item at the second index.
        yield return source[index2];

        if (index1 != index2)
        {
            // Return the items between the first and second index.
            for (int i = index1 + 1; i < index2; i++)
                yield return source[i];

            // Return the item at the first index.
            yield return source[index1];
        }

        // Return the remaining items.
        for (int i = index2 + 1; i < source.Count; i++)
            yield return source[i];
    }
}

Und wenn Sie einen Parameter eingeben möchten, der IEnumerable ist:

static public IEnumerable<T> Swap4<T>(this IEnumerable<T> source, int index1, int index2)
{
    // Parameter checking is skipped in this example.
    // It is assumed that index1 < index2. Otherwise a check should be build in and both indexes should be swapped.

    using(IEnumerator<T> e = source.GetEnumerator())
    {
        // Iterate to the first index.
        for(int i = 0; i < index1; i++) 
        {
            if (!e.MoveNext())
                yield break;
            yield return e.Current;
        }

        if (index1 != index2)
        {
            // Remember the item at the first position.
            if (!e.MoveNext())
                yield break;
            T rememberedItem = e.Current;

            // Store the items between the first and second index in a temporary list. 
            List<T> subset = new List<T>(index2 - index1 - 1);
            for (int i = index1 + 1; i < index2; i++)
            {
                if (!e.MoveNext())
                    break;
                subset.Add(e.Current);
            }

            // Return the item at the second index.
            if (e.MoveNext())
                yield return e.Current;

            // Return the items in the subset.
            foreach (T item in subset)
                yield return item;

            // Return the first (remembered) item.
            yield return rememberedItem;
        }

        // Return the remaining items in the list.
        while (e.MoveNext())
            yield return e.Current;
    }
}

Swap4 erstellt auch eine Kopie (eine Teilmenge davon) der Quelle. Im schlimmsten Fall ist es also so langsam und speicherintensiv wie die Funktion Swap2.

Martin Mulder
quelle
0

Wenn die Reihenfolge wichtig ist, sollten Sie eine Eigenschaft für die "T" -Objekte in Ihrer Liste behalten, die die Reihenfolge angibt. Um sie auszutauschen, tauschen Sie einfach den Wert dieser Eigenschaft aus und verwenden Sie ihn dann in der .Sort ( Vergleich mit der Sequenzeigenschaft ).

CaffGeek
quelle
Das ist, wenn Sie konzeptionell zustimmen können, dass Ihr T eine inhärente "Reihenfolge" hat, aber nicht, wenn Sie möchten, dass es auf willkürliche Weise ohne inhärente "Reihenfolge" sortiert wird, wie in einer Benutzeroberfläche.
Dave Van den Eynde
@ DaveVandenEynde, ich bin ein ziemlich unerfahrener Programmierer. Bitte verzeihen Sie mir, wenn dies keine gute Frage ist: Was bedeutet es, Elemente in einer Liste auszutauschen, wenn die Situation kein Ordnungskonzept beinhaltet?
Mathieu K.
@ MathieuK.well Ich wollte damit sagen, dass diese Antwort vorschlägt, dass die Reihenfolge von einer Eigenschaft für die Objekte abgeleitet wird, nach denen die Sequenz sortiert werden kann . Das ist normalerweise nicht der Fall.
Dave Van den Eynde
Ich verstehe (vielleicht falsch) diese Antwort so, dass ich sage: "Nummerieren Sie Ihre Objekte, speichern Sie die Nummern in einer Eigenschaft. Um zwei Elemente auszutauschen, tauschen Sie ihre Nummern aus. Verwenden Sie die nach dieser Eigenschaft sortierte Liste."
Mathieu K.