Add two objects in the same list - c#

I have a list of objects. Each object contains an ID and a value. There are several objects which have the same ID but different values. How would I go about adding the values together based on the matching ID? Additionally, how would I go about removing one entry after the addition is complete?
The object in question:
public class MyObject
{
public int ID { get; set; }
public int Value { get; set; }
}
Below is where I am getting the duplicate objects from a list of all objects. I'm simply getting all the duplicate IDs into a list of strings and then grabbing the entire duplicate object in the duplicateObjects list.
List<MyObject> myObjects = GetMyObjectsList();
List<string> duplicateIds = myObjects.GroupBy(x => x.ID)
.Where(group => group.Count() > 1)
.Select(group => group.Key).ToList();
List<MyObject> duplicateObjects = myObjects.Where(x => duplicateIds.Contains(x.ID)).ToList();
I'm stuck on the last steps which are adding the duplicate values and then removing one of the duplicates. How would I accomplish this with Linq?

Assume the list look like this
var list = new MyObject[]
{
new MyObject {ID = 1, Value = 2},
new MyObject {ID = 2, Value = 2},
new MyObject {ID = 1, Value = 3},
new MyObject {ID = 4, Value = 4},
new MyObject {ID = 2, Value = 4},
};
Then just select from list group by ID and sum value like this
var result = (from tm in list
group tm by tm.ID into Test
select new
{
ID = Test.Key,
Value = Test.Sum(x => x.Value)
});
Output
ID = 1, Value = 5
ID = 2, Value = 6
ID = 4, Value = 4
working fiddle here

I hope I understood your question correctly.As clarified in the comment,you want to "sum" all the values grouped by ID and then remove one of the duplicates. Please verify if the following is how you could like it to be behave.
var duplicates = myObjects.GroupBy(x => x.ID)
.Select(group => new { Group = group.Count() > 1 ? group.ToList().Take(group.Count()-1) : group.ToList(),Sum=group.Count()});
For Input
Output
If you need to exclude non-duplicates, then you would need to include an additional Where Condition
var duplicates = myObjects.GroupBy(x => x.ID)
.Where(x=>x.Count()>1)
.Select(group => new { Group = group.Count() > 1 ? group.ToList().Take(group.Count()-1) : group.ToList(),Sum=group.Count()});

Related

Select all from one List, replace values that exist on another List

Few days ago I asked same question with SQL, but now it arises in C# code
Lets say we have this kind of class for holding different id/text pairs:
public class Text {
public int id { get; set; }
public string text { get; set; }
...
}
Now lets populate some data,
ListA gets a lot of data:
List<Text> ListA = new List<Text>{
new () {id = 1, text = "aaa1"},
new () {id = 2, text = "aaa2"},
new () {id = 3, text = "aaa3"},
new () {id = 4, text = "aaa4"},
new () {id = 5, text = "aaa5"},
new () {id = 6, text = "aaa6"},
};
And ListB gets just a little bit of data:
List<Text> ListB = new List<Text>{
new () {id = 4, text = "bbb4"},
new () {id = 5, text = "bbb5"},
};
And now what we are looking:
var result = ... // Some Linq or Lambda magic goes here
// and if we do:
foreach(var item in result){
Console.WriteLine(item.Id + " " + item.Text);
}
// Result will be:
1 : aaa1
2 : aaa2
3 : aaa3
4 : bbb4
5 : bbb5
6 : aaa6
You can try looking for id within ListB:
var result = ListA
.Select(a => ListB.FirstOrDefault(b => b.id == a.id) ?? a);
Here for each a within ListA we try to find corresponding by id (b.id == a.id) item within ListB. If no such item is found we just return ListA item: ?? item
In case of .Net 6 you can use overloaded .FirstOrDefault version (we can pass a as a default value):
var result = ListA
.Select(a => ListB.FirstOrDefault(b => a.id == b.id, a));
It might be more efficient to convert ListB to a Dictionary first:
var dictB = ListB.ToDictionary(x=> x.id)
Then you can write
var result = ListA.Select(x => dictB.TryGetValue(x.id, out var b) ? b : x)
UPD Rewrote taking comment suggestions into account
One option is to do an Union operation, by specifying an EqualityComparer. If the order is important, you can do an OrderBy operation at the end.
class TextIdComparer : EqualityComparer<Text> {
public override bool Equals(Text x, Text y) => x.id == y.id;
}
var result = ListB.Union(ListA, new TextIdComparer()).OrderBy(x => x.id)

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 2 lists based on one property and concatenate other properties

IList<MyObject> ob1 = {new MyObject {Id = "1", Items = {BananaObject1, BananaObject2}}}
IList<MyObject> ob2 = { new MyObject {Id = "1", Items = {BananaObject2, BananaObject3}},
new MyObject {Id = "2", Items = {BananaObject3, BananaObject3}}}
I want to merge the 2 lists such that the resulting list would be
IList<MyObject> ob2 = { new MyObject {Id = "1", Items = {BananaObject1, BananaObject2, BananaObject3}},
new MyObject {Id = "2", Items = {BananaObject3, BananaObject3}}}
So since the id of the first item of the 2 lists were the same, they became one, and one of their property is concatenated.
I can do a for loop to achieve this, but I am looking for a neat best performance linq expression for this.
thank you
Concat the lists together, GroupBy Id property, SelectMany to get the merged list of items:
ob1.Concat(ob2)
.GroupBy(o => o.Id)
.Select(g => new MyObject()
{
Id = g.Key,
Items = g.SelectMany(o => o.Items ).Distinct().ToList()
});
Use MoreLINQ:
obj1.FullGroupJoin(obj2,
a=>a.Id,
b=>b.Id,
(id,a,b)=>new {id=id,Items=a.Items.Union(b.Items)},
new {id=-1, Items=new List<string>()}, //Default for left side
new {id=-2, Items=new List<string>()});//Default for right side

