LINQ: Group By, and retrieve common property - c#

I have the following dataset, which is stored in a variable called list1.
countrypk | countryname | statepk | statename
---------------------------------------------
1 USA 1 New York
1 USA 2 California
2 Canada 3 Manitoba
I want to be able to group by countrypk, and retrieve the country name.
I have the following LINQ that achieves that effect, but was wondering if there was a better or more straight forward way to do it in LINQ.
var finalList = list1
.GroupBy(item => item.countrypk)
.Where(item => item.Count() > 0)
.Select(item => item.First())
The desired output is:
countrypk | countryname
---------------------------------------------
1 USA
2 Canada

The addition of the Where is not needed. If you have a group it contains at least a single item in it. You can do something like this:
list1.GroupBy(item => item.countrypk)
.Select(item => new { item.Key, item.First().countryname} );
Or using a different overload of GroupBy:
list1.GroupBy(item => item.countrypk,
selector => new { selector.countrypk, selector.countryname} )
.Select(group => group.First())

If you grouped by countrypk, your result set wouldn't have duplicates in it. Your desired result set has duplicate countrypk values in it (1). To get your desired result set, do this:
var finalList = list1.Select(s => new { s.countrypk, s.countryname });
Edit: nevermind this part above, OP editted the question.
I want to be able to group by countrypk, and retrieve the country name What you're asking for is different than what your result set shows. If you want a map of countrypk to country name using your list1, here's one way to do it:
var finalList = list1
.GroupBy(g => new { g.countrypk, g.countryname })
.ToDictionary(k => k.Key.countrypk, v => v.Key.countryname);
Note that you don't need GroupBy to do this. Here's another solution:
var finalList = list1
.Select(s => new { s.countrypk, s.countryname })
.Distinct()
.ToDictionary(k => k.countrypk, v => v.countryname);
In either case, to get the country name for id 1, do this:
var countryName = finalList[1];

Try the following
var finalList = list1
.GroupBy(item => item.countrypk)
.Select(g => new { countrypk = g.Key, countryname = g.First().countryname });
which should provided the desired output

Basically you want to remove duplicates by countrypk and select only first two columns? Use this extension:
public static IEnumerable<TSource> DistinctBy<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
HashSet<TResult> set = new HashSet<TResult>();
foreach(var item in source)
{
var selectedValue = selector(item);
if (set.Add(selectedValue))
yield return item;
}
}
And then
var finalList = list1
.DistinctBy(item => item.countrypk)
.Select(item=> new {item.countrypk, item.countryname })
.ToList();

Related

How to select duplicate value from a list in C# based on condition?

I have a list with date and Fileno as values. I need to find the duplicate date and based on that find the highest Fileno.Then add that keyvalue pair and the distinct pair to the final list. The result should be as shown below.I am able to get the duplicate date but how to compare the duplicate dates and find the highest fileno?
Key Date Fileno
1 10/8/1980 1234
2 10/8/1980 1345
3 8/6/1970 4567
Result
2 10/8/1980 1345
3 8/6/1970 4567
Code
var list = new List<valuepair>();
list.Add(new valuepair {no=key,comdate=date,filnum=fileno})
Var dup= list.GroupBy(x => comdate.Value).Where(x => comdate.Count() > 1)
You're almost there, except the code you've posted is not compilable (!). You just need to take the element with the highest "filnum" from each group:
var list = new List<valuepair>();
list.Add(new valuepair { no=1, comdate="10/8/1980", filnum=1234 });
list.Add(new valuepair { no=2, comdate="10/8/1980", filnum=1345 });
list.Add(new valuepair { no=3, comdate="8/6/1970", filnum=4567 });
var listWithoutDuplicates = list.GroupBy(x => x.comdate)
// For each group (in which items have identical dates) take only
// the one with the highest "filnum"
.Select(group => group.OrderBy(x => x.filnum).First())
.ToList();
Try this:
var result = data
.GroupBy(
i => i.Date,
(key, group) => group.Single(x => x.Fileno == group.Max(y => y.Fileno)))
.ToList();

C# Linq - Refactoring ForEach Loop with Sub List

Am trying to refactor some data in order to display some charts.
I can't seem to figure out why using the following, it lists all the values at the top rather than being sequential like the source data.
var categories = VehicleSales.Select(v => v.name).Distinct().ToList();
var refactoredResults = new List<StackedColumnChart>();
foreach (var category in categories)
{
var subresult = VehicleSales.Where(x => x.vehicleType == category)
.GroupBy(x => x.vehicleType)
.Select(gcs => new StackedColumnChart
{
Category = category,
Values = gcs.Select(x => (int)x.data).DefaultIfEmpty(0).ToList()
}).ToList();
refactoredResults.AddRange(subresult);
}
Source Data:
Then the actual results and expected results:
Thanks in advance!
You can do that without loop and selecting a distinct values, just use GroupBy method and map each group to StackedColumnChart using Select
var refactoredResults = VehicleSales
.GroupBy(s => s.Category)
.Select(g => new StackedColumnChart
{
Category = g.Key,
Values = g.Select(s => s.Value).ToList()
})
.ToList();
If the original data is not sorted and you'll need to sort the values by week number, you can use OrderBy clause before selecting a values Values = g.OrderBy(s => s.WeekNumber).Select(s => s.Value).ToList()

Keep distinct value in List depending on condition

