Merge items in list with the same key/property - c#

This shouldn't be very hard to do but I'm struggling to find out the correct way to do this.
I have two lists that have the same properties. What I would like to do is merge these two lists together and combine/merge the individual items in the lists that have the same key/property.
Here is my ViewModel:
public class GradeViewModel {
public int GradeId { get; set; }
public string Name { get; set; }
public int Total { get; set; }
}
And here I am populating my lists:
var buyPostGrades = Mapper.Map<IEnumerable<BuyPostGrade>, IEnumerable<GradeViewModel>>(_reportService.GetBuyPostGrades());
var sellPostGrades = Mapper.Map<IEnumerable<SellPostGrade>, IEnumerable<GradeViewModel>>(_reportService.GetSellPostGrades());
Now what I would like to do is combine the two lists together into one and if there are any items between the lists that share the same GradeId then I want to merge them into just one item.
So, for example, if I had an item in my buyPostGrades list that looked like the following:
GradeId = 6,
Name = "TestGrade",
Total = 4
and then I had an item in my sellPostGrades list that looked like the following:
GradeId = 6,
Name = "TestGrade",
Total = 10
then I would like to merge those two items into one that would look like:
GradeId = 6,
Name = "TestGrade",
Total = 14
I'm sure there in some Linq that I can use to do this but I'm still new to it and am not sure which way would be best.

Assuming that identical grade ID implies identical name, you can merge two lists as follows:
var res buyPostGrades
.Concat(sellPostGrades)
.GroupBy(x => new {x.GradeId, x.Name})
.Select(g => new GradeViewModel {
GradeId = g.Key.GradeId
, Name = g.Key.Name
, Total = g.Sum(x => x.Total)
}).ToList();

Related

Group IEnumerable into a string

