Grouping in a generic list - c#

I have a generic list:
List<Test> lstReport = new List<Test>();
lstReport.Add(new Test { ID = 1, Category = "Hot work", Approver = "Praveen" });
lstReport.Add(new Test { ID = 1, Category = "Civil work", Approver = "Praveen" });
lstReport.Add(new Test { ID = 1, Category = "Others", Approver = "Praveen" });
lstReport.Add(new Test { ID = 4, Category = "Hot work", Approver = "Praveen" });
lstReport.Add(new Test { ID = 5, Category = "Critical work", Approver = "Praveen" });
Now I want to take unique rows from the generic list by the ID value.
For eg, I need to group the rows having same ID and if the existence of ID's more than once, then its Category field has to be changed to "Grouped".
How can I write this expression?

This Works:
var QueryResult = (from x in lstReport
group x by x.ID into res
select new Test
{
ID = res.Key,
Category = res.Count() > 1 ? "Grouped" : res.First().Category,
Approver = res.First().Approver
}).ToList();

List<Test> lstReport = new List<Test>();
...
var groups = lstReport.GroupBy(i => i.ID).ToList();
foreach(var group in groups) {
var items = group.ToList();
if(items.Count > 1)
items.ForEach(i => { i.Category = "Grouped"; });
}

You can try this
var q = from r in lstReport
group r by r.ID into g
select new Test{ID=g.Key, Category = g.Count()>1? "Grouped" : g.Min(Category)};
also you can have g.Max(...) or g.Min(...)

You can use Linq to group objects based on some property:
var list = lstReport.GroupBy(t => t.ID);
This will return a new collection with 3 items: IEnumerable<IGrouping<TKey, TSource>>; which basically means a collection containing a collection with one or more items.
Then you can iterate over that new collection and check if each item contains more than one subitem; if it does, then iterate over the subitems and modify the Category value.
http://msdn.microsoft.com/en-us/library/system.linq.enumerable.groupby(v=vs.110).aspx

Related

Get items from List A where Id is common in both List A and List B and counter of that Id is more than 1 in List A or List B

I was looking to get items from ListA, where the value of Id is same in both of the lists, and the count of Id must be more than 1 in list A or list B
var items = itemsA.Where(x => itemsB.Select(y => y.Id == x.Id).Count() > 1);
This gives me the result where same Ids in itemsB is more then 1, I want to use a or condition to check for the same counter in itemsA
Eg 1:
ListA=[{"id"=1,"name="abc"},{"id=1, "name"="def"}]
ListB=[{"id=2","name="xyz"}, {"id=1, "name"="mno"}]
Should return [{"id"=1,"name="abc"},{"id=1, "name"="def"}] because id =1 exists in listB and the count of id with value 1 in listA is more then 1.
Eg 2:
ListA=[{"id"=2,"name="abc"},{"id=1, "name"="def"}]
ListB=[{"id=1","name="xyz"}, {"id=1, "name"="mno"}]
should return {"id=1, "name"="def"} because common id in both list is 1 and the count of id with value 1 in ListB is more then 1.
I am not certain this is the best solution, but as far as I've understood the question, it should be a solution.
Assuming you have an Item class as follows:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
and define itemsA and itemsB as List<Item>s, you can first find all Ids that are present in both lists, then select the applicable items from itemsA based on occurrence of each Id in either list:
IEnumerable<int> idsInBothItemLists = itemsA
.Select(a => a.Id)
.Intersect(itemsB.Select(b => b.Id))
.Distinct();
List<Item> items = itemsA
.Where(a => idsInBothItemLists.Contains(a.Id))
.GroupBy(a => a.Id)
.Where(gr =>
gr.Skip(1).Any() ||
itemsB.Where(b => b.Id == gr.Key).Skip(1).Any())
.SelectMany(gr => gr.Select(item => item))
.ToList();
(.Skip(1).Any() serves the same purpose as .Count() > 1 in your original code; it simply checks whether there are any items left after skipping the first item.)
Printing the output from the suggested population of itemsA and itemsB
foreach (var entry in items)
{
Console.WriteLine(entry.Id + " " + entry.Name);
}
e.g. for input
var itemsA = new List<Item>
{
new Item { Id = 1, Name = "abc" },
new Item { Id = 3, Name = "def" },
new Item { Id = 1, Name = "ghi" },
new Item { Id = 2, Name = "jkl" }
};
var itemsB = new List<Item>
{
new Item { Id = 2, Name = "xyz" },
new Item { Id = 2, Name = "jkl" },
new Item { Id = 1, Name = "mno" },
new Item { Id = 3, Name = "pqr" }
};
gives
1 abc
1 ghi
2 jkl

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

Add duplicates together in List

First question :)
I have a List<Materiau> (where Materiau implements IComparable<Materiau>), and I would like to remove all duplicates and add them together
(if two Materiau is the same (using the comparator), merge it to the first and remove the second from the list)
A Materiau contains an ID and a quantity, when I merge two Materiau using += or +, it keeps the same ID, and the quantity is added
I cannot control the input of the list.
I would like something like this:
List<Materiau> materiaux = getList().mergeDuplicates();
Thank you for your time :)
Check out Linq! Specifically the GroupBy method.
I don't know how familiar you are with sql, but Linq lets you query collections similarly to how sql works.
It's a bit in depth to explain of you are totally unfamiliar, but Code Project has a wonderful example
To sum it up:
Imagine we have this
List<Product> prodList = new List<Product>
{
new Product
{
ID = 1,
Quantity = 1
},
new Product
{
ID = 2,
Quantity = 2
},
new Product
{
ID = 3,
Quantity = 7
},
new Product
{
ID = 4,
Quantity = 3
}
};
and we wanted to group all the duplicate products, and sum their quantities.
We can do this:
var groupedProducts = prodList.GroupBy(item => item.ID)
and then select the values out of the grouping, with the aggregates as needed
var results = groupedProducts.Select( i => new Product
{
ID = i.Key, // this is what we Grouped By above
Quantity = i.Sum(prod => prod.Quantity) // we want to sum up all the quantities in this grouping
});
and boom! we have a list of aggregated products
Lets say you have a class
class Foo
{
public int Id { get; set; }
public int Value { get; set; }
}
and a bunch of them inside a list
var foocollection = new List<Foo> {
new Foo { Id = 1, Value = 1, },
new Foo { Id = 2, Value = 1, },
new Foo { Id = 2, Value = 1, },
};
then you can group them and build the aggregate on each group
var foogrouped = foocollection
.GroupBy( f => f.Id )
.Select( g => new Foo { Id = g.Key, Value = g.Aggregate( 0, ( a, f ) => a + f.Value ) } )
.ToList();
List<Materiau> distinctList = getList().Distinct(EqualityComparer<Materiau>.Default).ToList();

