Count and Max Columns Group By in LINQ - c#

I have model
public class Rate
{
public int Nr{ get; set; }
public string Rate{ get; set; }
public int Order{ get; set; }
}
and a RateList = List<Rate> like this
Nr | Rate | Order
123 | A | 2
425 | A+ | 1
454 | B | 4
656 | B+ | 3
465 | A | 2
765 | B | 4
Notice that Order always match the Rate (A+ = 1, A = 2, B+ = 3, B = 4, C+ = 5 ...)
I want to count how many time the Rate occoured and display order by the Order
The result should look like this
Rate | Count | Order
A+ | 1 | 1
A | 2 | 2
B+ | 1 | 3
B | 2 | 4
or without column Order
Rate | Count
A+ | 1
A | 2
B+ | 1
B | 2
In SQL I could do like this if I had above list in table Tab
SELECT Rate, COUNT(Rate), Max(Order) from Tab group by Rate
but in LINQ?
I was trying something like this
var rating= RateList.Distinct().GroupBy(x => x.Rate)
.Select(x => new { Rate = x.Key, RateCount = x.Count() })
.OrderBy(x => x.Order);
but didnt work.
Thank You for help.

Your SQL query is equevalent to:
var rating = rateList.GroupBy(x => x.Rate)
.Select(x => new {
Rate = x.Key,
RateCount = x.Count(e => e != null),
Max = x.Max(g => g.Order)
});

Related

LINQ expression with mulitple count

I will appreciate help on writing the following SQL query with multiple count into a single LINQ expression
select count(MemoNo) as totalOrder,
count(distinct shopId) as shops,
sum(TotalAmount) as totalAmount
from Sales;
Sample data
| MemoNo | shopId | TotalAmount |
|-------------------------------|
| a10| s2| 200|
| a11| s2| 220|
| a12| s3| 100|
| a13| s3| 20|
| a14| s3| 100|
| a15| s4| 50|
| a16| s4| 20|
| a17| s4| 90|
Sample output should look like this
| totalOrder | shops | totalAmount |
|------------|-------|-------------|
| 8 | 3 | 800|
Try this way
var totalOrder = Sales.Select(p => p.MemoNo).Count();
var shop = Sales.Select(p => p.shopId).Distinct().Count();
var totalAmount = Sales.Sum(p => p.TotalAmount);
Updated
Demo on dotnet fiddle
var tempData = Sales.GroupBy(p => p.shopId).Select(g => new
{
shops = g.Count(),
totalAmount = g.Sum(p => p.TotalAmount),
totalOrder = g.Count()
});
var result = new
{
totalOrder = tempData.Sum(p => p.totalOrder),
shops = tempData.Count(),
totalAmount = tempData.Sum(p => p.totalAmount)
};
// Output: { totalOrder = 8, shops = 3, totalAmount = 800 }

Finding multiple unique matches from List<object> where two criteria have to be different

