Easiest way to merge two List<T>s - c#

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

Related

c# select one generic list into new form

I've the following class and generic list:
public class maxFormat
{
public string asin { get; set; }
public string _new { get; set; }
public string _likenew { get; set; }
public string _verygood { get; set; }
public string _good { get; set; }
public string _acceptable { get; set; }
}
List[MaxFormat] lstMax= new List[MaxFormat]();
I need to make some price calculation on around 2 million records and then add the results into the my generic list then i need to flatten the results into one. The field called "asin" is common for my records and one asin can have multiple prices like "new","good" etc.
So i've tried the FirstOrDefault() property of the linq to find and update if an asin already have a price but it doesnt give result. i think it takes too long.
Finally i decided to add records first and then group by and distinct them using linq to flatten results into one but performance is not enough for me because of Select property projects each element of a sequence. So these operations are performed for all elements.
var Final = maxFormat.GroupBy(x => x.asin ).Select(y => new
{
asin = y.Key.asin,
_new = y.Where(z => !string.IsNullOrEmpty(z._new)).Select(z => z._new).FirstOrDefault(),
_likenew = y.Where(z => !string.IsNullOrEmpty(z._likenew)).Select(z => z._likenew).FirstOrDefault(),
_verygood = y.Where(z => !string.IsNullOrEmpty(z._verygood)).Select(z => z._verygood).FirstOrDefault(),
_good = y.Where(z => !string.IsNullOrEmpty(z._good)).Select(z => z._good).FirstOrDefault(),
_acceptable = y.Where(z => !string.IsNullOrEmpty(z._acceptable)).Select(z => z._acceptable).FirstOrDefault()
}).Distinct().ToList();
So these codes are working fine but i need to improve the performance.
I need something like in EX:
Existing records in list:
ASIN=0000000001 New Price= 50.1; LikeNew Price = NULL; VeryGood Price= NULL;
Good Price=NULL;Acceptable Price= NULL;
ASIN=0000000001 New Price= NULL; LikeNew Price = 25; VeryGood Price= NULL;
Good Price=NULL;Acceptable Price= NULL;
Final result in var Final:
ASIN=0000000001 New Price= 50.1; LikeNew Price = 25; VeryGood Price= NULL;
Good Price=NULL;Acceptable Price= NULL;
Any suggestions?

Merge items in list with the same key/property

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

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

Categories