61

I'm used to doing this (from other languages):

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

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

This takes two lists of equal size and applies a function to their members, one at a time, to get a list of the results. It could be a function as simple as multiplication (above) or something more complex:

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

I can think of a few different ways to do this in C#, but I'm not sure how a C#-trained programmer would do it. What's the proper way of going about this in the C# world?

(The only part I'm asking about is where c = f(a,b). I'm familiar with creating lists and accessing their elements.)

Rawling
  • 49,248
  • 7
  • 89
  • 127
Joe
  • 3,804
  • 7
  • 35
  • 55
  • Good question, I would probably just do this with a `for` loop due to the need for parallel indices. For multiplying by a simple scalar, a `Select` statement. – BradleyDotNET Jun 03 '14 at 23:28
  • Look into the ins-and-outs of LINQ for operations such as this, as it provides clean, shorthand notition for selecting, or applying operators to lists. http://msdn.microsoft.com/en-us/library/bb397933.aspx – GEEF Jun 03 '14 at 23:29
  • @VP., write that as an answer, give an example of using LINQ for that purpose, and I'll be happy to upvote it. – Joe Jun 03 '14 at 23:31
  • See Jon's answer, it has the correct LINQ solution. – BradleyDotNET Jun 03 '14 at 23:32
  • 4
    Also consider [Anti-pattern: parallel collections, by Jon Skeet](http://msmvps.com/blogs/jon_skeet/archive/2014/06/03/anti-pattern-parallel-collections.aspx). – user Jun 04 '14 at 12:42
  • Any use of LINQ will be a **lot** slower then a simple for loop, as there will be a method call for each array item. So if "*" is fast, don't use LINQ. – Ian Ringrose Jun 04 '14 at 13:11
  • 1
    @MichaelKjörling, that link is dead, but see [Jon Skeet's blog](http://codeblog.jonskeet.uk/2014/06/03/anti-pattern-parallel-collections/). – Joe Mar 13 '15 at 17:21
  • @Rawling Not sure, but my thinking is that someone with a similar question may already be thinking about linq and hence search on that. – Jon Hanna May 12 '15 at 12:03

4 Answers4

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

For the more complex one after your edit:

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

Note that Zip is an extension method both from Enumerable that acts on IEnumerable<T> and from Queryable that acts on IQueryable<T>, so it is possible that, should the lambda be one a given query provider can deal with, that it could be processed as a SQL query on a database, or some other way other than in-memory in .NET.

Someone mentioned that this was new with 4.0 in the comments. It's not hard to implement for 3.5 yourself:

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

For .NET2.0 or .NET3.0 you can have the same, but not as an extension method, which answers another question from the comments; there wasn't really an idiomatic way of doing such things in .NET at that time, or at least not with a firm consensus among those of us coding in .NET then. Some of us had methods like the above in our toolkits (though not extension methods obviously), but that was more that we were influenced by other languages and libraries than anything else (e.g. I was doing things like the above because of stuff I knew from C++'s STL, but that was hardly the only possible source of inspiration)

Rui Jarimba
  • 11,166
  • 11
  • 56
  • 86
Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • 7
    Important note, [`Zip`](http://msdn.microsoft.com/en-us/library/vstudio/dd267698%28v=vs.100%29.aspx) is new to .NET 4.0 so if you are targeting 3.5 it will not be available. (but it is not hard to implment yourself as an extension method) – Scott Chamberlain Jun 03 '14 at 23:31
  • 1
    Couldn't remember quick enough that it was `Zip`. +1 – Simon Whitehead Jun 03 '14 at 23:31
  • @ScottChamberlain, That probably explains why I keep forgetting about it... +1 for the easiest and most succinct solution! – BradleyDotNET Jun 03 '14 at 23:31
  • Is there a .NET <4.0 idiomatic way of doing this? – Joe Jun 03 '14 at 23:32
  • I love `Zip`, but like others I always forget about it. You can get a list of squares for example like this `var ints = Enumerable.Range(1, 5); var squares = ints.Zip(ints, (i, j) => i * j);` – DavidG Jun 03 '14 at 23:33
  • 3
    @DavidG Or `Enumerable.Range(1,5).Select(x => x*x)` – Ben Aaronson Jun 03 '14 at 23:34
  • 1
    @Joe not really, but take a quick look at the [soure code for it](http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs#2b8d0f02389aab71), It would not be hard to add to your own project. – Scott Chamberlain Jun 03 '14 at 23:34
  • @ScottChamberlain, that's basically what I've already done, writing my own `Zip` function, but going down that road, I end up just remaking APL in C#... – Joe Jun 03 '14 at 23:36
  • @Joe, those who do not learn from Linq are doomed to re-implement it. (I'm not sure if that's better or worse than having ones rather ad-hoc but still pretty useful little collection of methods rendered obsolete when 3.5 came out). – Jon Hanna Jun 03 '14 at 23:50
  • 1
    Per one of your last updates about not being able to do extension methods in .NET 2.0 or 3.0, I discovered [a neat trick](http://stackoverflow.com/questions/11346554/can-i-use-extension-methods-and-linq-in-net-2-0-or-3-0) a while ago you can do to add support for extension methods to .NET 2.0 or 3.0. However you need to be using Visual Studio 2008 or newer for it to work. – Scott Chamberlain Jun 03 '14 at 23:51
  • @ScottChamberlain I prefer not to use that trick, and keep 3.0-or-lower acting 3.0-or-lower consistently, though it's a good one to know about for what it shows about how the `ExtensionAttribute` and the extension language feature interact with each other. – Jon Hanna Jun 03 '14 at 23:57
  • 1
    Ah, `Zip`. One of my favorite higher order functions in any language that supports them. Exactly the right choice for this kind of problem. – KChaloux Jun 04 '14 at 13:21
  • It's strange the answers that get the most votes. I'm happy with the answer, but I wouldn't put this anywhere near my top 10. – Jon Hanna Jun 11 '14 at 09:19
23

Assuming .Net 3.5 with lists of equal length:

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

Result:

5

2

6

DotNetFiddle.Net Example

Community
  • 1
  • 1
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • Is `Select` a Linq function? – Joe Jun 13 '14 at 23:13
  • 1
    [System.Linq.Select](http://msdn.microsoft.com/en-us/library/vstudio/system.linq.enumerable.select(v=vs.100).aspx) is an extension method found in the assembly `System.Core`. – Erik Philips Jun 13 '14 at 23:14
19

If you are not using .NET 4.0 here is how to write your own extension method to do a 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);
        }
    }
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
12

For .NET verions without LINQ I would recommend a for loop to accomplish this:

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

This assumes, of course, the lists are the same size and not changing. If you know the list size ahead of time you could also instantiate it to the correct size then just set the element during the loop.

GEEF
  • 1,175
  • 5
  • 17
  • 2
    Now we have all our bases covered, A post 3.5 solution, a 3.5 solution and a pre 3.5 solution. (for the size issue, you can replicate what Zip does by making the for loop `for (int i = 0; i < Math.Min(list1.Count, list2.Count); ++i)` – Scott Chamberlain Jun 03 '14 at 23:38
  • @ScottChamberlain Which features in your solution aren't available pre-3.5? – Ben Aaronson Jun 03 '14 at 23:39
  • @BenAaronson Extension methods. But you can put extension method support back in via a [neat hack](http://stackoverflow.com/questions/11346554/can-i-use-extension-methods-and-linq-in-net-2-0-or-3-0). – Scott Chamberlain Jun 03 '14 at 23:41
  • @ScottChamberlain Extension methods yes, but what LINQ features do you use? – Ben Aaronson Jun 03 '14 at 23:42
  • @ScottChamberlain Ah, got it. – Ben Aaronson Jun 03 '14 at 23:43
  • There's no need to restrict to lists for either input or output with 2.0 (or even with 1.0), but more importantly there's no reason to assume `list1.Count <= list2.Count` – Jon Hanna Jun 03 '14 at 23:47
  • There is a reason to assume `list1.Count == list2.Count`, because thats what the OP Stated: `This takes two lists of equal size and applies a function`. – Erik Philips Jun 03 '14 at 23:48
  • 1
    @ErikPhilips good .NET code does not assume it's been called correctly, if it's intended for reuse (a private method only called in a limited number of cases is another matter). – Jon Hanna Jun 04 '14 at 00:00
  • @JonHanna of course it does, I wouldn't disagree. However, a valid and acceptable answer on *SO* should at a minimum only includes what is needed to answer the question. This does that per the OPs exact request. – Erik Philips Jun 04 '14 at 00:44
  • I'll add that if I were going to place this sample code into my code somewhere, I would at least wrap this in a method, do some length checking, and make the comparison operator a parameter. – GEEF Jun 04 '14 at 00:46