I am having trouble selecting the first item in a list that is unique based on two fields, JOB_ID and EMPLOYEE_ID.
Each job should only be assigned to one employee (the one with the lowest OVERALL_SCORE), then move on and assign the next employee.
The List Objects are as follows:
JobMatch.cs
public int JOB_ID { get; set; }
public int JOB_MATCHES_COUNT { get; set; }
EmployeeMatch.cs
public int EMPLOYEE_ID { get; set; }
public int EMPLOYEE_MATCHES_COUNT { get; set; }
Rankings.cs
public int JOB_ID { get; set; }
public int EMPLOYEE_ID { get; set; }
public int TRAVEL_TIME_MINUTES { get; set; }
public int PRIORITY { get; set; }
public int OVERALL_SCORE { get; set; }
Rankings.cs gets an overall score based on the travel time field and
number of matches an Employee/Job has.
EmployeeMatch.cs
+-------------+-------------------+
| EMPLOYEE_ID | EMP_MATCHES_COUNT |
+-------------+-------------------+
| 3 | 1 |
| 4 | 1 |
| 2 | 3 |
| 1 | 4 |
+-------------+-------------------+
JobMatch.cs
+--------+-------------------+
| JOB_ID | JOB_MATCHES_COUNT |
+--------+-------------------+
| 1 | 1 |
| 2 | 2 |
| 3 | 2 |
| 4 | 4 |
+--------+-------------------+
Ranking.cs (shortened as to not fill the screen)
+--------+-------------+---------------+
| JOB_ID | EMPLOYEE_ID | OVERALL_SCORE |
+--------+-------------+---------------+
| 4 | 3 | 800 |
| 4 | 4 | 800 |
| 3 | 1 | 800 |
| 3 | 2 | 1200 |
| 2 | 1 | 1600 |
| 2 | 2 | 1800 |
| 4 | 1 | 2000 |
| 4 | 2 | 2100 |
| 1 | 1 | 6400 |
+--------+-------------+---------------+
Basically, the idea is to select the first unique Employee and Job in this list and then the best matches will be put into a separate list, something like the following for the above scenario:
+--------+-------------+---------------+
| JOB_ID | EMPLOYEE_ID | OVERALL_SCORE |
+--------+-------------+---------------+
| 4 | 3 | 800 |
| 3 | 1 | 800 |
| 2 | 2 | 1800 |
+--------+-------------+---------------+
I tried the following but it didn't work as intended:
var FirstOrder = (rankings.GroupBy(u => u.JOB_ID)
.Select(g => g.First())).ToList();
var SecondOrder = (FirstOrder.GroupBy(u => u.EMPLOYEE_ID)
.Select(g => g.First())).ToList();
The idea is choosing first element and then removing corresponding elements from list to make sure next choice is unique, as below:
var rankings = new List<Rankings> {
new Rankings{ JOB_ID= 4,EMPLOYEE_ID= 3, OVERALL_SCORE= 800 },
new Rankings{ JOB_ID= 4,EMPLOYEE_ID= 4, OVERALL_SCORE= 800 },
new Rankings{ JOB_ID= 3,EMPLOYEE_ID= 1, OVERALL_SCORE= 800 },
new Rankings{ JOB_ID= 3,EMPLOYEE_ID= 2, OVERALL_SCORE= 1200 },
new Rankings{ JOB_ID= 2,EMPLOYEE_ID= 1, OVERALL_SCORE= 1600 },
new Rankings{ JOB_ID= 2,EMPLOYEE_ID= 2, OVERALL_SCORE= 1800 },
new Rankings{ JOB_ID= 4,EMPLOYEE_ID= 1, OVERALL_SCORE= 2000 },
new Rankings{ JOB_ID= 4,EMPLOYEE_ID= 2, OVERALL_SCORE= 2100 },
new Rankings{ JOB_ID= 1,EMPLOYEE_ID= 1, OVERALL_SCORE= 6400 },
};
var cpy = new List<Rankings>(rankings);
var result = new List<Rankings>();
while (cpy.Count() > 0)
{
var first = cpy.First();
result.Add(first);
cpy.RemoveAll(r => r.EMPLOYEE_ID == first.EMPLOYEE_ID || r.JOB_ID == first.JOB_ID);
}
result:
+--------+-------------+---------------+
| JOB_ID | EMPLOYEE_ID | OVERALL_SCORE |
+--------+-------------+---------------+
| 4 | 3 | 800 |
| 3 | 1 | 800 |
| 2 | 2 | 1800 |
+--------+-------------+---------------+
Really, if you're trying to get the best score for the job, you don't need to select by unique JOB_ID/EMPLOYEE_ID, you need to sort by JOB_ID/OVERALL_SCORE, and pick out the first matching employee per JOB_ID (that's not already in the "assigned list").
You could get the items in order using LINQ:
var sorted = new List<Ranking>
(
rankings
.OrderBy( r => r.JOB_ID )
.ThenBy( r => r.OVERALL_SCORE )
);
...and then peel off the employees you want...
var best = new List<Ranking>( );
sorted.ForEach( r1 =>
{
if ( !best.Any
(
r2 =>
r1.JOB_ID == r2.JOB_ID
||
r1.EMPLOYEE_ID == r2.EMPLOYEE_ID
) )
{
best.Add( r1 );
}
} );
Instead of using Linq to produce a sorted list, you could implement IComparable<Ranking> on Ranking and then just sort your rankings:
public class Ranking : IComparable<Ranking>
{
int IComparable<Ranking>.CompareTo( Ranking other )
{
var jobFirst = this.JOB_ID.CompareTo( other.JOB_ID );
return
jobFirst == 0?
this.OVERALL_SCORE.CompareTo( other.OVERALL_SCORE ):
jobFirst;
}
//--> other stuff...
}
Then, when you Sort() the Rankings, they'll be in JOB_ID/OVERALL_SCORE order. Implementing IComparable<Ranking> is probably faster and uses less memory.
Note that you have issues...maybe an unstated objective. Is it more important to fill the most jobs...or is more important to find work for the most employees? The route I took does what you suggest, and just take the best employee for the job as you go...but, maybe, the only employee for job 2 may be the same as the best employee for job 1...and if you put him/her on job 1, you might not have anybody left for job 2. It could get complicated :-)
Basically you could use System.Linq.Distinct method reinforced with the custom equality comparer IEqualityComparer<Ranking>. The System.Linq provide this method out of the box.
public class Comparer : IEqualityComparer<Ranking>
{
public bool Equals(Ranking l, Ranking r)
{
return l.JOB_ID == r.JOB_ID || l.EMPLOYEE_ID == r.EMPLOYEE_ID;
}
public int GetHashCode(Ranking obj)
{
return 1;
}
}
The trick here is with the GetHashCode method, and then as simple as this
rankings.Distinct(new Comparer())

