How do I group in memory lists? - c#

I have a list of Foo. Foo has properties Bar and Lum. Some Foos have identical values for Bar. How can I use lambda/linq to group my Foos by Bar so I can iterate over each grouping's Lums?

var q = from x in list
group x by x.Bar into g
select g;
foreach (var group in q)
{
Console.WriteLine("Group " + group.Key);
foreach (var item in group)
{
Console.WriteLine(item.Bar);
}
}

Deeno,
Enjoy:
var foos = new List<Foo> {
new Foo{Bar = 1,Lum = 1},
new Foo{Bar = 1,Lum = 2},
new Foo{Bar = 2,Lum = 3},
};
// Using language integrated queries:
var q = from foo in foos
group foo by foo.Bar into groupedFoos
let lums = from fooGroup in groupedFoos
select fooGroup.Lum
select new { Bar = groupedFoos.Key, Lums = lums };
// Using lambdas
var q = foos.GroupBy(x => x.Bar).
Select(y => new {Bar = y.Key, Lums = y.Select(z => z.Lum)});
foreach (var group in q)
{
Console.WriteLine("Lums for Bar#" + group.Bar);
foreach (var lum in group.Lums)
{
Console.WriteLine(lum);
}
}
To learn more about LINQ read 101 LINQ Samples

Related

Make two foreach statements into one

I have the following foreach statements, which I want to transfer into linq query.
var equalityGroup= new Dictionary<string, List<string>();
var firstGroup = new Dictionary<string, List<string>();
var request = new List<Request>();
foreach(var element in request)
{
var key = element.Number;
if (!equalityGroup.ContainsKey(key))
{
equalityGroup.Add(key, new List<string>());
}
foreach(var item in firstGroup)
{
var query = item.Value.FindAll(y => y ==element.Id);
if (query.Any())
{
equalityGroup[key].AddRange(query);
}
}
}
Can someone give me a good example for Linq that will work as this foreaches?
I think you should be able to greatly increase the performance for large collections by performing a join (which uses hash-tables internally) instead of nested loops.
var firstGroup = new Dictionary<string, List<string>>();
var request = new List<Request>();
var q = from element in request
join y in firstGroup.SelectMany(x => x.Value) on element.Id equals y
group y by element.Id into g
select new { g.Key, g };
var equalityGroup = q.ToDictionary(x => x.Key, x => x.g.ToList());

LINQ merging 2 lists, keeping seqeunce and origin [duplicate]

This question already has answers here:
LINQ - Full Outer Join
(16 answers)
Closed 4 years ago.
Here I have 2 lists of same object type.
object = {id: xxx, ...} // attribute "id" is used to find the identical obj
List oldSet = [old1, old2, old3];
List newSet = [new2, new3, new4];
// old2 = {id= 2, result = 5, ...}
// new2 = {id= 2, result = 1, ...}
// expected result = {oldSet: old2; newSet: new2}
I want to merge both lists, also keeping the origin of which list it came from.
The expected result as below:
List mergedSet = [{old1, null}, {old2, new2}, {old3, new3}, {null, new4}];
I'm thinking to use LINQ C# for it, but stuck somewhere.
Kindly advise.
Thanks! :)
Here's some code that does what you want using Linq. It basically walks through all the old list, and adds pairs to the merged list by looking for matches from the new list (and adding null as the second item if no match was found). Then it walks through the remaining items in the new list and adds them with null for the first item. It selects a dynamic type with two properties: OldSet and NewSet, so you know where each item came from.
The merge code is simply:
var mergedSet = oldSet.Select(o =>
new {OldSet = o, NewSet = newSet.FirstOrDefault(n => n.id == o.id)})
.Concat(newSet.Where(n => oldSet.All(o => o.id != n.id)).Select(n =>
new {OldSet = (Item) null, NewSet = n}));
This is based on the following item class:
class Item
{
public int id { get; set; }
public string result { get; set; }
public override string ToString()
{
return $"{result}{id}";
}
}
We create our lists:
List<Item> oldSet = new List<Item>
{
new Item {id = 1, result = "old"},
new Item {id = 2, result = "old"},
new Item {id = 3, result = "old"},
};
List<Item> newSet = new List<Item>
{
new Item {id = 2, result = "new"},
new Item {id = 3, result = "new"},
new Item {id = 4, result = "new"},
};
Run the merge code (very first snippet), and then display results:
foreach (var item in mergedSet)
{
Console.WriteLine($"{item.NewSet},{item.OldSet}");
}
Output
Try something like this :
List<string> oldSet = new List<string>() {"old1", "old2", "old3"};
List<string> newSet = new List<string>() {"new2", "new3", "new4"};
var results = oldSet.Select((x,i) => new { oldSet = x, newSet = newSet[i]}).ToList();
You can left join the two lists. I edited the answer as you actually need to left join twice, union, and apply a select distinct to get the cases where oldSet = null and no duplicates...
var mergedSet = (from o in oldSet
join n in newSet on o.id equals n.id into ns
from n in ns.DefaultIfEmpty()
select new { OldSet = o, NewSet = n })
.Union(from n in newSet
join o in oldSet on n.id equals o.id into os
from o in os.DefaultIfEmpty()
select new { OldSet = o, NewSet = n })
.Distinct();
Might be an overkill, but if you really want to use LINQ
List<Item> oldSet = new List<Item>
{
new Item {id = 1, result = "old"},
new Item {id = 2, result = "old"},
new Item {id = 3, result = "old"},
};
List<Item> newSet = new List<Item>
{
new Item {id = 2, result = "new"},
new Item {id = 3, result = "new"},
new Item {id = 4, result = "new"},
};
var resultL = oldSet.GroupJoin(
newSet,
o => o.id,
n => n.id,
(o,n) => new { Old = o, New = n })
.SelectMany(
n => n.New.DefaultIfEmpty(),
(o,n) => new Tuple<Item,Item>(o.Old,n));
var resultR= newSet.GroupJoin(
oldSet,
n => n.id,
o=> o.id,
(n,o) => new { Old = o, New = n })
.SelectMany(
o=> o.Old.DefaultIfEmpty(),
(n,o) => new Tuple<Item,Item>(o,n.New));
var result = resultL.Union(resultR).Distinct();
In this case, you have to use two GroupJoin and the Union the results.
Look at the following code:
var res1 = oldSet.GroupJoin(newSet, o => o, k => k, (x, y) => { var yy = y.FirstOrDefault(); return new { X = x, Y = yy }; });
var res2 = newSet.GroupJoin(oldSet, o => o, k => k, (x, y) => { var yy = y.FirstOrDefault(); return new { X = yy, Y = x }; });
var result = res1.Union(res2).ToList();// Your result is here

