tags:

views:

650

answers:

10
List<double> a = new List<double>{1,2,3};
List<double> b = new List<double>{1,2,3,4,5};

a + b should give me 2,4,6,4,5

obvisouly i can write a loop but is there a better way? using linq?

+12  A: 

You could use a modified "zip" operation easily enough, but nothing built in. Something like:

    static void Main() {
        var a = new List<int> { 1, 2, 3 };
        var b = new List<int> { 1, 2, 3, 4, 5 };
        foreach (var c in a.Merge(b, (x, y) => x + y)) {
            Console.WriteLine(c);
        }
    }
    static IEnumerable<T> Merge<T>(this IEnumerable<T> first,
            IEnumerable<T> second, Func<T, T, T> operation) {
        using (var iter1 = first.GetEnumerator())
        using (var iter2 = second.GetEnumerator()) {
            while (iter1.MoveNext()) {
                if (iter2.MoveNext()) {
                    yield return operation(iter1.Current, iter2.Current);
                } else {
                    yield return iter1.Current;
                }
            }
            while (iter2.MoveNext()) {
                yield return iter2.Current;
            }
        }
    }
Marc Gravell
Why is your solution so long?
jjnguy
because it is reusable with any number of sequences and operations - not just lists/indexers/addition. Fixed the vertical space... better?
Marc Gravell
touche !
jjnguy
It only works when the two lists are of the same time. Check out my proposed solution, which takes in to account lists of different types.
IRBMe
@IRBMe - I beg to differ. Note the testing and handling of MoveNext. Frankly - it is correct as written.
Marc Gravell
I thoroughly beg to differer again, good sir! You can't use your function to Merge a list of type int with a List of type char, for example. Your generic function works on two List<T>s. What would the return type be with your function in that case, if it works, as you claim? A List<int> or a List<char>?
IRBMe
Ah... I think we have cross meanings; I mistook your typo "of the same time" as meaning "length"; it seems you meant it to mean "type". So no: my answer doesn't address different types, and I'm not sure it is useful to do so (you would just use Select, for example, to get compatible sequences). It does, however, correctly handle different lengths... which yours **really** doesn't (at time of writing).
Marc Gravell
Indeed, sorry for the typo. That should indeed have said type.
IRBMe
+1  A: 

Below is a solution to your problem.

List<double> a = new List<double>{1,2,3};
List<double> b = new List<double>{1,2,3,4,5};

List<double> sum = new List<double>();
int max = Math.Min(a.Count, b.Count);
for (int i = 0; i < max; i++){
    sum.Add(a[i] + b[i]);
}

if (a.Count < b.Count)
    for (int i = max i < b.Count)
        sum.Add(b[i]);
else
    for (int i = max i < a.Count)
    sum.Add(a[i]);
jjnguy
From OP: "obvisouly i can write a loop but is there a better way?"
Marc Gravell
Touche again.....
jjnguy
The code is good, though; I've +1'd to negate the (unfair, IMO) downvote.
Marc Gravell
Well, thanks..I shall +1 you 'cuz you answered his question.
jjnguy
A: 

In this case, whether lists are of the same length, or of different lengths, it doesn't really matter. .NET class library doesn't have Enumerable.Zip method to combine two sequences (it will only come in .NET 4.0), and you would need something like that here either way. So you either have to write a loop, or to write your own Zip (which would still involve a loop).

There are some hacks to squeeze this all in a single LINQ query without loops, involving joining on indices, but those would be very slow and really pointless.

Pavel Minaev
Why, exactly, did this get a -1? If it is factually incorrect, please point out the specific problems. Otherwise, I do not see how this is not a direct answer to the question as stated ("i can write a loop but is there a better way? using linq?").
Pavel Minaev
My solution used LINQ with no loop. Obviously there will be loop done by LINQ, but it will be written by the compiler.
Yuriy Faktorovich
A: 

What happened to the 1 and the extra 2 and 3? If you're looking for distinct values:

var one = new List<int> { 1, 2, 3 };
var two = new List<int> { 1, 2, 3, 4, 5 };

foreach (var x in one.Union(two)) Console.Write("{0} ", x);

Will give you 1 2 3 4 5

If you're looking for just the second list appended to the first then:

foreach(var x in one.Concat(two)) // ...

will give you 1 2 3 1 2 3 4 5

Edit: Oh, I see, you're looking for a sort of Zip, but which returns the extra parts. Try this:

public static IEnumerable<V> Zip<T, U, V>(
 this IEnumerable<T> one,
 IEnumerable<U> two,
 Func<T, U, V> f)
{
 using (var oneIter = one.GetEnumerator()) {
  using (var twoIter = two.GetEnumerator()) {
   while (oneIter.MoveNext()) {
    twoIter.MoveNext();
    yield return f(oneIter.Current,
     twoIter.MoveNext() ?
      twoIter.Current :
      default(U));
   }

   while (twoIter.MoveNext()) {
    yield return f(oneIter.Current, twoIter.Current);
   }
  }
 }
}

and here's one that's more like a normal zip function, which doesn't return the extras:

