ranking in linq based on group by condition? - c#

I need to rank on list by Linq.
class test
{
public int Id { get; set; }
public string Destination { get; set; }
}
my data is as follows:
ID Destination
1 Miami
2 Miami
3 Boston
4 Atlanta
what i want is this:
ID Destination Value
1 Miami Miami1
2 Miami Miami2
3 Boston Boston1
4 Atlanta Atlanta1
How to get this by Linq??

Try this:
list.GroupBy(l => l.Destination)
.SelectMany(g => g.Select((x,i) => new {
x.Id,
x.Destination,
Value = x.Destination + (i+1)
}));
The i in the SelectMany will give you the index of the item in each group. Just add 1 to get the rank.
Results:
Id destination Value
1 Miami Miami1
2 Miami Miami2
3 Boston Boston1
4 Atlanta Atlanta1

Here's how you could compute the ranks within groups:
var result =
items.GroupBy(d => d.destination)
.SelectMany(g =>
g.Select(d =>
new
{
d.Id,
d.destination,
Value = d.destination +
(g.Count(x => x.Id < d.Id) + 1)
}));

var result = list.GroupBy(x=>x.Destination)
.Select(g => g.Select((x,i) =>
new {
ID = x.ID,
Destination = x.Destination,
Value = x.Destination + (i + 1)
}))
.SelectMany(x=>x);

Related

LINQ Group by and count concatenated output

With this query:
foreach(var y in items)
{
<div>#y.fieldA => #y.FieldB</div>
}
I get this output:
3 => 2
3 => 1
2 => 2
3 => 2
I know in LINQ we can easily count and group by with this:
foreach(var y in items.GroupBy(g => g.Field)
.Select(group => new {Field = group.Key, count = group.Count()}))
{
<div>#y.Field : #y.count</div>
}
But how can I adapt this to the concatenated output so that I can get this result:
3 => 2 : 2
3 => 1 : 1
2 => 2 : 1
Use Select to project the desired string instead of projecting an anonymous object:
foreach(var item in items.GroupBy(g => $"{g.FieldA} => {g.FieldB}")
.Select(g => $"{g.Key} : {g.Count()}"))
{
<div>#item</div>
}
For a pre C# 6.0 version:
foreach(var item in items.GroupBy(g => g.FieldA + " => " + g.FieldB)
.Select(g => g.Key + " : " + g.Count()))
{
<div>#item</div>
}

LINQ multiple group by and then getting the first group by value count