C# Linq To Create Group On Multiple Properties and format the resultant group into single key/property?

I have following scenario where I want to find duplicates after forming the group and realign/format the duplicate data with some common class.
Example -
var lst = new List<Test>
{
new Test{Category="A",Class="Class1",Id="101",Name="John"},
new Test{Category="B",Class="Class2",Id="102",Name="Peter"},
new Test{Category="A",Class="Class2",Id="103",Name="David"},
new Test{Category="C",Class="Class3",Id="104",Name="Julia"},
new Test{Category="D",Class="Class4",Id="105",Name="Ken"},
new Test{Category="A",Class="Class1",Id="106",Name="Robert"},
};
I have created the group as -
var group =
from c in lst
group c by new
{
c.Category,
c.Class
} into g
select new
{
Category = g.Key.Category,
Class = g.Key.Class,
Id = lst.Where(x => g.Key.Category == x.Category && g.Key.Class==x.Class)
.Select(y => y.Id).ToList()
};
Which results me 2 group items for Category A with different Classes -
GroupItem1 - Category = "A" , Class = "Class1", Id = {101,106}
GroupItem2 - Category = "A" , Class = "Class2", Id = {103}
So I have requirement to show result in such case as below with other categories as -
Category = "A", Class = "Class1 OR SomeCommonClass", Id = {101,106,103}
Is it possible to achieve this result with minimum code and optimized logic.
If you want to group by Category and get the result below is the query.
var group =
from c in lst
group c by new
{
c.Category
} into g
select new
{
Category = g.Key.Category,
Class = lst.Where(x => g.Key.Category == x.Category).Select(y => y.Class).ToList(),
Id = lst.Where(x => g.Key.Category == x.Category)
.Select(y => y.Id).ToList()
};
Replace your group query with this:
var groups =
from c in lst
group c by c.Category into g
select new { Category = g.Key, Class = g.Select(c => c.Class).Distinct().Join(" or "), IDs = g.Select(c => c.Id).ToList() };
where Join is an IEnumerable extension method:
public static string Join(this IEnumerable<string> strings, string sep) => String.Join(sep, strings.ToArray());
var group = lst.GroupBy(l => l.Category)
.Select(x => new
{
Category = x.Key,
Class = string.Join(" OR ", x.Select(c => c.Class).Distinct()),
Ids = x.Select(c => c.Id).ToList()
}).ToList();

Linq how to query a list of items for a combined list of a child collection based on a property of the parent item