I wonder if someone could spare me a few minutes to give me some advice please?
I've created an IEnumerable list:
public class EmailBlock
{
public int alertCategory { get; set; }
public string alertName { get; set; }
public string alertURL { get; set; }
public string alertSnippet { get; set; } //Need to work out the snippet
}
List<EmailBlock> myEmailData = new List<EmailBlock>();
Which I then loop through some data (Umbraco content - not that that's really relevant!) and add items to the list.
myEmailData.Add(new EmailBlock { alertCategory = category.Id, alertName = alert.GetPropertyValue("pageTitle"), alertURL = alert.NiceUrl });
What ultimately I'd like to do is group the list by the alertCategory and then load each 'group' (another loop occurs later to check what members have subscribed to what alert category) into a variable which I can then use as an email's content.
You could use Linq's GroupBy() to do this:
using System.Linq
...
//Create a type to hold your grouped emails
public class GroupedEmail
{
public int AlertCategory { get; set; }
public IEnumerable<EmailBlock> EmailsInGroup {get; set; }
}
var grouped = myEmailData
.GroupBy(e => e.alertCategory)
.Select(g => new GroupedEmail
{
AlertCategory = g.Key,
EmailsInGroup = g
});
You can select to an anonymous type if required and project your sequence into whatever structure you require.
Linq has a nice group by statement:
var emailGroup = emailList.GroupBy(e => e.alertCategory);
Then you can loop through each grouping and do whatever you want:
foreach(var grouping in emailGroup)
{
//do whatever you want here.
//note grouping will access the list of grouped items, grouping.Key will show the grouped by field
}
Edit:
To retrieve a group after you have grouped them, just use Where for more than one or First for just one:
var group = emailGroup.First(g => g.Key == "name you are looking for");
or
var groups = emailGroup.Where(g => listOfWantedKeys.Contains(g.Key));
this is a lot more efficient than looping through every time you need to find something.

Changing GroupBy keys depending on Class Structure

I have a list of items with multiple columns and would like to group them by some fields depending on a boolean:
I have the following class:
public class Item
{
public string Group { get; set; }
public string Person { get; set; }
public string Currency { get; set; }
public string Country { get; set; }
public string County { get; set; }
public string OtherAdd { get; set; }
public string Income { get; set; }
}
which is part of a List:
var results = items.ToList(); //items is IEnumerable<Item>
if int type = 1, then I want to group by more elements:
results = results
.GroupBy(e => new { e.Group, e.Person, e.Branch, e.Currency, e.Country, e.County, e.OtherAdd})
.Select(g => new Item
{
Group = g.Key.Group,
Person = g.Key.Person,
Currency = g.Key.Currency,
Currency = g.Key.Country,
Currency = g.Key.County,
Currency = g.Key.OtherAdd,
Income = g.Sum(p => double.Parse(p.Income, System.Globalization.CultureInfo.InvariantCulture)).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
})
.ToList();
if int type = 2, then I want to group by fewer elements (e.g. because OtherAdd would be an empty String):
results = results
.GroupBy(e => new { e.Group, e.Person, e.Branch, e.Currency})
.Select(g => new Item
{
Group = g.Key.Group,
Person = g.Key.Person,
Currency = g.Key.Currency,
Income = g.Sum(p => double.Parse(p.Income, System.Globalization.CultureInfo.InvariantCulture)).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
})
.ToList();
etc.
Is there a way for me to change the GroupBy key depending on my integer type without repeating the code?
Well, you could use the old SQL trick, conditional values:
.GroupBy(e => new { e.Group, Person = (e.Type == 1 ? e.Person : Guid.NewGuid().ToString()), ... }
While this will still include the columns in the group by, all the items will have unique keys, so it doesn't quite matter. Sadly, I don't think there's a way around generating the unique keys, unlike in SQL (where you could just use NULL).
A better way might be to implement your own grouping class, instead of using an anonymous type. You could then use your own equality and hashing semantics, to make sure whether you include all the fields or not. However, that is arguably going to be more work than just having the similar code repeated.
Or, you might want to revise your whole design. It doesn't sound like what you're trying to do makes much sense - it's already quite suspicious that you're using the same type for two different things, and using strings for all the fields doesn't help either. Maybe you could try a different object design?

LINQ Sum list of items grouped by type inside list

I have the following order object which contains a list of order addons. I am trying to create a report that shows all the addon types and their quantities summed.
public class Order {
public IList<OrderAddon> OrderAddons { get; set; }
}
public class OrderAddon {
public enum OrderType { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}
This is where I am at and can't figure out if the entire query is wrong of I am just missing something.
var query = from order in Model.Orders
from addon in order.OrderAddons
group order by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.) << This is where I am stuck
};
When I hit . my intellisense is showing me properties in order object not the addon object.
That's because you're saying group order by ..., so the orderAddons object becomes a grouping of orders. You can use this if you're going to need properties from both objects:
from order in Model.Orders
from addon in order.OrderAddons
group new{addon, order} by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.addon.Quantity)
};
If this is all the data you need, this is a little simpler:
from order in Model.Orders
from addon in order.OrderAddons
group order.Quantity by addon.AddonType
into quantityByAddonType select new
{
Name = quantityByAddonType.Key,
Quantity = quantityByAddonType.Sum()
};
an alternative syntax same result...
var result = Model.Orders
.SelectMany(order => order.OrderAddons)
.GroupBy(addon => addon.OrderType)
.Select(grouping => new
{
Name = grouping.Key,
Quantity = grouping.Sum(addon => addon.Quantity)
});

Linq Comparing Two Lists and Generating a New One

I am Working on a MVC4 project and I am in need to create a new list of objects by comparing two lists. db below refers to the DbContext object.
In the project that I work, I am in need of comparing two lists. The first has all the subjects offered in a semester. The other has the subjects selected by the student.
List<Subject> subjects = db.Subjects;
//...
List<Subject> semesterSubjects = student.Subjects;
Out of this two lists I need to create a list of items that state if or not each an every item is selected. It looks like this.
class SubjectSelection
{
public int ID { get; set; }
public String Title { get; set; }
public bool IsSelected { get; set; }
}
What I need now is to compare subjects & semesterSubjects and get a IEnumerable<SubjectSelection>
Could I use Linq to get such a result and how?
Best way is to do this job on the server side. Select ids from your semester subjects:
var selectedIDs = semesterSubjects.Select(s => s.ID).ToList();
And pass them to query:
var query = db.Subjects.Select(s => new SubjectSelection()
{
ID = s.ID,
Title = s.Title,
IsSelected = selectedIDs.Contains(s.ID)
});
If you are using same db context instance to retrieve both semester subjects list and subjects list, then you can simply use:
var query = subjects.Select(s => new SubjectSelection()
{
ID = s.ID,
Title = s.Title,
IsSelected = semesterSubjects.Contains(s)
});
Entity Framework implements identity map pattern, which means it returns same in-memory instance of subject object, which returned by different queries.
var allSubjectsWithSelection = subjects.Select(o=>new SubjectSelection(){ID = o.ID,Title = o.Title, IsSelected = semesterSubjects.Any(s=>s.ID == o.ID)}).ToList();

