Поменяйте местами два элемента в списке <T> - PullRequest
61 голосов
/ 19 января 2010

Есть ли способ LINQ, чтобы поменять местами два элемента внутри list<T>?

Ответы [ 5 ]

98 голосов
/ 19 января 2010

Проверьте ответ от Марка от C #: Хорошая / лучшая реализация метода Swap .

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

который может быть linq-i-fied как

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);
30 голосов
/ 19 января 2010

Может быть, кто-то придумает умный способ сделать это, но вы не должны. Обмен двух элементов в списке по своей природе связан с побочными эффектами, но операции LINQ не должны иметь побочных эффектов. Таким образом, просто используйте простой метод расширения:

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;
    }
}
10 голосов
/ 22 апреля 2013

Не существует Swap-метода, поэтому вы должны создать его самостоятельно.Конечно, вы можете связать его, но это нужно делать с одним (неписанным?) Правилом: LINQ-операции не изменяют входные параметры!

В других ответах «linqify» (вход) список изменен и возвращен, но это действие нарушает это правило.Если было бы странно, если у вас есть список с несортированными элементами, выполните операцию LINQ "OrderBy" и обнаружите, что входной список также отсортирован (как результат).Этого не должно быть!

Итак ... как нам это сделать?

Моей первой мыслью было просто восстановить коллекцию после того, как она закончила итерацию.Но это грязное решение, поэтому не используйте его:

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

Это грязное решение, потому что изменяет список ввода, даже если он восстанавливаетэто в исходное состояние.Это может вызвать несколько проблем:

  1. Список может быть доступен только для чтения, что вызовет исключение.
  2. Если список используется несколькими потоками, список изменится для других потоков во время выполнения этой функции.
  3. Если во время итерации возникает исключение, список не будет восстановлен,(Это можно решить, написав try-finally внутри Swap-функции и поместив код восстановления в блок finally).

Существует лучшее (и более короткое) решение: простосделайте копию оригинального списка.(Это также позволяет использовать IEnumerable в качестве параметра вместо 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;
}

Одним из недостатков этого решения является то, что оно копирует весь список, который будет использовать память, и это делает решениедовольно медленно

Вы могли бы рассмотреть следующее решение:

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

И если вы хотите ввести параметр, равный IEnumerable:

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 также делает копию (aподмножество) источника.В худшем случае, он такой же медленный и занимает много памяти, как функция Swap2.

6 голосов
/ 25 сентября 2017

Список имеет обратный метод.

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

Источник: https://msdn.microsoft.com/en-us/library/hf2ay11y(v=vs.110).aspx

0 голосов
/ 19 января 2010

Если порядок имеет значение, вам следует сохранить свойство в списке объектов "T" в вашем списке, которое обозначает последовательность. Чтобы поменять их местами, просто поменяйте значение этого свойства, а затем используйте его в .Sort ( сравнение со свойством последовательности )

...