Wat is de C#-idiomatische manier om een ​​operator op twee lijsten toe te passen?

Ik ben dit gewend (vanuit andere talen):

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

Hiervoor zijn twee lijsten van gelijke grootte nodig en past een functie één voor één op hun leden toe om een ​​lijst van de resultaten te krijgen. Het kan een eenvoudige functie zijn als vermenigvuldigen (hierboven) of iets ingewikkelder:

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

Ik kan een paar verschillende manieren bedenken om dit in C# te doen, maar ik weet niet zeker hoe een door C# opgeleide programmeur dit zou doen. Wat is de juiste manier om dit aan te pakken in de C#-wereld?

(Het enige waar ik naar vraag is waarc = f(a,b). Ik ben bekend met het maken van lijsten en het openen van hun elementen.)


Antwoord 1, autoriteit 100%

var c = a.Zip(b, (x, y) => x * y);

Voor de meer complexe na je bewerking:

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

Merk op dat Zipeen extensiemethode is die zowel van Enumerabledie werkt op IEnumerable<T>en van Queryabledie werkt op IQueryable<T>, dus het is mogelijk dat, als de lambda er een is die een bepaalde queryprovider aankan, deze kan worden verwerkt als een SQL-query in een database, of op een andere manier dan in het geheugen in .NET .

Iemand zei in de opmerkingen dat dit nieuw was met 4.0. Het is niet moeilijk om zelf 3.5 te implementeren:

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

Voor .NET2.0 of .NET3.0 kun je dezelfde hebben, maar niet als een extensiemethode, die een andere vraag uit de opmerkingen beantwoordt; er was op dat moment niet echt een idiomatische manier om dergelijke dingen in .NET te doen, of in ieder geval niet met een stevige consensus onder degenen onder ons die toen in .NET codeerden. Sommigen van ons hadden methoden zoals de bovenstaande in onze toolkits (hoewel het natuurlijk geen uitbreidingsmethoden waren), maar dat was meer dat we werden beïnvloed door andere talen en bibliotheken dan door iets anders (ik deed bijvoorbeeld dingen zoals hierboven vanwege dingen die ik wist van C++’s STL, maar dat was niet de enige mogelijke inspiratiebron)


Antwoord 2, autoriteit 30%

Ervan uitgaande dat .Net 3.5 met lijsten van gelijke lengte:

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

Resultaat:

5

2

6

DotNetFiddle.Net-voorbeeld


Antwoord 3, autoriteit 25%

Als u .NET 4.0 niet gebruikt, kunt u als volgt uw eigen extensiemethode schrijven om een ​​Zip te maken.

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

Antwoord 4, autoriteit 16%

Voor .NET-versies zonder LINQ zou ik een for-lus aanbevelen om dit te bereiken:

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

Dit veronderstelt natuurlijk dat de lijsten even groot zijn en niet veranderen. Als je de lijstgrootte van tevoren weet, kun je deze ook naar de juiste grootte instantiëren en het element tijdens de lus instellen.

Other episodes