Поменяйте местами два элемента в списке ‹T›

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


person Tony The Lion    schedule 19.01.2010    source источник
comment
Почему это так важно, почему он хочет это сделать. Люди, которые ищут в Google элементы списка подкачки C #, захотят получить прямой ответ на этот конкретный вопрос.   -  person Daniel Macias    schedule 24.05.2012
comment
@DanielMacias Это правда. Эти ответы похожи на «но зачем ты это делаешь?» так раздражают. Я думаю, что нужно дать хотя бы жизнеспособный ответ, прежде чем пытаться спорить, почему.   -  person julealgon    schedule 11.02.2014
comment
почему вы хотите использовать LINQ для этого? если LINQ специфичен, почему бы не изменить заголовок, чтобы добавить LINQ   -  person ina    schedule 03.05.2017


Ответы (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);
person Jan Jongboom    schedule 19.01.2010
comment
Почему этот метод расширения должен возвращать список? Вы изменяете список на месте. - person vargonian; 06.04.2012
comment
Затем вы можете связать методы. - person Jan Jongboom; 07.04.2012
comment
не забудьте сделать метод расширения общедоступным, если вам не терпится использовать его в Unity3D! (Unity не сможет его найти, если вы этого не сделаете) - person col000r; 23.09.2014
comment
Легко и приятно. Спасибо - person fnc12; 19.03.2015
comment
@ col000r: Разве каждый метод не должен быть общедоступным, чтобы быть доступным вне класса? - person Jack; 30.08.2015
comment
У меня Список ‹T› не содержит определения «своп», как я могу использовать его в моем списке? - person Yohan Dahmani; 06.06.2018
comment
Предупреждение: версия LINQified по-прежнему изменяет исходный список. - person Philipp Michalski; 15.03.2019

Может быть, кто-то придумает, как это сделать хитро, но не стоит. Перестановка двух элементов в списке по своей сути является побочным эффектом, но операции 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;
    }
}
person jason    schedule 19.01.2010
comment
Побочные эффекты? Вы можете уточнить? - person Tony The Lion; 19.01.2010
comment
Под побочными эффектами он подразумевает, что они изменяют список и, возможно, элементы в списке, а не просто запрашивают данные, которые ничего не меняют. - person saret; 19.01.2010
comment
делает ли T temp = list [firstIndex]; создать глубокую копию объекта в списке? - person Paul McCarthy; 03.03.2020
comment
@PaulMcCarthy Нет, он создает новый указатель на исходный объект, никакого копирования. - person NetMage; 20.08.2020

List<T> имеет Reverse() метод, однако он меняет порядок только двух (или более) последовательных элементов.

your_list.Reverse(index, 2);

Второй параметр 2 указывает, что мы меняем порядок 2 элементов на обратный, начиная с элемента в данном index.

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

person user1920925    schedule 25.09.2017
comment
Хотя Google привел меня сюда, это то, что я искал. - person Jnr; 16.05.2018
comment
Функция .Reverse () не является Linq и не называется Swap, но похоже, что цель вопроса состоит в том, чтобы найти встроенную функцию, которая выполняет обмен. Это единственный ответ, обеспечивающий это. - person Americus; 28.08.2018
comment
Но полезно только для замены соседних элементов, так что это не совсем ответ. - person NetMage; 20.08.2020

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

person Martin Mulder    schedule 21.04.2013

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

person CaffGeek    schedule 19.01.2010
comment
Это если вы можете концептуально согласиться с тем, что ваш T имеет собственный порядок, но не если вы хотите, чтобы он сортировался произвольным образом без внутреннего порядка, например, в пользовательском интерфейсе. - person Dave Van den Eynde; 30.01.2010
comment
@DaveVandenEynde, я довольно начинающий программист, поэтому, пожалуйста, простите меня, если это не большой вопрос: в чем смысл замены элементов в списке, если ситуация не связана с концепцией порядка? - person Mathieu K.; 01.03.2017
comment
@ MathieuK. Я хотел сказать, что этот ответ предполагает, что порядок выводится из свойства объектов, по которым последовательность может быть отсортирована. Обычно это не так. - person Dave Van den Eynde; 01.03.2017
comment
Я (возможно, неправильно) понимаю, что этот ответ заключается в том, чтобы сказать: пронумеровать ваши объекты, сохраняя числа в свойстве. Затем, чтобы поменять местами два элемента, поменяйте местами их номера. Используйте список, отсортированный по этому свойству. - person Mathieu K.; 04.03.2017