Zamień dwie pozycje na liście

Czy istnieje sposób LINQ na zamianę pozycji dwóch elementów wewnątrz list<T>?

 52
Author: Tony The Lion, 2010-01-19

5 answers

Sprawdź odpowiedź Marc z C#: dobra / najlepsza implementacja metody Swap .

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

Które mogą być linq-i-fied jak

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);
 83
Author: Jan Jongboom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:37

Może ktoś wymyśli na to sprytny sposób, ale nie powinieneś. Zamiana dwóch pozycji na liście jest z natury efektem ubocznym, ale operacje LINQ powinny być bez efektu ubocznego. Dlatego wystarczy użyć prostej metody rozszerzenia:

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;
    }
}
 29
Author: jason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-19 18:07:05

Nie istnieje żadna metoda wymiany, więc musisz ją utworzyć samodzielnie. Oczywiście można linqify, ale trzeba to zrobić z jednym (niepisanym?) rules in mind: LINQ-operations do not change the input parameters!

W innych odpowiedziach "linqify" lista (input) jest modyfikowana i zwracana, ale ta akcja hamuje tę regułę. Jeśli byłoby dziwne, jeśli masz listę z nieposortowanymi elementami, wykonaj operację LINQ "OrderBy" i odkryj, że lista wejściowa jest również posortowana (tak jak wynik). To nie może się zdarzyć!

Więc... jak to zrobimy?

Moją pierwszą myślą było przywrócenie kolekcji po jej zakończeniu. Ale to jest brudne rozwiązanie, więc go nie używaj:

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

To rozwiązanie jest brudne, ponieważ modyfikuje listę wejściową, nawet jeśli przywraca ją do pierwotnego stanu. Może to spowodować kilka problemów:

  1. lista może być tylko odczytywana, co spowoduje wyrzucenie wyjątku.
  2. jeśli lista jest współdzielona przez wiele wątków, lista zmieni się dla innych wątków w czasie trwania tej funkcji.
  3. jeśli podczas iteracji wystąpi wyjątek, lista nie zostanie przywrócona. (Można to rozwiązać pisząc try-finally wewnątrz funkcji Swap i umieszczając kod przywracania wewnątrz bloku finally).

Istnieje lepsze (i krótsze) rozwiązanie: po prostu zrób kopię oryginalnej listy. (Dzięki temu możliwe jest również użycie liczby mnogiej jako parametr, zamiast 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;
}
Jedną z wad tego rozwiązania jest to, że kopiuje całą listę, która zużywa pamięć, a to sprawia, że rozwiązanie jest raczej powolne.

Możesz rozważyć następujące rozwiązanie:

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];
    }
}

I jeśli chcesz wprowadzić parametr jako liczbę liczbową:

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 tworzy również kopię (podzbiór) źródła. Tak więc najgorszy scenariusz, jest tak powolny i zużywa pamięć jak funkcja Swap2.

 9
Author: Martin Mulder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-21 21:38:01

Lista posiada metodę odwrotną.

your_list.Reverse(i, 2) // will swap elements with indexs i, i + 1. 

Źródło: https://msdn.microsoft.com/en-us/library/hf2ay11y (v=vs.110). aspx

 2
Author: user1920925,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-28 15:48:04

Jeśli kolejność ma znaczenie, należy zachować właściwość na " t " obiektów na liście, która oznacza sekwencję. Aby je zamienić, po prostu zamień wartość tej właściwości, a następnie użyj tego w .Sort (porównanie z właściwością sequence )

 0
Author: CaffGeek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-19 14:51:59