Permuter deux éléments dans la liste

Existe-t-il un moyen LINQ d'échanger la position de deux éléments dans un list<T>?

52
demandé sur Tony The Lion 2010-01-19 17:46:13

5 réponses

Vérifiez la réponse de Marc à partir de C#: Bonne / meilleure implémentation de la méthode D'échange .

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

Qui peut être linq-I-fied comme

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);
83
répondu Jan Jongboom 2017-05-23 11:54:37

Peut-être que quelqu'un pensera à une façon intelligente de le faire, mais vous ne devriez pas. L'échange de deux éléments dans une liste est intrinsèquement chargé d'effets secondaires, mais les opérations LINQ devraient être sans effets secondaires. Ainsi, il suffit d'utiliser une méthode d'extension simple:

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;
    }
}
29
répondu jason 2012-07-19 18:07:05

Il n'y a pas de méthode d'échange existante, vous devez donc en créer une vous-même. Bien sûr, vous pouvez le linqify, mais cela doit être fait avec un (non écrit?) règles à l'esprit: LINQ-opérations ne changent pas les paramètres d'entrée!

Dans les autres réponses "linqify", la liste (input) est modifiée et renvoyée, mais cette action freine cette règle. Si ce serait bizarre si vous avez une liste avec des éléments non triés, faites une opération LINQ "OrderBy" et découvrez que la liste d'entrée est également triée (tout comme le résultat). Ce n'est pas permis d'arriver!

Donc... comment faisons-nous cela?

Ma première pensée était juste de restaurer la collection après qu'elle ait été terminée. Mais c'est une solution sale , alors ne l'utilisez pas:

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

Cette solution est sale parce qu'il ne modifier la liste d'entrée, même si elle rétablit l'état d'origine. Cela pourrait causer plusieurs problèmes:

  1. la liste peut être en lecture seule qui lancera une exception.
  2. Si la liste est partagée par plusieurs threads, la liste va changer pour les autres threads pendant la durée de cette fonction.
  3. Si une exception se produit pendant l'itération, la liste ne sera pas restaurée. (Cela pourrait être résolu pour écrire un try-finally à l'intérieur de la fonction Swap, et mettre le code de restauration à l'intérieur du bloc finally).

Il y a une meilleure solution (et plus courte): il suffit de faire une copie de la liste originale. (Cela permet également d'utiliser un IEnumerable comme paramètre, au lieu D'un 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;
}

Un inconvénient de cette solution est qu'elle copie toute la liste qui va consommer de la mémoire et qui rend la solution plutôt lente.

, Vous pourriez envisager la solution suivante:

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

Et si vous voulez que le paramètre d'entrée soit 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 fait également une copie de (un sous-ensemble de) la source. Donc, dans le pire des cas, il est aussi lent et consomme de la mémoire que la fonction Swap2.

9
répondu Martin Mulder 2013-04-21 21:38:01

Liste a la méthode inverse.

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

Source: https://msdn.microsoft.com/en-us/library/hf2ay11y (v=vs. 110).aspx

2
répondu user1920925 2018-08-28 15:48:04

Si l'ordre est important, vous devez conserver une propriété sur les objets " T " dans votre liste qui indique la séquence. Afin de les échanger, il suffit d'échanger la valeur de cette propriété, puis l'utiliser dans le .Trier (comparaison avec la propriété de séquence)

0
répondu CaffGeek 2010-01-19 14:51:59