61 votes

Quelle est la méthode C#-idiomatique pour appliquer un opérateur sur deux listes ?

J'ai l'habitude de faire cela (dans d'autres langues) :

 a = 1, 2, 3;
 b = 5, 1, 2;

 c = a * b;  // c = 5, 2, 6

Elle prend deux listes de taille égale et applique une fonction à leurs membres, un par un, pour obtenir une liste de résultats. Il peut s'agir d'une fonction aussi simple que la multiplication (ci-dessus) ou de quelque chose de plus complexe :

 c = b>a ? b-a : 0;  // c = 4, 0, 0

Je peux penser à plusieurs façons différentes de faire cela en C#, mais je ne suis pas sûr de la façon dont un programmeur formé au C# le ferait. Quelle est la bonne façon de procéder dans le monde C# ?

(La seule partie sur laquelle je pose des questions est celle où c = f(a,b) . Je suis familier avec la création de listes et l'accès à leurs éléments).

77voto

Jon Hanna Points 40291
var c = a.Zip(b, (x, y) => x * y);

Pour le plus complexe, après votre montage :

var c = a.Zip(b, (x, y) => x > y ? x - y : 0);

Notez que Zip est une méthode d'extension à la fois de Enumerable qui agit sur IEnumerable<T> et de Queryable qui agit sur IQueryable<T> Il est donc possible, si le lambda est traité par un fournisseur de requêtes donné, qu'il soit traité comme une requête SQL sur une base de données, ou d'une autre manière qu'en mémoire dans .NET.

Quelqu'un a mentionné que c'était une nouveauté de la version 4.0 dans les commentaires. Il n'est pas difficile de l'implémenter soi-même en 3.5 :

public class MyExtraLinqyStuff
{
    public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
    {
      //Do null checks immediately;
      if(first == null)
        throw new ArgumentNullException("first");
      if(second == null)
        throw new ArgumentNullException("second");
      if(resultSelector == null)
        throw new ArgumentNullException("resultSelector");
      return DoZip(first, second, resultSelector);
    }
    private static IEnumerable<TResult> DoZip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
    {
      using(var enF = first.GetEnumerator())
      using(var enS = second.GetEnumerator())
        while(enF.MoveNext() && enS.MoveNext())
          yield return resultSelector(enF.Current, enS.Current);
    }
}

Pour .NET2.0 ou .NET3.0, vous pouvez avoir la même chose, mais pas en tant que méthode d'extension, ce qui répond à une autre question des commentaires ; il n'y avait pas vraiment de manière idiomatique de faire de telles choses dans .NET à cette époque, ou du moins pas avec un consensus ferme parmi ceux d'entre nous qui codaient en .NET à l'époque. Certains d'entre nous avaient des méthodes comme celles mentionnées ci-dessus dans leurs boîtes à outils (mais pas des méthodes d'extension évidemment), mais c'était plus parce que nous étions influencés par d'autres langages et bibliothèques qu'autre chose (par exemple, je faisais des choses comme celles mentionnées ci-dessus à cause de ce que je savais de la STL de C++, mais ce n'était pas la seule source d'inspiration possible).

23voto

Erik Philips Points 18156

En supposant que .Net 3.5 avec des listes de longueur égale :

var a = new List<int>() { 1, 2, 3 };
var b = new List<int>() { 5, 1, 2 }; 

var c = a.Select((x, i) => b[i] * x);

Résultat :

5

2

6

Exemple de DotNetFiddle.Net

19voto

Scott Chamberlain Points 32782

Si vous n'utilisez pas .NET 4.0, voici comment écrire votre propre méthode d'extension pour effectuer un Zip.

static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector) 
{
    using (IEnumerator<TFirst> e1 = first.GetEnumerator())
    using (IEnumerator<TSecond> e2 = second.GetEnumerator())
    {
        while (e1.MoveNext() && e2.MoveNext())
        {
            yield return resultSelector(e1.Current, e2.Current);
        }
    }
}

12voto

VP. Points 406

Pour les versions de .NET sans LINQ, je recommande une boucle for pour accomplir ceci :

List<int> list1 = new List<int>(){4,7,9};
List<int> list2 = new List<int>(){11,2,3};
List<int> newList = new List<int>();
for (int i = 0; i < list1.Count; ++i)
{
    newList.Add(Math.Max(list1[i], list2[i]));
}

Cela suppose, bien sûr, que les listes soient de la même taille et ne changent pas. Si vous connaissez la taille de la liste à l'avance, vous pouvez également l'instancier à la taille correcte puis définir l'élément pendant la boucle.

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