101 votes

Échanger deux éléments de la liste <T>

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

137voto

Jan Jongboom Points 15148

Vérifiez la réponse de Marc sur http://stackoverflow.com/questions/552731/c-good-best-implementation-of-swap-method .

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

qui peut être comme linq-i-fied

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

33voto

Jason Points 125291

Peut-être que quelqu'un va penser à une façon intelligente de faire cela, mais vous ne devriez pas. L'échange de deux éléments d'une liste est par nature chargé d'effets secondaires, mais les opérations LINQ devraient être libres d'effets secondaires. Ainsi, utilisez simplement 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;
    }
}
 

10voto

Martin Mulder Points 5326

Il n'existe pas de Swap méthode, donc, vous devez créer un vous-même. Bien sûr, vous pouvez linqify, mais qui doit être fait avec un (non-écrite?) les règles à l'esprit: LINQ-les opérations ne modifiez pas les paramètres d'entrée!

Dans l'autre "linqify" des réponses, l' (entrée) de la liste est modifiée et s'est retourné, mais cette action des freins à cette règle. Si serait bizarre si vous avez une liste non triée des éléments, faire un LINQ "OrderBy"-opération et de découvrir que la liste d'entrée est également trié (tout comme le résultat). Ce n'est pas permis d'arriver!

Donc... comment faisons-nous cela?

Ma première pensée était juste pour rétablir la collecte après la fin de l'itération. Mais c'est un sale solution, il ne faut pas l'utiliser:

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 pourrait être en lecture seule qui renvoie 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 lors de l'itération, la liste ne sera pas restauré. (Cela pourrait être résolu à écrire un essayez-enfin à l'intérieur de la Swap en fonction, et mettre de la restauration-code à l'intérieur de la enfin-bloc).

Il y a une meilleure (et plus court) solution: il suffit de faire une copie de la liste d'origine. (Cela rend également possible l'utilisation d'un IEnumerable en tant que paramètre à la place d'une 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 que la copie de l'ensemble de la liste qui consomment de la mémoire et qui rend la solution plutôt lent.

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 paramètre d'entrée pour être 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 également fait une copie (un sous-ensemble de) la source. Tellement pire des cas, il est aussi lent et la consommation de mémoire en fonction Swap2.

0voto

CaffGeek Points 10925

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X