How to progressively build a group by Linq query? - c#

Suppose I have the items
{ // item 0
Color = yellow,
Priority = high,
},
{ // item 1
Color = yellow,
Priority = medium,
},
{ // item 2
Color = green,
Priority = high,
},
I want to be able to progressively build a GroupBy query as using var grouped = items.GroupBy(item => item.Color), what in this case I would have 2 groups and then group again using by the key item.Priority to have 3 groups.
Unfortunately using var regrouped = grouped.GroupBy(item => item.Priority) is not a valid solution.
The final result must be equivalent as doing
items.GroupBy(item => new {item.Color, item.Priority});

Since you want to regroup on a subkey as a new object, you can use ToLookup to create the IGroupings you need, and SelectMany to flatten them into the new IEnumerable:
var regrouped = grouped.SelectMany(cg => cg.GroupBy(i => i.Priority)
.ToLookup(pg => new { Color = cg.Key, Priority = pg.Key },
pg => pg.Select(i => i)
)
);

If I understood you correctly, you want to group each group of Color into Priority. If that's the case, this should work:
var items = new List<Item>
{
new Item { Color = "yellow", Priority = "high" },
new Item { Color = "red", Priority = "high" },
new Item { Color = "yellow", Priority = "medium" },
new Item { Color = "red", Priority = "medium" },
new Item { Color = "green", Priority = "high" },
new Item { Color = "green", Priority = "high" },
new Item { Color = "red", Priority = "high" },
};
var groupped = items.GroupBy(x => x.Color);
var regroupped = groupped.Select(x => new
{
Color = x.Key,
Priorities = x.GroupBy(y => y.Priority)
});
Notice that this returns the same result set than your query
var regroupped = items.GroupBy(item => new {item.Color, item.Priority});

Related

Joining Two list using LINQ Query and displaying only relevant data