Select all columns but group by only one in linq

I have been looking for a way to get multiple columns but group by only one in SQL and I found some info. However I can not came up with a way to do it in linq.
I have the following toy example table:
| Id | Message | GroupId | Date |
|-------------------------------|
| 1 | Hello | 1 | 1:00 |
| 2 | Hello | 1 | 1:01 |
| 3 | Hey | 2 | 2:00 |
| 4 | Dude | 3 | 3:00 |
| 5 | Dude | 3 | 3:01 |
And I would like to recover all columns for the rows that have a distinct GroupId as follows (with a 'Date' desc order):
| Id | Message | GroupId | Date |
|-------------------------------|
| 1 | Hello | 1 | 1:00 |
| 3 | Hey | 2 | 2:00 |
| 4 | Dude | 3 | 3:00 |
I do not really care about which row is picked from the grouped ones (first, second...) as long as is the only one given that group Id.
I have came out with the following code so far but it does not do what is supposed to:
List<XXX> messages = <MyRep>.Get(<MyWhere>)
.GroupBy(x => x.GroupId)
.Select(grp => grp.OrderBy(x => x.Date))
.OrderBy(y => y.First().Date)
.SelectMany(y => y).ToList();
This will give you one item per group:
List<dynamic> data = new List<dynamic>
{
new {ID = 1, Message = "Hello", GroupId = 1, Date = DateTime.Now},
new {ID = 2, Message = "Hello", GroupId = 1, Date = DateTime.Now},
new {ID = 3, Message = "Hey", GroupId = 2, Date = DateTime.Now},
new {ID = 4, Message = "Dude", GroupId = 3, Date = DateTime.Now},
new {ID = 5, Message = "Dude", GroupId = 3, Date = DateTime.Now},
};
var result = data.GroupBy(item => item.GroupId)
.Select(grouping => grouping.FirstOrDefault())
.OrderByDescending(item => item.Date)
.ToList();
//Or you can also do like this:
var result = data.GroupBy(item => item.GroupId)
.SelectMany(grouping => grouping.Take(1))
.OrderByDescending(item => item.Date)
.ToList();
If you want to control OrderBy then:
var result = data.GroupBy(item => item.GroupId)
.SelectMany(grouping => grouping.OrderBy(item => item.Date).Take(1))
.OrderByDescending(item => item.Date)
.ToList();

How to add List<T> containing item of type List<string> ,int,string into another List

