Подтвердить что ты не робот

Смените два элемента в списке <T>

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

4b9b3361

Ответ 1

Проверьте ответ от Marc из С#: Хорошая/лучшая реализация метода 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);

Ответ 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;
    }
}

Ответ 3

Нет существующего Swap-метода, поэтому вы должны создать его самостоятельно. Конечно, вы можете его linqify, но это нужно делать с одним (неписаным?) Правилами: 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;
}

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

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

Ответ 4

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