Projecting aggregated data to flat one

How can I get from this:
var props = new List<RoomProperties>();
props.Add(new RoomProperties(new[] {3, 4, 5}, new string[] {"AC", "WC"}));
props.Add(new RoomProperties(new[] {2}, new string[] {"AC", "TV"}));
props.Add(new RoomProperties(new[] {3}, new string[] {"Music", "Phone"}));
To this:
Key = 2, Values = ("AC", "TV")
Key = 3, Values = ("AC", "WC","Music", "Phone" )
Key = 4, Values = ("AC", "WC")
Key = 5, Values = ("AC", "WC")
With props.props.SelectMany() I can get keys to flatten, but values are not associated with it. If you have a good idea how do do this in elegant way I would appreciate that.
Basically I want room properties concatenated per ID and IDs are uniquely represented with that data.
private class RoomProperties
{
public readonly int[] RoomIds;
public string[] Properties { get; private set; }
public RoomProperties(int[] roomIds, string[] properties)
{
RoomIds = roomIds;
Properties = properties;
}
}
You can flatten sequence to anonymous objects with room id and room properties. Then group these object by room id, and select all properties from group (I also added Distinct to avoid duplicated values for room):
props.SelectMany(p => p.RoomIds.Select(id => new { id, p.Properties }))
.GroupBy(x => x.id)
.Select(g => new {
g.Key,
Values = g.SelectMany(x => x.Properties).Distinct()
});

"in" operator in linq c#?

I have a generic list which contains member details and I have a string array of memberIds..I need to filter the list and get the results which contains all the memberIds..How can I achieve this using LINQ.
I tried the following
string[] memberList = hdnSelectedMemberList.Value.Split(',');
_lstFilteredMembers = lstMainMembers.Where(p =>memberList.Contains(p.MemberId))
.ToList();
But the above query is giving me only the results that match the first member ID..so lets say if I have memberIds 1,2,3,4 in the memberList array..the result it returns after the query contains only the members with member ID 1..even though the actual list has 1,2,3,4,5 in it..
Can you please guide me what I am doing wrong.
Thanks and appreciate your feedback.
Strings make terrible primary keys. Try trimming the list:
string[] memberList = hdnSelectedMemberList.Value
.Split(',')
.Select(p => p.Trim())
.ToList();
_lstFilteredMembers = lstMainMembers.Where(p => memberList.Contains(p.MemberId)).ToList();
Because I have a feeling hdnSelectedMemberList may be "1, 2, 3, 4".
Use a join:
var memquery = from member in lstMainMembers
join memberid in memberList
on member.MemberId equals memberid
select member;
With jmh, I'd use a join
var members = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var ids = new[] { 1, 3, 6, 14 };
var result = members.Join(ids, m => m, id => id, (m, id) => m);
foreach (var r in result)
Console.WriteLine(r); //prints 1, 3, 6
The code you are showing is correct, and works in a Unit Test:
public class Data
{
public string MemberId { get; set; }
}
[TestMethod]
public void Your_Code_Works()
{
// Arrange fake data.
var hdnSelectedMemberList = "1,2,3,4";
var lstMainMembers = new[]
{
new Data { MemberId = "1" },
new Data { MemberId = "2" },
new Data { MemberId = "3" },
new Data { MemberId = "4" },
new Data { MemberId = "5" }
};
// Act - copy/pasted from StackOverflow
string[] memberList = hdnSelectedMemberList.Split(',');
var _lstFilteredMembers = lstMainMembers.Where(p => memberList.Contains(p.MemberId)).ToList();
// Assert - All pass.
Assert.AreEqual(4, _lstFilteredMembers.Count);
Assert.AreEqual("1", _lstFilteredMembers[0].MemberId);
Assert.AreEqual("2", _lstFilteredMembers[1].MemberId);
Assert.AreEqual("3", _lstFilteredMembers[2].MemberId);
Assert.AreEqual("4", _lstFilteredMembers[3].MemberId);
}
There must be something wrong with your code outside what you have shown.
Try Enumerable.Intersect to get the intersection of two collections:
http://msdn.microsoft.com/en-us/library/system.linq.enumerable.intersect.aspx
_lstFilteredMembers = lstMainMembers.Intersect(memberList.Select(p => p.MemberID.ToString())).ToList()
Why not just project the IDs list into a list of members?
var result = memberList.Select(m => lstMainMembers.SingleOrDefault(mm => mm.MemberId == m))
Of course, that will give you a list that contains null entries for items that don't match.
You could filter those out, if you wanted to...
result = result.Where(r => r != null)
Or you could filter it before the initial select...
memberList.Where(m => lstMainMembers.Any(mm => mm.MemberId == m)).Select(m => lstMainMembers.Single(mm => mm.MemberId == m))
That's pretty ugly, though.

Categories