I am new to programming and working on LINQ, I have two list both have different data, the thing I am trying to do is join both of them in a separate list and then display only "Black" and "White" car data using LINQ (Query or IQueryable whatever it is) here is my code, that do entirely different thing
static void Main(string[] args)
{
List<Cars> cars = new List<Cars>();
cars.Add (new Cars { Make = "Honda", Model = 2020, Color = "Black"});
cars.Add (new Cars { Make = "Suzuki", Model = 2020, Color = "White" });
cars.Add (new Cars { Make = "Toyota", Model = 2020, Color = "Green" });
cars.Add (new Cars { Make = "Kia", Model = 2020, Color = "Blue" });
List<MakeBy> makeby = new List<MakeBy>();
makeby.Add(new MakeBy { Color = "White", Country = "China" });
makeby.Add(new MakeBy { Color = "Black", Country = "Japan" });
makeby.Add(new MakeBy { Color = "White", Country = "Japan" });
makeby.Add(new MakeBy { Color = "White", Country = "Korea" });
var CombineCars = cars.Zip(makeby, (e, s) => e.Color + "White" + s.Color + "Black");
foreach(var item in CombineCars)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
See if the following works. If not, please specify more precisely, what output you need.
var CombineCars = cars.Join(maekby,
c => c.Color,
m => m.Color,
(c, m) => new
{
carMake = c.Make,
carModel = c.Model,
carColor = c.Color,
makeByColor = m.Color,
makeByCountry = m.Country
});
Now you can access it like:
foreach (var car in CombineCars)
{
Console.WriteLine($"Car model: {car.carModel}, car make: {car.carMake}"); //and so on
}
Haven't tested it, but it should do what you need.

Using Linq to combine two lists and get total quantity

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

Group by Distinct using Linq

I want to distinctly merge two or more collections on an id and create a collection of the other property for each id.
I have an object foo
public class foo
{
public int id { get; set; }
public string color { get; set; }
}
Which I have multiple List<foo> which have common id's but with different colors
//list 1
id = 1, color = "red"
id = 2, color = "blue"
id = 3, color = "green"
id = 1, color = "blue"
id = 2, color = "orange"
//list 2
id = 1, color = "black"
id = 2, color = "amber"
id = 3, color = "red"
id = 4, color = "red"
id = 2, color = "silver"
I want to use linq to project a new collection that will be distinct on the id but create and list of the color property.
id = 1, color = { "red", "blue", "black" }
id = 2, color = { "blue", "orange", "amber", "silver" }
id = 3, color = { "green", "red" }
id = 4, color = { "red" }
Q: How can this be written in linq
var colours = list.GroupBy(x => x.id)
.Select(x => new {id = x.Key, color = x.Select(y => y.color).ToList()})
.ToList();
That will give you a list called colours in the structure you want.
If you want the colour field to be a comma separated string then change it to:
var colours = list.GroupBy(x => x.id)
.Select(x => new {id = x.Key, color = x.Select(y => y.color).ToArray().Join(",")})
.ToList();
I think you want a Dictionary<int, List<string>> instead:
Dictionary<int, List<string>> idColors = foos
.GroupBy(f => f.id)
.ToDictionary(g => g.Key, g => g.Select(f => f.color).ToList());
Now you can access every id's color(s) in this way:
List<string> colors = idColors[1];
Console.WriteLine(string.Join(",", colors));
var result = List.GroupBy(g => new { g.id, g.color })
.Select(g => g.First())
.ToList();
you can use below mentioned code
var mergedList = list1.Concat(list2)
.GroupBy(person => person.id)
.Select(group => group.Aggregate(
(merged, next) => merged.Merge(next)))
.ToList();
I think this is what you are looking for:
list1.Concat(list2)
.GroupBy(x => x.id)
.Select(x => new foo
{
id = x.Key,
color = string.Join(",", x.Select(g => g.color))
}).ToList();
Try this:
var combinedList = list1;
combinedList.AddRange(list2);
var uniqueIds = combinedList.Select(p => p.id).Distinct()
var result = new Dictionary<int, List<string>>();
uniqueIds.ForEach(i => result.Add(i, new List<string>());
combinedList.ForEach(i => result[i.id].Add(i.color);
I haven't tested this, but I think it should be okay...
Might be a better way of doing it, but this should get you working for the moment!

How to write a LINQ query combining group by and aggregates?

Given the following input, how do I write a LINQ query or expression to return an aggregated result set for the quantity?
Input:
var foo = new[] { new { PO = "1", Line = 2, QTY = 0.5000 },
new { PO = "1", Line = 2, QTY = 0.2500 },
new { PO = "1", Line = 2, QTY = 0.1000 },
new { PO = "1", Line = 2, QTY = -0.1000 }
}.ToList();
Desired result:
Something along the lines of
new { PO = "1", Line = 2, QTY = 0.7500 } // .5 + .25 + .1 + -.1
How would I write it for multiple lines as well (see the object model in foo)?
How about this:
var result = foo.GroupBy(x => x.Line)
.Select(g => new { PO = g.First().PO,
Line = g.Key,
QTY = g.Sum(x => x.QTY) });
In the case you just have one Line, just add a .Single() - result is an IEnumerable of the anonymous type defined when you set up foo.
Edit:
If both PO and Line should designate different groups (PO can have different values), they both have to be part of the group key:
var result = foo.GroupBy(x => new { x.PO, x.Line})
.Select(g => new {
PO = g.Key.PO,
Line = g.Key.Line,
QTY = g.Sum(x => x.QTY)
});
var query = (from t in foo
group t by new {t.PO, t.Line}
into grp
select new
{
grp.Key.PO,
grp.Key.Line,
QTY = grp.Sum(t => t.QTY)
}).ToList()

C# Linq Union On Attribute

I have two linq queries that I want to unionize on a common attribute:
One
{
Id,
Name,
Color
}
Two
{
Color,
Cost
}
I want to get the union of One and Two by unionizing on Color? If there is not a Two with a Color that corresponds to One, I want to set Cost to 0 in the output? How do I do this in LINQ?
Here is a sample using anonymous types on how to perform a left outer join:
var products = new[] {
new { Id = 1, Name = "Alpha", Color = "Red" },
new { Id = 2, Name = "Beta", Color = "Green" },
new { Id = 3, Name = "Gamma", Color = "Blue" }
};
var costs = new[] {
new { Color = "Red", Cost = 100 },
new { Color = "Blue", Cost = 200 },
new { Color = "Blue", Cost = 300 }
};
var query = products
.GroupJoin(
costs, p => p.Color, c => c.Color,
(p, c) => new { p.Id, p.Name, p.Color, Costs = c.DefaultIfEmpty() }
)
.SelectMany(
gj => gj.Costs,
(gj, c) => new { gj.Id, gj.Name, gj.Color, Cost = c == null ? 0 : c.Cost }
);
Query result:
Id Name Color Cost
-------------------
1 Alpha Red 100
2 Beta Green 0
3 Gamma Blue 200
3 Gamma Blue 300
This is called a join, not a union.
See the documentation.
You want a left outer join to keep the values appearing in the first list but are not present in the second.

Categories

Resources