Merge equal items with multiple lists but different length

I have 2 lists.
They are different in length but same type.
I want that an Item from List2 replaces an equal item in List1.
var item1 = new Item { Id = 1, Name = "Test1" };
var item2 = new Item { Id = 2, Name = "Test2" };
var item3 = new Item { Id = 3, Name = "Test3" };
var item4 = new Item { Id = 4, Name = "Test4" };
var item5 = new Item { Id = 5, Name = "Test5" };
var list1 = new List<Item> { item1, item2, item3, item4, item5 };
var list2 = new List<Item> { new Item { Id = 1, Name = "NewValue" } };
As a result I expect a list with 5 items where the item with Id = 1 has a value "NewValue".
How can I do that preferable with linq.
UPDATE
I extend my question:
How can the replacement of the replaced Item happen without copying all properties manually. Just imagine I have 100 properties...
This is one way to do it:
First define an equality comparer that depends only on the Id property of the Item class like this:
public class IdBasedItemEqualityComparer : IEqualityComparer<Item>
{
public bool Equals(Item x, Item y)
{
return x.Id == y.Id;
}
public int GetHashCode(Item obj)
{
return obj.Id.GetHashCode();
}
}
Then you can take items list1 that don't have corresponding items in list2 using the Except method and then you can concatenate that with list2 using the Concat method like this:
var result = list1.Except(list2, new IdBasedItemEqualityComparer()).Concat(list2).ToList();
Notice how I use the IdBasedItemEqualityComparer with the Except method, so that comparison is based only on Id.
Off the top of my head this is one solution
var list3 = new List<Item>();
foreach (var item in list1)
list3.Add(list2.FirstOrDefault(s => s.Id == item.Id) ?? item);
I think LEFT OUTER JOIN in Linq will be able to merge 2 lists regardless of number of properties(columns) like this:
List<Item> newItems =
(from l1 in list1
join l2 in list2 on l1.Id equals l2.Id into l12
from l2 in l12.DefaultIfEmpty()
select new { Item = (l2 == null) ? l1 : l2 }).Select(r => r.Item).ToList();

Applying a filter to subsequences of a sequence using Linq

If I have a List<MyType> as so, with each line representing an item in the collection:
{{ Id = 1, Year = 2010 },
{ Id = 1, Year = 2009 },
{ Id = 1, Year = 2008 },
{ Id = 2, Year = 2010 },
{ Id = 2, Year = 2009 },
{ Id = 2, Year = 2008 }}
I wish to retrieve a collection from this collection of the most recent item for each Id. What will the Linq for this look like?
Desired output:
{{ Id = 1, Year = 2010 },
{ Id = 2, Year = 2010 }}
I have a naiive implementation using a second list variable and a foreach loop, but it's inefficient.
//naiive implementation "p-code"
//...
var mostRecentItems = new List<MyType>();
var ids = collection.Select(i => i.Id).Distinct();
foreach(var id in ids)
{
mostRecentItems.Add(collection.Where(i => i.Id == id).OrderByDescending().First);
}
return mostRecentItems;
Most simply:
var mostRecentById = from item in list
group item by item.Id into g
select g.OrderByDescending(x => x.Year).First();
Group by id, then select the first item in each group ordered in a descending fashion.
var mostRecentItems = collection.GroupBy( c => c.Id )
.Select( g => g.OrderByDescending( i => i.Year ).First() );
or more simply still:
var result = list
.GroupBy(i => i.Id)
.Select(g => new {Id = g.Key, Year = g.Max(y => y.Year)});

Categories