24
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}.

Obviously I can write a loop, but is there a better way? Using LINQ?

1
  • Are you looking for distinct values (no repetitions? Also, what about the 1? Commented Jul 27, 2009 at 21:21

14 Answers 14

31

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;
            }
        }
    }
Sign up to request clarification or add additional context in comments.

9 Comments

Why is your solution so long?
because it is reusable with any number of sequences and operations - not just lists/indexers/addition. Fixed the vertical space... better?
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 - I beg to differ. Note the testing and handling of MoveNext. Frankly - it is correct as written.
The operation isn't called for non-existing items. We ended up modifying it to call operation with default(T) passed for missing operand. Thanks!
|
18

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.

2 Comments

"check which has more elements and use that as the "b" above." - How?
@firsttimer (a.Count > b.Count) ? a.Skip(b.Count) : b.Skip(a.Count)
9
Enumerable.Range(0, new[] { a.Count, b.Count }.Max())
    .Select(n => a.ElementAtOrDefault(n) + b.ElementAtOrDefault(n));

2 Comments

Personally, I would use Math.Max(a.Count, b.Count) instead of new[] { a.Count, b.Count }.Max().
ElementAtOrDefault, being for IEnumerable, will (probably) traverse the whole list. Probably better to do the longer a.Count > n ? a[n] : 0 instead.
9

I had to slightly adjust Marc's solution for my use to allow for lists of different types, so I thought I'd post in incase anyone else needs it.

public static IEnumerable<TResult> Merge<TFirst,TSecond,TResult>(this IEnumerable<TFirst> first,
            IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> 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 operation(iter1.Current, default(TSecond));
                }
            }
            while (iter2.MoveNext()) {
                yield return operation(default(TFirst),  iter2.Current);
            }
        }
    }
}

Comments

3

The way to do this with just Zip is:

b.Zip(a.DefaultIfEmpty(), (x,y) => x+y)

As default value of numeric types is 0 you don't need any extra handling.

1 Comment

Beware: DefaultIfEmpty() does not do what you think it does. It returns a single default element if sequence a is empty. It does not provide additional items for an already existing sequence!
2

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

2 Comments

What if my a/b collections are IEnumerable and are not Lists?
In that case in order to be efficient you'll have to iterate both lists simultaneously so use Marc Gravell's implementation.
1

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

1 Comment

From OP: "obvisouly i can write a loop but is there a better way?"
1

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

Comments

0

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.

2 Comments

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?").
My solution used LINQ with no loop. Obviously there will be loop done by LINQ, but it will be written by the compiler.
0

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'

7 Comments

He is actually trying to do a pairwise sum of elements in both lists, treating missing elements from shorter lists as 0.
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 msdn.microsoft.com/en-us/library/…: "Current also throws an exception if the last call to MoveNext returned false, which indicates the end of the collection."
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.
It works fine according to this: msdn.microsoft.com/en-us/library/… 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.
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.
|
0

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

Comments

-1

As a combination of the answers from Marc and Damian, you can just use this code, which is a bit more production ready:

public static class EnumerableExtensions
{
    public static IEnumerable<T> Merge<T>(this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, T> operation)
    {
        return Merge<T, T, T>(first, second, operation);
    }

    public static IEnumerable<TResult> Merge<T, TResult>(this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, TResult> operation)
    {
        return Merge<T, T, TResult>(first, second, operation);
    }

    public static IEnumerable<TResult> Merge<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> operation)
    {
        if (first == null) throw new ArgumentNullException(nameof(first));
        if (second == null) throw new ArgumentNullException(nameof(second));

        using (var iter1 = first.GetEnumerator())
        using (var iter2 = second.GetEnumerator())
        {
            while (iter1.MoveNext())
            {
                yield return iter2.MoveNext()
                    ? operation(iter1.Current, iter2.Current)
                    : operation(iter1.Current, default);
            }

            while (iter2.MoveNext())
            {
                yield return operation(default, iter2.Current);
            }
        }
    }
}

Comments

-1

You can zip b with c, where c have the same size:

var c = a.Concat(Enumerable.Repeat(0, b.Length - a.Length)); 

Comments

-3

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

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.