How to rank elements when it has duplicates - c#

I am trying to figure out a way to rank items in a list that has duplicated values.
For example:
QTECDE
RANK
40
1
30
2
24
3
18
4
4
5
4
5
3
6
But my code always skips a number when I have a duplicated rank. This what I get:
QTECDE
RANK
40
1
30
2
24
3
18
4
4
5
4
5
3
7 (7 insted of 6)
Here's my code:
var rankedList = oList.OrderByDescending(p => p.QTECDE)
.Select((p, i) => new { Order = 1 + i, lst = p })
.GroupBy(p => new { p.lst.QTECDE })
.SelectMany(g => g.Select(p => new
{
RANK = g.Min(x => x.Order),
NO_ART = p.lst.NO_ART,
QTECDE = p.lst.QTECDE,
LIB_INDEX_FR_SUP = p.lst.LIB_NIVEAU_SUP_FR,
LIB_IMAGE = p.LIB_IMAGE,
}));
Any solutions?

You just need the index of the group not the items:
var rankedList = oList
.OrderByDescending(p => p.QTECDE)
.GroupBy(p => new { p.QTECDE })
.SelectMany((g, groupIndex) => g
.Select(p => new
{
RANK = groupIndex + 1,
NO_ART = p.NO_ART,
QTECDE = p.QTECDE,
LIB_INDEX_FR_SUP = p.LIB_NIVEAU_SUP_FR,
LIB_IMAGE = p.LIB_IMAGE,
}));

You're determining the rank/order on your source items. You want to apply the (item, index) to your SelectMany() instead of your Select().

Related

How to get dictinct userid with order by close using Linq

I have table called info, and its data something like this:
FKUserID
CompletedTime
Type
1
2021-03-10 12:56:00.423
5
245
2021-03-10 12:46:46.977
5
1
2021-03-10 12:44:53.683
5
1
2021-03-10 12:40:54.733
5
1
2021-03-10 12:35:26.307
5
245
2021-03-10 11:11:33.887
5
245
2021-03-10 10:18:11.403
5
I need to get distinct userID data, and also with the maximum completed time of theirs CompletedTime column
expected output is:
FKUserID
CompletedTime
Type
1
2021-03-10 12:56:00.423
5
245
2021-03-10 12:46:46.977
5
I need to do this using Linq query
How can I do this, I did it using SQl, need it using Linq
SELECT FKUserID , MAX(CompletedTime)
from Info
where cast(CompletedTime as date) = '2021-03-10'
and Status = 5
GROUP BY FKUserID;
You can use the following query. The following query implements your SQL query.
var query = context.Info.GroupBy(a => a.FKUserID)
.Join(context.Info,
left => left.Key,
right => right.FKUserID,
(left, right) => new { left, right })
.Where(a => a.right.CompletedTime.ToShortDateString() == "2021-03-10" && a.right.Status == 5)
.Select(a => new
{
FKUserID = a.left.Key,
CompletedTime = a.left.Max(x => x.CompletedTime)
}).ToList();
Something like this
var d = new DateTime(2021, 3, 10);
var d2 = d.AddDays(1);
dbContext.Infos
.Where(i => i.Status == 5 && i.CompletedTime >= d && i.CompletedTime < d2)
.GroupBy(i => i.FkUserId)
.Select(g => new {
FkUserId = g.Key,
CompletedTime = g.Max(g2 => g2.CompletedTime)
}
);

To Sum Value using linq

In the below code i have a list i am trying to get values from list using linq query and sum the values.But i don't know how to sum the values.So please help me to resolve the issue.
list contains:
Desc Month level value
M1 Jan L1 2
M1 Jan L2 6
M2 Feb L1 4
M2 Feb L2 1
My Expected Result:
M1 Jan 8
M2 Feb 5
var sums1 = objList
.GroupBy(y => new { y.Desc, y.Month, y.value })
.Select(group => new { KeyValue = group.Key, Count = group.Count() });
You shouldn't be including the value in your grouping - you want to group by just Desc and Month (I assume), then sum the value parts. Two options:
var sums1 = objList
.GroupBy(y => new { y.Desc, y.Month })
.Select(group => new { KeyValue = group.Key, Sum = group.Sum(y => y.value) });
Or in a single call:
var sums1 = objList.GroupBy(
y => new { y.Desc, y.Month },
(key, values) => new { key.Desc, key.Month, Sum = values.Sum(y => y.value) });
Replace your group.Count with group.Sum(x=>x.value)
Use LINQ's Sum() extension method for IEnumerable.
.Select(group => new { KeyValue = group.Key, Sum = group.Sum(obj => obj.value)});

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

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