Easiest way to merge two List<T>s

I've got two List<Name>s:
public class Name
{
public string NameText {get;set;}
public Gender Gender { get; set; }
}
public class Gender
{
public decimal MaleFrequency { get; set; }
public decimal MaleCumulativeFrequency { get; set; }
public decimal FemaleCumulativeFrequency { get; set; }
public decimal FemaleFrequency { get; set; }
}
If the NameText property matches, I'd like to take the FemaleFrequency and FemaleCumulativeFrequency from the list of female Names and the MaleFrequency and MaleCumulativeFrequency values from the list of male Names and create one list of Names with all four properties populated.
What's the easiest way to go about this in C# using .Net 3.5?
Are you attempting to sum each of the values when you merge the lists? If so, try something like this:
List<Name> list1 = new List<Name>();
List<Name> list2 = new List<Name>();
List<Name> result = list1.Union(list2).GroupBy(x => x.NameText).Select(x => new
Name
{
NameText = x.Key,
Gender = new Gender
{
FemaleCumulativeFrequency = x.Sum(y => y.Gender.FemaleCumulativeFrequency),
FemaleFrequency = x.Sum(y => y.Gender.FemaleFrequency),
MaleCumulativeFrequency = x.Sum(y => y.Gender.MaleCumulativeFrequency),
MaleFrequency = x.Sum(y => y.Gender.MaleFrequency)
}
}).ToList();
What this does is the following:
Unions the lists, creating an IEnumerable<Name> that contains the contents of both lists.
Groups the lists by the NameText property, so if there are duplicate Names with the same NameText, they'll show up in the same group.
Selects a set of new Name objects, with each grouped Name's properties summed... you can also use Average if that makes more sense.
Converts the entire query to a List<Name> by calling the "ToList()" method.
Edit: Or, as you've said below, you simply want to merge the two lists... do this:
List<Name> allNames = femaleNames.Union(maleNames).ToList();
This looks a lot like the census name frequency data, right? Gender is a bit of a misnomer for the class you have it's more like "FrequencyData".
In effect you want a Dictionary so you can lookup any name and get the four values for it. You could simply take the males and do ToDictionary(...) on it and then iterate over the females, if the name exists in the dictionary, replace the female probabilities on it, if it doesn't exist, create a new dictionary entry.
My own approach to this same data was to create a Table in a database with all four values attached.
Here's some code for your scenario ...
Dictionary<string, Gender> result;
result = males.ToDictionary(x => x.NameText, x => x.Gender);
foreach (var female in females)
{
if (result.ContainsKey(female.NameText))
{
result[female.NameText].FemaleCumulativeFrequency = female.Gender.FemaleCumulativeFrequency;
result[female.NameText].FemaleFrequency = female.Gender.FemaleFrequency;
}
else
result.Add(female.NameText, female.Gender);
}
I think this could be what you want although I'm not sure if it handles the cumulative frequencies as you'd expect:
var mergedNameList = maleNames
.Concat(femaleNames)
.GroupBy(n => n.NameText)
.Select(nameGroup => new Name
{
NameText = nameGroup.Key,
Gender = new Gender
{
MaleFrequency = nameGroup.Sum(n => n.Gender.MaleFrequency),
MaleCumulativeFrequency = nameGroup.Sum(n => n.Gender.MaleCumulativeFrequency),
FemaleFrequency = nameGroup.Sum(n => n.Gender.FemaleFrequency),
FemaleCumulativeFrequency = nameGroup.Sum(n => n.Gender.FemaleCumulativeFrequency)
}
}.ToList();

Categories