Hi say I have the objects:
public class InvoiceLine
{
}
and
public class InvoiceHeader
{
public char Group { get; set; }
public List<InvoiceLine> InvoiceLines { get; set; }
}
Data is set up for them as follows:
var invoiceLine1 = new InvoiceLine();
var invoiceLine2 = new InvoiceLine();
var invoiceLine3 = new InvoiceLine();
var invoiceLine4 = new InvoiceLine();
var invoiceLine5 = new InvoiceLine();
var invoiceLine6 = new InvoiceLine();
var invoiceLine7 = new InvoiceLine();
var invoiceLine8 = new InvoiceLine();
var invoiceHeader1 = new InvoiceHeader { Group = 'A', InvoiceLines = new List<InvoiceLine> { invoiceLine1, invoiceLine2 } };
var invoiceHeader2 = new InvoiceHeader { Group = 'A', InvoiceLines = new List<InvoiceLine> { invoiceLine3, invoiceLine4 } };
var invoiceHeader3 = new InvoiceHeader { Group = 'B', InvoiceLines = new List<InvoiceLine> { invoiceLine5, invoiceLine6 } };
var invoiceHeader4 = new InvoiceHeader { Group = 'B', InvoiceLines = new List<InvoiceLine> { invoiceLine7, invoiceLine8 } };
var invoiceHeaders = new List<InvoiceHeader>
{
invoiceHeader1,
invoiceHeader2,
invoiceHeader3,
invoiceHeader4
};
What I want to get is a Lists of invoiceLines for each Group.
So I would like for group A:
invoice1, invoice2, invoice3 and invoice4
and for group B:
invoice5, invoice6, invoice7 and invoice8
The furthest I got was:
var invoiceLinesGroupA = invoiceHeaders.SelectMany(x => x.InvoiceLines);
which as far as I can tell will get all eight invoiceLines. Somehow I need to discrimate by group to just get the ones for groupA and do likewise for groupB.
Can anyone help with this?
You may just want to group the invoice headers by the group:
var groups = invoiceHeader.GroupBy(ih => ih.Group);
Then you can access the lines of the groups:
foreach(var group in groups)
{
Console.WriteLine("Group " + group.Group);
Console.WriteLine("Lines:");
Console.WriteLine(string.Join(", ", group.SelectMany(h => h.InvoiceHeader.InvoiceLines)));
}
Output would be something like
Group A
Lines:
invoice1, invoice2, invoice3, invoice4
Group B
Lines:
invoice5, invoice6, invoice7, invoice8
Look at this overload of Enumerable.SelectMany
var result = invoiceHeaders
.SelectMany(x => x.InvoiceLines,
(group, InvoiceLine)=>{group, InvoiceLine})
.Where(res => res.group='A');
This should do it:
var test = from h in invoiceHeaders
from i in h.InvoiceLines
group i by h.Group
into g
select new {key = g.Key, rows = g.ToArray()};
You can then access the items like this test.Where(x => x.key == 'A').rows
var q = from current in myInvoiceHeaders
join c in myInvoiceLines on current.Id equals c.Header.Id into linesByHeader
select new {
Header = current,
Lines = linesByHeader
};
should work under the premise that you have a criteria to join both entities.
var regrouped = invoiceHeaders
.SelectMany(
header => header.InvoiceLines,
(header, line) => new { header, line }
)
.GroupBy(
o => o.header.Group,
o => o.line,
(groupName, lines) => new InvoiceHeader
{
Group = groupName,
InvoiceLines = lines.ToList()
}
)
.ToList();

GroupBy and count the unique elements in a List

I have a list that contains only strings. What I would love to do is group by and return a count.
For instance:
Foo1
Foo2
Foo3
Foo1
Foo2
Foo2
Would result in Foo1: 2, Foo2: 3, Foo3: 1. I've tried with Linq but the list has a GroupBy that might do the trick but i messed it up, can't figure the use :(
var list = new List<string> { "Foo1", "Foo2", "Foo3", "Foo2", "Foo3", "Foo3", "Foo1", "Foo1" };
var grouped = list
.GroupBy(s => s)
.Select(group => new { Word = group.Key, Count = group.Count() });
var items= myList
.GroupBy(g => g)
.Select(t => new {count= t.Count(), key= t.Key });
foreach (var group in items)
Console.WriteLine ( group.key + " " + group.count);
var grouped = select new
{
Foo= grp.Key,
Bar= grp.Select(x => x.SomeField).Distinct().Count()
};
a working example with the NorthWind database so that you can check::
NWindCustomersDataContext dc = new NWindCustomersDataContext();
var query = (from c in dc.Customers
join o in dc.Orders on c.CustomerID equals o.CustomerID
group o by c.CustomerID into g
select new
{
CustomerID = g.Key,
Company = (from cust in dc.Customers
where cust.CustomerID == g.Key
select cust).ToList(),
Count = g.Select(x => x.OrderID).Distinct().Count()
}).OrderByDescending(y => y.Count);
foreach (var item in query)
{
Response.Write("CustomerID: " + item.CustomerID + "</br>" + "CompanyName: " + item.Company[0].CompanyName.ToString() + "</br>");
}
Here you can find a very good example
A good solution is available on http://msdn.microsoft.com/en-us/library/vstudio/bb534304(v=vs.100).aspx
It groups data by key; each key has it's own list of data you can iterate over it.

Categories