1

I have the following two lists coming from two different warehouses.

var list1 = new List<Tshirt> {
    new Tshirt(){ Color = "blue", size="M", qty=3 },
    new Tshirt(){ Color = "red", size="M", qty=2 },
    new Tshirt(){ Color = "green", size="M", qty=3 },
    new Tshirt(){ Color = "blue", size="M", qty=3 },
}

var list2 = new List<Tshirt> {
    new Tshirt(){ Color = "blue", size="M", qty=5 },
    new Tshirt(){ Color = "red", size="M", qty=7 },
}

Using LINQ, how do I end up with a combined list like this.

var list3 = new List<Tshirt> {
    new Tshirt(){ Color = "blue", size="M", qty=11 },
    new Tshirt(){ Color = "red", size="M", qty=9 },
    new Tshirt(){ Color = "green", size="M", qty=3 }
}
johnfree
  • 107
  • 6
  • Just to confirm, is that correct that `list3` contains two `Tshirt` instances with `Color = "blue"` and `size = "M"` and `qty` of `8` and `3` instead of one instance where `qty = 11`? If so, what are the conditions/logic that cause medium blue `Tshirt` instances of quantities `3`, `3`, and `5` in the input lists to be represented as `8` and `3` in the output list? Also, what have you tried so far to implement this? – Lance U. Matthews Nov 24 '18 at 03:56
  • Sorry, I meant to group all shirts by color and size then combine the qtys. – johnfree Nov 24 '18 at 06:44
  • So far what I've tried was add list2 to list1 by using AddRange method. Then I create a list3 and loop over list1. If list3 already has the current color+size combination then I add the additional qty to it. Otherwise, I create a new item in list3 using the properties of current item. This seems highly inefficient though, I think LINQ is better suited for this sort of thing – johnfree Nov 24 '18 at 06:57

2 Answers2

4

(I originally answered this question incorrectly, see the second heading below ("To combine all distinct Tshirt instances together") for my original, irrelevant, answer)

To combine all Tshirt instances and sum their qtys:

I see you're using a tuple of color + size to uniquely identify a type of t-shirt, which means if we combine all Tshirt instances together (Concat), then group them by color + size, then Sum the qty values, then return new Tshirt instances in a new list.

List<Tshirt> aggregatedShirts = uniqueShirts = Enumerable
    .Empty<Tshirt>()
    .Concat( list1 )
    .Concat( list2 )
    .GroupBy( shirt => new { shirt.Color, shirt.size } )
    .Select( grp => new Tshirt()
    {
        Color = grp.Key.Color,
        size  = grp.Key.size,
        qty   = grp.Sum( shirt => shirt.qty )
    } )
    .ToList();

To combine all distinct Tshirt instances together

Assuming class Tshirt implements IEquatable<Tshirt> then just use Concat( ... ).Distinct().ToList():

I'd do it this way, others might prefer not to use Empty:

List<Tshirt> uniqueShirts = Enumerable
    .Empty<Tshirt>()
    .Concat( list1 )
    .Concat( list2 )
    .Distinct()
    .ToList();

If Tshirt does not implement IEquatable then you can use the overload of Distinct that accepts an IEqualityComparer<TSource>:

class TshirtComparer : IEqualityComparer<Tshirt>
{
    public static TshirtComparer Instance { get; } = new TshirtComparer();

    public Boolean Equals(Tshirt x, Tshirt y)
    {
        if( ( x == null ) != ( y == null ) ) return false;
        if( x == null ) return true;

        return x.Color == y.Color && x.size == y.size && x.qty == y.qty;
    }

    public Int32 GetHashCode(Tshirt value)
    {
        if( value == null ) return 0;
        // See https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode
        Int32 hash = 17;
        hash = hash * 23 + value.Color?.GetHashCode() ?? 0;
        hash = hash * 23 + value.size?.GetHashCode() ?? 0;
        hash = hash * 23 + value.qty;
        return hash;
    }
}

Usage:

List<Tshirt> uniqueShirts = Enumerable
    .Empty<Tshirt>()
    .Concat( list1 )
    .Concat( list2 )
    .Distinct( TshirtComparer.Instance )
    .ToList();

Then to get the total quantity:

Int32 totalQuantity = uniqueShirts.Sum( shirt => shirt.qty );
Dai
  • 141,631
  • 28
  • 261
  • 374
  • The input lists `list1` and `list2` have a total of three medium blue shirt instances, and the output list `list3` has two medium blue instances, with some quantities combined. By using `Distinct()` won't this result in only one medium blue shirt instance? – Lance U. Matthews Nov 24 '18 at 03:53
  • @BACON Argh, you're right! I didn't read the question fully before answering. – Dai Nov 24 '18 at 03:54
  • @BACON I've updated my answer with a solution to the OP's actual question. – Dai Nov 24 '18 at 04:01
  • The new code still yields one `Tshirt` instance per combination of `Color`/`size`. As I commented on the question itself, I don't know (or, at least, it's not explained) how code could know that medium blue shirts in quantities `3 + 3 + 8` should be combined to yield `8 + 3`. Maybe no `Tshirt` instance can have `qty >= 10`? Or there's something special about having two instances with `qty = 3` such that only one of them gets added to the `qty = 8` instance and the one remaining contributes to a new instance? I think more information is needed. – Lance U. Matthews Nov 24 '18 at 04:13
  • @BACON Yes, the OP's question post is definitely low-effort - but I'm assuming my new answer is what they're looking for, because anything else doesn't make much sense. – Dai Nov 24 '18 at 04:14
  • Yeah, the code you provided makes more sense than what they (appear to be) asking for. In fact, I'd be willing to bet that they created the code for `list3` by copying and pasting `list1` and forgot to delete the fourth `Tshirt` instance... – Lance U. Matthews Nov 24 '18 at 04:17
  • BACON, you are correct, my apologies for not double checking my question. I made the necessary correction. – johnfree Nov 24 '18 at 07:03
  • Thank you Dai, that was exactly what I was looking for – johnfree Nov 24 '18 at 07:19
3
 var list3 = list1.Union(list2).GroupBy(o => new {o.Color, o.size})
                .Select(o => new Tshirt()
                {
                    Color = o.Key.Color,
                    size = o.Key.size,
                    qty = o.Sum(q => q.qty)
                }).OrderByDescending(o => o.qty).ToList();
xiang wang
  • 31
  • 3