LINQ group by sequence and count with sorting

I am searching a best performance method to group and count sequences with sorting using LINQ. I will be processing files even bigger than 500 MBs so performance is most important key in that task.
List<int[]> num2 = new List<int[]>();
num2.Add(new int[] { 35, 44 });
num2.Add(new int[] { 200, 22 });
num2.Add(new int[] { 35, 33 });
num2.Add(new int[] { 35, 44 });
num2.Add(new int[] { 3967, 11 });
num2.Add(new int[] { 200, 22 });
num2.Add(new int[] { 200, 2 });
The result have to be like this:
[35, 44] => 2
[200, 22] => 2
[35, 33] => 1
[35, 44] => 1
[3967, 11] => 1
[200, 2 ] => 1
I have done something like this:
Dictionary<int[], int> result2 = (from i in num2
group i by i into g
orderby g.Count() descending
select new { Key = g.Key, Freq = g.Count() })
.ToDictionary(x => x.Key, x => x.Freq);
SetRichTextBox("\n\n Second grouping\n");
foreach (var i in result2)
{
SetRichTextBox("\nKey: ");
foreach (var r in i.Key)
{
SetRichTextBox(r.ToString() + " ");
}
SetRichTextBox("\n Value: " + i.Value.ToString());
}
But it is not working properly. Any help?
For arrays of length 2, this will work.
num2.GroupBy(a => a[0])
.Select(g => new { A0 = g.Key, A1 = g.GroupBy(a => a[1]) })
.SelectMany(a => a.A1.Select(a1 => new { Pair = new int[] { a.A0, a1.Key }, Count = a1.Count() }));
I think that should give you optimal performance; you could also try an .AsParallel() clause after your first Select statement.
This strategy (grouping successively by the n-th element of the arrays) generalises to arrays of arbitrary length:
var dim = 2;
var tuples = num2.GroupBy(a => a[0])
.Select(g => new Tuple<int[], List<int[]>>(new [] { g.Count(), g.Key }, g.Select(a => a.Skip(1).ToArray()).ToList()));
for (int n = 1; n < dim; n++)
{
tuples = tuples.SelectMany(t => t.Item2.GroupBy(list => list[0])
.Select(g => new Tuple<int[], List<int[]>>(new[] { g.Count() }.Concat(t.Item1.Skip(1)).Concat(new [] { g.Key }).ToArray(), g.Select(a => a.Skip(1).ToArray()).ToList())));
}
var output = tuples.Select(t => new { Arr = string.Join(",", t.Item1.Skip(1)), Count = t.Item1[0] })
.OrderByDescending(o => o.Count)
.ToList();
which generates an output of
Arr = "35, 44", Count = 2
Arr = "200, 22", Count = 2
Arr = "35, 33", Count = 1
Arr = "200, 2", Count = 1
Arr = "3967, 11", Count = 1
in your example. I'll let you test it for higher dimensions. :)
You should be able to parallelise these queries without too much difficulties, as the successive groupings are independent.
You can do something like this:
var results = from x in nums
group x by new { a = x[0], b = x[1] } into g
orderby g.Count() descending
select new
{
Key = g.Key,
Count = g.Count()
};
foreach (var result in results)
Console.WriteLine(String.Format("[{0},{1}]=>{2}", result.Key.a, result.Key.b, result.Count));
The trick is to come up with a way to compare the values in the array, instead of the arrays themselves.
The alternative (and possibly better option) would be to transform your data from int[] to some custom type, override the equality operator on that custom type, then just group x by x into g, but if you're really stuck with int[] then this works.

Categories

Resources