I have a linq query like followin:
var _transactionsList = TransactionsData
.GroupBy(x => new { x.ItemID, x.Title, x.GalleryURL })
.Select(pr => new TransactionsTabResults
{
ItemID = pr.Key.ItemID,
Title = pr.Key.Title,
GalleryURL = pr.Key.GalleryURL,
ItemPrice = pr.OrderByDescending(a => a.TransactionDate).First().ItemPrice,
TotalSoldItems = pr.Count(),
TotalRevenuePerItem = pr.Sum(y => y.ItemPrice),
AveragePrice = pr.Average(y => y.ItemPrice),
}).ToList();
I'm trying to fetch the total sold items value by grouping it by like this:
ItemID Sales ItemName
1 1 Item1
1 3 Item1
1 5 Item1
1 6 Item1
2 2 Item2
2 2 Item2
2 2 Item2
2 2 Item2
The desired output would be:
ItemID Sales ItemName
1 15 Item1
2 8 Item2
The query above that I wrote gives me wrong values for total sales by saying:
TotalSoldItems = pr.Count(),
How can I count, or sum all the sales of one Item which has unique ID(this is what I'm grouping by)...
What am I doing wrong??
You are using GroubBy wrong way. You create new unique object every time. So your .GroupBy(x => new { x.ItemID, x.Title, x.GalleryURL }) and .Select(x => new { Key = new { x.ItemID, x.Title, x.GalleryURL}, Value =x }) means the same
If you need unique Id then group by Id only
TransactionsData
.GroupBy(x => x.ItemID)
.Select(pr => new TransactionsTabResults
{
ItemID = pr.Key,
Title = pr.First().Title,
GalleryURL = pr.First().GalleryURL,
ItemPrice = pr.OrderByDescending(a => a.TransactionDate).First().ItemPrice,
TotalSoldItems = pr.Count(),
TotalRevenuePerItem = pr.Sum(y => y.ItemPrice),
AveragePrice = pr.Average(y => y.ItemPrice),
}).ToList();
Advice
Optimize your LINQ. You are iterating through collections many times. This is suggested code:
TransactionsData
.GroupBy(x => x.ItemID)
.Select(pr =>
{
var items = x.pr.ToArray;
var sum = items.Sum(y => y.ItemPrice);
return new TransactionsTabResults
{
ItemID = pr.Key,
Title = items[0].Title,
GalleryURL = items[0].GalleryURL,
ItemPrice = pr.Aggregate((max, cur)=>max.TransactionDate<cur.TransactionDate?cur:max).ItemPrice,
TotalSoldItems = items.Length,
TotalRevenuePerItem = sum,
AveragePrice = sum/items.Length,
};
}).ToList();

select distinct name and combine an objects property in a List<T>

I have have a List below, and I am trying to combine the T.name property and sum up the T.score property
Data
Name | score
-------------
Jon | 50
Jon | 100
Ash | 100
Ash | 75
Desired result is
Jon | 150
Ash | 175
I am able to do this in LINQ and create a var with the desired results
List<PieSeriesData> PiSeriesData = new List<PieSeriesData>();
var x = PiSeriesData.GroupBy(i => i.Name)
.Select(g => new { Id = g.Key, Total = g.Sum(i => i.Score) })
.ToList();
The issue with that is I need x to be a List type, which is what the HighCharts nuget requires.
As I understand from the chat you need the output to be a List<PieSeriesData>. You need to project a new object of PieSeriesData class in the select and not of an anonymous type: _See that you also need to assign the properties that exist and not use new ones like Id and Total:
List<PieSeriesData> PiSeriesData = new List<PieSeriesData>();
var x = PiSeriesData.GroupBy(i => i.Name)
.Select(g => new PieSeriesData {
Name = g.Key,
Score = g.Sum(i => i.Score)
}).ToList();
// Now x is of type: List<PieSeriesData>
Why not create new class
public class PieSeriesRenderData
{
public string ID { get; set; }
public int Total { get; set; }
}
And after that return this:
List<PieSeriesData> piSeriesData = new List<PieSeriesData>();
var x = piSeriesData.GroupBy(i => i.Name)
.Select(g => new PieSeriesRenderData { Id = g.Key, Total = g.Sum(i => i.Score) })
.ToList();
Now you will have object of List<PieSeriesRenderData> which you can return it as Json from your web service and you will render the highchart in your ajax.successful method.

c# Linq count group by

My getTrustActivitiesFromStorage List looks something this
venueId venueName activityId
1 Location1 Zumba
2 Location2 Yoga
1 Location1 Yoga
1 Location1 MetaFit
3 Location3 Zumba
Here's the code i use to group etc
List<TrustActivities> filteredVenues = new List<TrustActivities>();
IEnumerable<TrustActivities> groupedVenueCollection = getTrustActivitiesFromStorage
.GroupBy(customer => customer.venueName)
.Select(group => group.First())
.OrderBy(x => x.venueName);
// Loop
foreach (TrustActivities activity in groupedVenueCollection)
{
filteredVenues.Add(new TrustActivities
{
filterId = Convert.ToInt32(activity.venueId),
filterName = activity.venueName,
filterCount = 55
});
}
This successfully groups the list and outputs the 3 matches:
1 Location1 (55)
2 Location2 (55)
3 Location3 (55)
The final bit i need help with is counting each group, so filterCount = 55 will be replace with the dynamic count to give:
1 Location1 (3)
2 Location2 (1)
3 Location3 (1)
can someone show me how to do this?
thanks
You just need group.Count():
var groupedVenueCollection = getTrustActivitiesFromStorage
.GroupBy(customer => customer.venueName)
.OrderBy(g => g.Key);
foreach (var group in groupedVenueCollection)
{
TrustActivities firstActivity = group.First();
filteredVenues.Add(new TrustActivities
{
filterId = Convert.ToInt32(firstActivity.venueId),
filterName = firstActivity.venueName, // or group.Key
filterCount = group.Count() // <--- !!!
});
}
You could also do it in one query without a loop:
List<TrustActivities> filteredVenues = getTrustActivitiesFromStorage
.GroupBy(customer => customer.venueName)
.OrderBy(g => g.Key)
.Select(g => new { Activity = g.First(), Count = g.Count() })
.Select(x => new TrustActivities
{
filterId = Convert.ToInt32(x.Activity.venueId),
filterName = x.Activity.venueName,
filterCount = x.Count
})
.ToList();
Instead of the .Select(g => g.First()), you'd do something like this:
IEnumerable<TrustActivities> groupedVenueCollection = getTrustActivitiesFromStorage
.GroupBy(customer => new { customer.venueId, customer.venueName });
foreach (var activity in groupedVenueCollection)
{
filteredVenues.Add(new TrustActivities
{
filterId = Convert.ToInt32(activity.Key.venueId),
filterName = activity.Key.venueName,
filterCount = activity.Count()
});
}
Also, your variable names are confusing. The table appears to be venues, but you call them customers and activities

how to get an ordered list with default values using linq

I have an ICollection of records (userID,itemID,rating) and an IEnumerable items
for a specific userID and each itemID from a set of itemIDs, i need to produce a list of the users rating for the items or 0 if no such record exists. the list should be ordered by the items.
example:
records = [(1,1,2),(1,2,3),(2,3,1)]
items = [3,1]
userID = 1
result = [0,2]
my attempt:
dataset.Where((x) => (x.userID == uID) & items.Contains(x.iID)).Select((x) => x.rating);
it does the job but it doesn't return 0 as default value and it isnt ordered...
i'm new to C# and LINQ, a pointer in the correct direction will be very appreciated.
Thank you.
This does the job:
var records = new int[][] { new int[] { 1, 1, 2 }, new int[] { 1, 2, 3 }, new int[] { 2, 3, 1 } };
var items = new int[] { 3, 1 };
var userId = 1;
var result = items.Select(i =>
{
// When there's a match
if (records.Any(r => r[0] == userId && r[1] == i))
{
// Return all numbers
return records.Where(r => r[0] == userId && r[1] == i).Select(r => r[2]);
}
else
{
// Just return 0
return new int[] { 0 };
}
}).SelectMany(r => r); // flatten the int[][] to int[]
// output
result.ToList().ForEach(i => Console.Write("{0} ", i));
Console.ReadKey(true);
How about:
dataset.Where((x) => (x.userID == uID)).Select((x) => items.Contains(x.iID) ? x.rating : 0)
This does the job. But whether it's maintainable/readable solution is topic for another discussion:
// using your example as pseudo-code input
var records = [(1,1,2),(1,2,3),(2,3,1)];
var items = [3,1];
var userID = 1;
var output = items
.OrderByDescending(i => i)
.GroupJoin(records,
i => i,
r => r.ItemId,
(i, r) => new { ItemId = i, Records = r})
.Select(g => g.Records.FirstOrDefault(r => r.UserId == userId))
.Select(r => r == null ? 0 : r.Rating);
How this query works...
ordering is obvious
the ugly GroupJoin - it joins every element from items with all records that share same ItemId into annonymous type {ItemId, Records}
now we select first record for each entry that matches userId - if none is found, null will be returned (thanks to FirstOrDefault)
last thing we do is check whether we have value (we select Rating) or not - 0
How about this. your question sounds bit like an outer join from SQL, and you can do this with a GroupJoin, SelectMany:
var record1 = new Record() { userID = 1, itemID = 1, rating = 2 };
var record2 = new Record() { userID = 1, itemID = 2, rating = 3 };
var record3 = new Record() { userID = 2, itemID = 3, rating = 1 };
var records = new List<Record> { record1, record2, record3 };
int userID = 1;
var items = new List<int> { 3, 1 };
var results = items
.GroupJoin( records.Where(r => r.userID == userID), item => item, record => record.itemID, (item, record) => new { item, ratings = record.Select(r => r.rating) } )
.OrderBy( itemRating => itemRating.item)
.SelectMany( itemRating => itemRating.ratings.DefaultIfEmpty(), (itemRating, rating) => rating);
To explain what is going on
For each item GroupJoin gets the list of rating (or empty list if no rating) for the specified user
OrderBy is obvious
SelectMany flattens the ratings lists, providing a zero if the ratings list is empty (by DefaultIfEmpty)
Hope this makes sense.
Be aware, if there is more than one rating for an item by a user, they will all appear in the final list.

Categories

Resources