I have a list where I'm applying the following condition with linQ:
I want to select all items where Name contains a certain string.
var nameFilter = result
.Where(e => e.Name.Contains(requestedValue))
.ToList();
At the end, sometimes it happens that I am having a list with repeated names:
For example:
requestedValue = 'form';
I end up with:
Name Price
transformer 100
transformer 20
formation 340
former 201
I got transformer twice. In that case, I want to only leave transformer with the least price : 20
How could I do this with linQ without looping?
You can take advantage of GroupBy method
var nameFilter = result.Where(e => e.Name.Contains(requestedValue))
.GroupBy(k=>k.Name, g=>g.Price, (k,g)=>new Model {Name = k, Price = g.Min()})
.ToList();
where new Model should be changed to your class name.
If you have more properties to return probably it will be more convenient to do
var nameFilter = result.Where(e => e.Name.Contains(requestedValue))
.GroupBy(k => k.Name, g => g, (k, g) =>
{
var minPrice = g.Min(x => x.Price);
return g.First(x => x.Price == minPrice);
}).ToList();
Finding minPrice and finding the item with minPrice can be done is a single for loop or, for example, by using following discussion here

find the original key of groupby in linq C#

var aaa = data.GroupBy(o => o.Date).Select(o => new { o }).ToList();
var bbb = aaa.Select(o => o.Key).ToList();
//There is a error of `Key`
Does that mean the Key is only allowed for the original List after GroupBy. Is it possible to obtain the Key for any Select after GroupBy?(Surely, we can storge the Key = o.Key in the Select )
Furthermore,
var aaa = data.GroupBy(o => o.Date).Select(o => o.ToList()}).ToList();
If we change aaa into two dimensional List, Is it possible to obtain the previous Key?
In your first linq expression, in the Select you are wrapping the IGrouping object that you got from the GroupBy with a new anonymous object.
So to get that Key property in your second line you should:
//Original:
var bbb = aaa.Select(o => o.Key).ToList();
//Change to:
var bbb = aaa.Select(o => o.o.Key).ToList();
For second question, if you want to get the Key in this case:
//Original:
var aaa = data.GroupBy(o => o.Date)
.Select(o => o.ToList()})
.ToList();
//Then you should:
var aaa = data.GroupBy(o => o.Date)
.Select(o => o.ToList()})
.Select(x => o.FirstOrDefault().Date)
.ToList();
Reason being is that:
You group your items by Date
First select you convert a IGrouping into a List<YourClass> but now you have an IEnumerable<List<YourClass>> where each record in the IEnumerable, all the inner items will have the same date
In second Select - take whichever item in the inner collections - and get the Date it is the same as getting the Key in the example before
To achieve what you actually what to get (grouping by the date and getting for each group the symbols):
var result = data.GroupBy(item => item.Date)
.Select(group => new { group.Key, Symbols = group.Select(item => item.Symbol).ToList() });
//Or using a different overload of the `GroupBy`:
var result = data.GroupBy(item => item.Date,
(key,group) => return new { Key = key, Symbols = group.Select(item => item.Symbol).ToList() });
Problem is your incorrect syntax. You crated an anonymous object, so you need to access the property with instance name. So you should be doing this.
var bbb = aaa.Select(o => o.o.Key).ToList();
if we change aaa into two dimensional List, Is it possible to obtain
the previous Key?
No, because you have groped values collection not the Key. So result will not contain Key.

How do i sum a list of items by code(or any field)?

I have an object that has a list of another object in it. i.e Object1 contains List<Object2>.
Assuming this is the definition of object 2:
public class Object2
{
string code,
string name,
decimal amount
}
I want to be a able to make a list2 from the list whose value will contain what something similar to what a select name, code, sum(amount) group by code kinda statement could have given me
this is what i did but it didnt contain what i needed on passing through.
var newlist = obj2List.GroupBy(x => x.code)
.Select(g => new { Amount = g.Sum(x => x.amount) });
I want code and name in the new list just like the sql statement above.
You're almost there:
var newlist = obj2List.GroupBy(x => x.code)
.Select(g => new
{
Code = g.First().code,
Name = g.First().name,
Amount = g.Sum(x => x.amount)
});
This groups the items by code and creates an anonymous object for each group, taking the code and name of first item of the group. (I assume that all items with the same code also have the same name.)
If you are grouping by code and not by name you'd have to choose something for name from the list, perhaps with First() or Last() or something.
var newlist = obj2List.GroupBy(x => x.code).Select(g => new {
Code = g.Key,
Name = g.First().name,
Amount = g.Sum(x => x.amount)
});
var query = Object1.Obj2List
.GroupBy(obj2 => obj2.code)
.Select(g => new {
Names = string.Join(",", g.Select(obj2.name)),
Code = g.Key,
Amount = g.Sum(obj2 => obj2.Amount)
});
Since you group by code only you need to aggregate the name also in some way. I have used string.Join to create a string like "Name1,Name2,Name3" for each code-group.
Now you could consume the query for example with a foreach:
foreach(var x in query)
{
Console.WriteLine("Code: {0} Names: {1} Amount: {2}"
, x.Code, x.Names, x.Amount);
}
Instead of using the LINQ Extension Methods .GroupBy() and .Select() you could also use a pure LINQ statement which is way easier to read if you come from a SQL Background.
var ls = new List<Object2>();
var newLs = from obj in ls
group obj by obj.code into codeGroup
select new { code = codeGroup.Key, amount = codeGroup.Sum(s => s.amount) };

Categories

Resources