public static IEnumerable<V> Zip<T, U, V>(
 this IEnumerable<T> one,
 IEnumerable<U> two,
 Func<T, U, V> f)
{
 using (var oneIter = one.GetEnumerator()) {
  using (var twoIter = two.GetEnumerator()) {
   while (oneIter.MoveNext()) {
    yield return f(oneIter.Current,
     twoIter.MoveNext() ?
      twoIter.Current :
      default(U));
   }
  }
 }
}

Example usage:

var one = new List<int>  { 1, 2, 3, 4, 5};
var two = new List<char> { 'h', 'e', 'l', 'l', 'o' };

foreach (var x in one.Zip(two, (a,b) => new {A = a, B =b }))
 Console.WriteLine("{0} => '{1}'", x.A, x.B);

Results in:

1 => 'h'
2 => 'e'
3 => 'l'
4 => 'l'
5 => 'o'

IRBMe
He is actually trying to do a pairwise sum of elements in both lists, treating missing elements from shorter lists as 0.
Pavel Minaev
I see! I've edited my answer to include that functionality.
IRBMe
This is incorrect; the value of Current is undefined it you don't know that MoveNext returns true; in fact, for older iterators they will throw an exception if you do this; see http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.current.aspx: "Current also throws an exception if the last call to MoveNext returned false, which indicates the end of the collection."
Marc Gravell
Specifically, when you use `twoIter.MoveNext()` without checking the value, then access `twoIter.Current`, or in the second `while` loop where you access `oneIter.Current` when you know that `oneIter.MoveNext()` has returned false.
Marc Gravell
It works fine according to this: http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.movenext.aspx The only way you can invalidate the enumerator is by modifying the collection partway through. If you continue to call GetNext, it simply continues to return false until you call Reset.
IRBMe
I don't think you are understanding the example; it is **required** to check the value of MoveNext; in the example given in the page you cite, failing to do this will cause an out-of-range exception. Also, Reset() is deprecated, and it is **required** (in the language spec) for most implementations of this (i.e. iterator blocks) to throw an exception.
Marc Gravell
Note the handling in Current that turns the out-of-range into an invalid-operation; either way, it completely validates my point; you **cannot** access Current without checking MoveNext() returned true. Actually, for iterator blocks, IIRC it will return the *last* value successfully yielded - so you could also corrupt the answer (so {1,2,3} and {1,2,3,4,5} could return {2,4,6,7,8})
Marc Gravell
I see what you mean now, although the page I cited was actually not where the problem is. The problem is in accessing Current. Fixed
IRBMe
A: 

My implementation using a loop:

List<double> shorter, longer;
if (a.Count > b.Count)
{
    shorter = b; longer = a
}
else
{
    shorter = a; longer = b;
}

List<double> result = new List<double>(longer);
for (int i = 0; i < shorter.Count; ++i)
{
     result[i] += shorter[i];
}
Pavel Minaev
Note that this is an addition to my other answer which explains why this cannot be done via LINQ in 3.5. It's just an attempt to provide the shortest and most readable loop-based implementation.
Pavel Minaev
+1  A: 

The ugly LINQ solution:

var sum = Enumerable.Range(0, (a.Count > b.Count) ? a.Count : b.Count)
    .Select(i => (a.Count > i && b.Count > i) ? a[i] + b[i] : (a.Count > i) ? a[i] : b[i]);
Yuriy Faktorovich
+3  A: 
Enumerable.Range(0, new[] { a.Count, b.Count }.Max())
    .Select(n => a.ElementAtOrDefault(n) + b.ElementAtOrDefault(n));
gabe
Personally, I would use `Math.Max(a.Count, b.Count)` instead of `new[] { a.Count, b.Count }.Max()`.
Ryan Versaw
good point, shorter than mine.
gabe
ElementAtOrDefault, being for IEnumerable, will (probably) traverse the whole list. Probably better to do the longer `a.Count > n ? a[n] : 0` instead.
Mark Brackett
A: 

Using .NET 4.0's Zip operator:

var sums = b.Zip(a, (x, y) => x + y)
            .Concat(b.Skip(a.Count()));

If you want to generalize this, check which has more elements and use that as the "b" above.

Joe Chung
+1  A: 

How about this:

List<double> doubles = Enumerable.Range(0, Math.Max(a.Count, b.Count))
    .Select(x => (a.Count > x ? a[x] : 0) + (b.Count > x ? b[x] : 0))
    .ToList();
Handcraftsman
What if my a/b collections are IEnumerable and are not Lists?
Orion Edwards
In that case in order to be efficient you'll have to iterate both lists simultaneously so use Marc Gravell's implementation.
Handcraftsman
A: 

Here's 3 more:

Make the lists the same size, then just simple Select.

(a.Count < b.Count ? a : b).AddRange(new double[Math.Abs(a.Count - b.Count)]);
var c = a.Select((n, i) => n + b[i]);

Don't make them the same size, but go through the longest and check for end of range on the shortest (store shortList.Count for easy perf gain):

var longList = a.Count > b.Count ? a : b;
var shortList = longList == a ? b : a;
var c = longList.Select((n, i) => n + (shortList.Count > i ? shortList[i] : 0));

Take while you can, then Skip and Union the rest:

var c = a.Take(Math.Min(a.Count, b.Count))
         .Select((n, i) => n + b[i])
         .Union(a.Skip(Math.Min(a.Count, b.Count));
Mark Brackett