I have a list with complex data
public class CAR
{
public int ID {get ; set ; }
public string Name { get ; set ; }
public string EngineType { get ; set ; }
public List<string> Months { get; set; }
}
Note that Months data is List<string> its max count is 150
List<CAR> A = new List<CAR>();
List<CAR> B = new List<CAR>();
A has follwoing data
ID | Name | EngineType | Months[0] | Months[1] | Months[2] | Months[3] .. | Months[149] |
1 | Zen | 1001 | 1 | 1 | 4 | 5 .. | 6 |
2 | Benz | 2002 | 6 | 4 | 5 | 6 .. | 2 |
3 | Zen | 1001 | 3 | 1 | 7 | 5 .. | 0 |
4 | Zen | 1001 | 2 | 2 | 4 | 5 .. | 6 |
5 | Zen | 2002 | 2 | 2 | 4 | 5 .. | 6 |
6 | Benz | 2002 | 1 | 1 | 1 | 1 .. | 1 |
IF EngineType and Name are same we add those rows and store the result in a single row
Eg : adding rows
row 1 in B = 1 + 3 + 4
row 2 in B = 2 + 6
row 3 in B = 5
B should contain the following op
ID | Name | EngineType | Months[0] | Months[1] | Months[2] | Months[3] ... | Months[149] |
- | Zen | Petrol | 6 | 4 | 15 | 15 .. | 12 |
- | Benz | Diesel | 7 | 5 | 6 | 7 | 3 |
- | Zen | Diesel | 2 | 2 | 4 | 5 .. | 6 |
had months data been separate entity of type integer something else i could have done this
B = from val in A
group val by new val.EngineType into g
select new CAR{
EngineType = g.Key,
Name = g.Name,
Month0 = g.Sum(p => p.Month0),
Month1 = g.Sum(p => p.Month1),
Month2 = g.Sum(p => p.Month2),
.
.
.
.
.
.
Month148 = g.Sum(p => p.Month148),
Month149 = g.Sum(p => p.Month149)
}.ToList<CAR>();
But since its of type List<string> is there a way to get this done?
Thanks a lot!
Use the power of LINQ:
var B = A.GroupBy(x => new { x.Name, x.EngineType })
.Select(g => new Car
{
Name = g.Key.Name,
EngineType = g.Key.EngineType,
Months = g.SelectMany(x => x.Months.Select((y,i) => new { i, y = int.Parse(y) }))
.GroupBy(x => x.i)
.OrderBy(g2 => g2.Key)
.Select(g2 => g2.Sum(x => x.y).ToString()).ToList()
}).ToList();
foreach (CAR c in A)
{
bool blnadded = false;
if (B.Count == 0)
{
B.Add(c);
blnadded = true;
}
else
foreach (CAR d in B)
{
if (d.Name == c.Name && d.EngineType == c.EngineType)
{
for (int i = 0; i < d.Months.Count; i++)
d.Months[i] = (Convert.ToInt32(d.Months[i]) + Convert.ToInt32(c.Months[i])).ToString();
blnadded = true;
}
}
if (blnadded==false)
B.Add(c);
}

linq group by and order by on some list?

I have a list(avgEnergyObj) like
Timestamp | MeterID | Energy
----------------------------
190990001 | 1 | 98090.0
190990003 | 2 | 98909.3
190990002 | 2 | 99000.3
190990004 | 1 | 99900.9
i want to sort it by time stamp and group by meterID like -
Timestamp | MeterID | Energy
----------------------------
190990001 | 1 | 98090.0
190990003 | 2 | 99000.3
190990002 | 1 | 98909.3
190990004 | 2 | 99900.9
i have written something (not working) some error -
List<FetchingEnergy> avgEnergyObj2 =
avgEnergyObj.GroupBy(p => p.MeterId)
.Select(group =>
new {
meterID = group.Key,
FetchingEnergy = group.OrderBy(x => x.TimeStamp)
})
.OrderBy(group => group.FetchingEnergy.First().TimeStamp);
var sortedList = avgEnergyObj
.OrderBy(x => x.MeterId)
.ThenBy(x => x.TimeStamp)
.ToList();

Categories

Resources