Поміняйте місцями два елементи у списку <T>


81

Чи існує LINQ-спосіб поміняти місцями два елементи всередині a list<T>?


57
Чому це має значення, чому він хоче це зробити. Люди, які шукають "поміняти елементи списку c #", хочуть отримати пряму відповідь на це конкретне питання.
Даніель Мачіас,

10
@DanielMacias Це так правда. Ці відповіді схожі на "а чому ти це робиш?" так дратує. Я думаю, що слід дати принаймні життєздатну відповідь, перш ніж намагатись аргументувати причину.
julealgon

чому ви хочете використовувати LINQ для цього? якщо LINQ конкретний, чому б не змінити заголовок, щоб додати LINQ
в

Відповіді:


119

Перевірте відповідь від Марка з C #: Хороша / найкраща реалізація методу обміну .

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

10
Чому цьому методу розширення потрібно повернути список? Ви змінюєте список на місці.
vargonian

13
Тоді ви можете зв'язати методи.
Ян Йонгбум,

не забудьте зробити метод розширення загальнодоступним, якщо ви використовуєте це в Unity3D (Unity не може знайти його, якщо ви цього не зробите)
col000r

Приємно і просто. Дякую
fnc12

5
Попередження: Версія "LINQified" все ще змінює оригінальний список.
Філіп Міхальскі,

32

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

Побічні ефекти? Чи можете ви детальніше сказати?
Tony The Lion

12
Під "Побічними ефектами" він має на увазі, що вони змінюють список і, можливо, елементи у списку - на відміну від простого запитування даних, які нічого не могли б змінити,
Сарет

робить "T temp = list [firstIndex];" створити глибоку копію об’єкта у списку?
Пол Маккарті

1
@PaulMcCarthy Ні, він створює новий вказівник на вихідний об'єкт, взагалі не копіюючи.
NetMage

12

List<T>має Reverse()метод, однак він змінює порядок лише двох (або більше) послідовних елементів.

your_list.Reverse(index, 2);

Там, де другий параметр 2вказує, ми змінюємо порядок 2 елементів, починаючи з елемента в заданомуindex .

Джерело: https://msdn.microsoft.com/en-us/library/hf2ay11y(v=vs.110).aspx


1
Хоча Google привів мене сюди, це те, що я шукав.
Jnr

1
Функція .Reverse () не є Linq і не називається свопом, але, схоже, метою цього питання є пошук "вбудованої" функції, яка виконує своп. Це єдина відповідь передбачає це.
Америкус

Але корисно лише міняти місцями сусідні елементи, тому насправді не відповідь.
NetMage

10

Не існує методу обміну, тому вам доведеться створити його самостійно. Звичайно, ви можете його зв’язати, але це потрібно робити з урахуванням одного (неписаного?) Правила: 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. Якщо під час ітерації трапиться виняток, список не буде відновлено. (Це може бути вирішено для написання спроби-нарешті всередині функції обміну і розміщення коду відновлення всередині блоку-завершення).

Є краще (і коротше) рішення: просто зробіть копію оригінального списку. (Це також дозволяє використовувати 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.


0

Якщо порядок має значення, слід зберегти властивість об’єктів «T» у вашому списку, що позначає послідовність. Для того, щоб поміняти місцями, просто поміняйте місцями значення цієї властивості, а потім використовуйте це в .Sort ( порівняння з властивістю послідовності )


Це якщо ви можете концептуально погодитись, що ваш T має властивий "порядок", але не якщо ви хочете, щоб його сортували довільним чином без властивого "порядку", наприклад, в інтерфейсі користувача.
Дейв Ван ден Ейнде

@DaveVandenEynde, я досить початківець програміст, тому, будь ласка, пробачте мене, якщо це не велике питання: що означає поміняти місцями пункти у списку, якщо ситуація не передбачає поняття порядку?
Mathieu K.

@ MathieuK.well, що я хотів сказати, це те, що ця відповідь передбачає, що порядок є похідним від властивості об’єктів, за якими можна сортувати послідовність . Зазвичай це не так.
Дейв Ван ден Ейнде

Я (можливо, неправильно) розумію цю відповідь, кажучи: "Пронумеруйте свої об'єкти, зберігаючи цифри у властивості. Потім, щоб поміняти місцями два місця, поміняйте місцями їхні місця. Використовуйте список, відсортований за цією властивістю".
Матьє К.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.