Поменять местами два элемента в списке


есть ли LINQ способ поменять положение двух элементов внутри list<T>?

5 52

5 ответов:

Проверьте ответ от Марка от 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);

возможно, кто-то придумает умный способ сделать это, но вы не должны. замена двух элементов в списке по своей сути является побочным эффектом, но операции 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;
    }
}

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

в других ответах "linqify" список (input) изменяется и возвращается, но это действие тормозит это правило. Если было бы странно, если у вас есть список с несортированными элементами, выполните операцию 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-block).

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

своп 4 также делает копию (подмножество) источник. Так что хуже всего сценарий случая, он так же медленен и потребляет память, как функция Swap2.

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

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

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