Count occurrences of an event by date with C# - c#

I am facing an issue with counting the number of occurrences by date in C#. Should I use Linq to filter it? Please advise. Thank you.
Date
Player ID
1/1/2001
23
1/1/2001
29
1/1/2001
24
3/1/2001
22
3/1/2001
23
My preferred output should be
Date
No. Of Players
1/1/2001
3
2/1/2001
0
3/1/2001
2
This is my current code, how can I do it within the select:
var convertTable = dataPageTable.AsEnumerable();
Records = new List<List<ContentOutputModel>>(convertTable.Select(dr =>
{
var playerId = dr.GetColumn<long>("PlayerID").ToString();
var dateInt = dr.GetColumn<int>("Date").ToString();
var dateStr = dateInt.Substring(6, 2) + "/" + dateInt.Substring(4, 2) + "/" + dateInt.Substring(0, 4);
var output = new List<ContentOutputModel>(new ContentOutputModel[] {
new ContentOutputModel() { Text = dateStr },
new ContentOutputModel() { Text = playerId },
});
return output;
}));

Here's the cleanest that I could come up with:
List<Player> players = new List<Player>()
{
new Player() { Date = new DateTime(2021, 1, 1), ID = 23 },
new Player() { Date = new DateTime(2021, 1, 1), ID = 29 },
new Player() { Date = new DateTime(2021, 1, 1), ID = 24 },
new Player() { Date = new DateTime(2021, 1, 3), ID = 22 },
new Player() { Date = new DateTime(2021, 1, 3), ID = 23 }
};
var first = players.Min(p => p.Date);
var last = players.Max(p => p.Date);
var days = last.Subtract(first).Days + 1;
var lookup = players.ToLookup(p => p.Date);
var output =
from n in Enumerable.Range(0, days)
let Date = first.AddDays(n)
select new
{
Date,
Count = lookup[Date].Count(),
};
That gives me:

You can achieve by Group() via System.Linq.
Order players by Date and get startDate and endDate.
Generate an array with dates from startDate to endDate.
3.1 With group to count player(s) by Date.
3.2 Left join result from (2) with the result (3.1) to get Date and Count.
using System.Linq;
using System.Collections.Generic;
List<Player> players = new List<Player>
{
new Player{Date = new DateTime(2021, 1, 1), ID = 23},
new Player{Date = new DateTime(2021, 1, 1), ID = 29},
new Player{Date = new DateTime(2021, 1, 1), ID = 24},
new Player{Date = new DateTime(2021, 1, 3), ID = 22},
new Player{Date = new DateTime(2021, 1, 3), ID = 23}
};
var startDate = players.OrderBy(x => x.Date)
.First()
.Date;
var endDate = players.OrderBy(x => x.Date)
.Last()
.Date;
var dates = Enumerable.Range(0, 1 + endDate.Subtract(startDate).Days)
.Select(offset => startDate.AddDays(offset))
.ToArray();
var result = (from a in dates
join b in
(
from p in players
group p by p.Date into g
select new { Date = g.Key, Count = g.Count() }
) on a.Date equals b.Date into ab
from b in ab.DefaultIfEmpty()
select new { Date = a.Date, Count = b != null ? b.Count : 0 }
);
Sample program
Output
Date: 1/1/2021, Count: 3
Date: 2/1/2021, Count: 0
Date: 3/1/2021, Count: 2

You can use linq to do this as long as it can enumerate through a list or some other IEnumerable. Try this:
var playersSorted = yourlist.GroupBy(x => x.Date)
.Where(g => g.Any())
.Select(y => new {Date = y.Key, Count = y.Count()}).ToList();

var playersgroup = from e in players
group e by Date into g
select new { Date= g.Key, NoOfPlayers = g.Count() };

Related

LINQ reusing Select statement with anonymous grouping types

I am trying to find a way to cut out repeated code in an application that center around LINQ select statements. Lets say we have existing table rows that need to be aggregated and grouped for different reporting requirements and all original data is just grouped by day and needs to be grouped by week / month and another property.
DataRow is an example object that needs to be grouped and converted into an object ReportTableRow (please note this is a much reduced object but the actual objects have far more properties and therefore become much more drawn out).
public class DataRow
{
public DateTime Date { get; set; }
public string AccountNumber { get; set; }
public string MachineNumber { get; set; }
public int TEST { get; set; }
}
public class ReportTableRow
{
public int WeekNumber { get; set; }
public int Month { get; set; }
public string AccountNumber{ get; set; }
public string MachineNumber { get; set; }
public int TEST { get; set; }
public string TEST_TRAFFICLIGHT { get; set; }
}
And we create a list of DataRows:
List<DataRow> reportTable = new List<DataRow>()
{
new DataRow()
{
Date = new DateTime(2021, 06, 14),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 1
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 6
},
new DataRow()
{
Date = new DateTime(2021, 06, 16),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 4
},
new DataRow()
{
Date = new DateTime(2021, 06, 17),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 18),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 7
},
new DataRow()
{
Date = new DateTime(2021, 06, 19),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 20),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 11
},
new DataRow()
{
Date = new DateTime(2021, 06, 14),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 1
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 6
},
new DataRow()
{
Date = new DateTime(2021, 06, 16),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 4
},
new DataRow()
{
Date = new DateTime(2021, 06, 17),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 18),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 7
},
new DataRow()
{
Date = new DateTime(2021, 06, 19),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 20),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 11
}
};
So if we either need the data grouped "BY WEEK" or "BY MONTH" then we need to actually return in the report the WeekNumber or Month number respectively and the grouping would look something like this where GetTrafficLight method returns a string value based on the value of the sum of TEST:
switch (aggregate.ToUpper())
{
case "BY WEEK":
reportTable = reportTable
.GroupBy(x => new { x.AccountNumber, WeekNumber = CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(x.Date, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday) })
.Select(x => new ReportTableRow
{
WeekNumber = x.Key.WeekNumber,
Month = x.Max(y => y.Date).Month,
MachineNumber = x.FirstOrDefault().MachineNumber,
TEST = x.Sum(y => y.TEST),
TEST_TRAFFICLIGHT = GetTrafficLight(x.Sum(y => y.TEST)
})
.ToList();
break;
case "BY MONTH":
reportTable = reportTable
.GroupBy(x => new { x.AccountNumber, x.Date.Month })
.Select(x => new ReportTableRow
{
WeekNumber = CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(x.Max(y => y.Date), CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday),
Month = x.Key.Month,
MachineNumber = x.FirstOrDefault().MachineNumber,
TEST = x.Sum(y => y.TEST),
TEST_TRAFFICLIGHT = GetTrafficLight(x.Sum(y => y.TEST)
})
.ToList();
break;
}
The question is, is there anyway to remove the "Select" code and pass it into either a method or object that would accept an anonymous grouping so that it can be reused multiple times. Changing the anonymous grouping to a compile time object that contains two properties means duplicates are then returned in the dataset, could be that finding a way to remove the duplicates in compile time grouped members might help to resolve?
Please note, the code has been created to pose this question.
This is optional, but the first thing I would do is put that "aggregate" into a boolean variable:
bool byWeek = aggregate.ToUpper() == "BY WEEK";
The next thing I would do is capture and tame that "GetWeekOfYear" logic:
int getFirstMonday (DateTime date) =>
CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(
date,
CalendarWeekRule.FirstFourDayWeek,
DayOfWeek.Monday
);
Doing these things makes it more feasible to put the conditional logic inside the GroupBy and Select methods, making it so that you don't have to do the whole thing twice:
List<ReportTableRow> results =
reportTable
.GroupBy(x => new {
x.AccountNumber,
TimeBin = byWeek ? getFirstMonday(x.Date): x.Date.Month
})
.Select(x => new ReportTableRow {
WeekNumber = byWeek ? x.Key.TimeBin : getFirstMonday(x.Max(y => y.Date)),
Month = byWeek ? x.Max(y => y.Date).Month : x.Key.TimeBin,
MachineNumber = x.FirstOrDefault().MachineNumber,
TEST = x.Sum(y => y.TEST),
TEST_TRAFFICLIGHT = GetTrafficLight(x.Sum(y => y.TEST))
})
.ToList();
I should note that your sample data makes it so that the results are the same regardless of 'aggregate' setting. But if you change it a bit, such as changing the days for some of the entries, you'll get different results in the aggregation. And at least for the changes I made my code repeats the behavior of your code when toggling 'aggregate'.

Group the results by calender week and get the average

I have a data set as follows
Name Start Date End Date Percentage
A 1/1/2015 31/3/2015 50
B 1/1/2015 30/6/2015 100
C 1/4/2015 30/6/2015 50
I want to group this data set by calender week and need to get the average percentage
The results should be as follows
Year Week Average
2015 1 ---
2015 2 ---
What will be the optimum linq query to get this result. I have tried following code. But the values are not correct
var grouped = result.Select(p =>
new
{
Week = CultureInfo.InvariantCulture.Calendar.GetWeekOfYear
(p.StartDate, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday),
Year = p.StartDate.Year,
EndDate = p.EndDate,
percentage = p.percentage
})
.GroupBy(x => new { x.Year, x.Week })
.OrderBy(x=>x.Key.Year).ThenBy(x=>x.Key.Week)
.Select((g, i) => new
{
g.Key.Year,
g.Key.Week,
Sum = g.Average(t => t.percentage)
});
public class Program
{
public static void Main()
{
var input = new List<P>
{
new P { StartDate = new DateTime(2015, 1, 1), EndDate = new DateTime(2015, 3, 31), Week = DayOfWeek.Monday, Year = 2015, percentage = 50 },
new P { StartDate = new DateTime(2015, 1, 1), EndDate = new DateTime(2015, 6, 30), Week = DayOfWeek.Monday, Year = 2015, percentage = 100 },
new P { StartDate = new DateTime(2015, 4, 1), EndDate = new DateTime(2015, 6, 30), Week = DayOfWeek.Monday, Year = 2015, percentage = 50 },
};
var result = input.SelectMany(p =>
{
var weeks = Enumerable.Range(0, (p.EndDate - p.StartDate).Days / 7)
.Select(i => CultureInfo.InvariantCulture.Calendar.AddWeeks(p.StartDate, i))
.Select(i => CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(i, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday));
return weeks.Select(w => new { Week = w, Percentage = p.percentage / weeks.Count() });
})
.GroupBy(x => x.Week)
.Select(g => new { Week = g.Key, Average = g.Average(t => t.Percentage) });
}
}
public class P
{
public DateTime StartDate;
public DateTime EndDate;
public DayOfWeek Week;
public int Year;
public double percentage;
}
I haven't used Year here, but you can get the idea and add year yourself.

Get dates that contain all selected products

I have a calendar app where you select various combinations of products- a service goes out and gets the available dates based on the calendar date range. A date is only "Available" if ALL selected products are available on a particular date.
class SelectedProduct
{
public int ID { get; set; }
public int Qty { get; set; }
}
class AvailableInventory
{
public int ID { get; set; }
public DateTime Date { get; set; }
}
// List of selected products from user
List<SelectedProduct> SelectedProducts;
// populated from service with all dates for all products
List<AvailableInventory> AvailableInventory;
I want to be able to say get list of Available Inventory for each date that contains inventory for all ID's in selected products.
This is (non-working) pusdo code of a possible solution, I just don't know linq well enough to get it right
var results = List<AvailableInventory>();
foreach (var group in AvailableInventory.GroupBy(x => x.Date))
{
if (group.Contains(ALL ID's in SelectedProducts)
{
results.AddRange(group);
}
}
This groups inventory by date (ignoring the date portion), then selects only those groups that contain all selected product IDs, and finally selects all available inventory for the matching groups.
var results =
AvailableInventory.GroupBy(i => i.Date.Date)
.Where(g => !SelectedProducts.Select(p => p.ID)
.Except(g.Select(i => i.ID))
.Any())
.SelectMany(g => g);
The result is a collection of AvailableInventory.
You can group by the date, then filter out groups that don't have all the SelectedProducts.
// List of selected products from user
List<SelectedProduct> SelectedProducts = new List<SelectedProduct> {
new SelectedProduct { ID = 1, Qty = 1 },
new SelectedProduct { ID = 2, Qty = 2 },
};
// populated from service with all dates for all products
List<AvailableInventory> AvailableInventory = new List<AvailableInventory> {
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 11) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 11) },
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 12) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 13) },
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 14) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 14) },
};
var query = AvailableInventory.GroupBy(i => i.Date)
.Where(g => SelectedProducts.All(s => g.Any(i => i.ID == s.ID)));
foreach(var group in query)
{
Console.WriteLine("Date: {0}", group.Key);
foreach(var inventory in group)
{
Console.WriteLine(" Available: {0}", inventory.ID);
}
}
This would output:
Date: 4/11/2014 12:00:00 AM
Available: 1
Available: 2
Date: 4/14/2014 12:00:00 AM
Available: 1
Available: 2
I think this is what you are looking for. Try this
var result = AvailableInventory.Where(i => SelectedProducts.Any(x => x.ID == i.ID)).GroupBy(o => o.Date)
.Select(g => g.First()).ToList();
This is the test data I used based on your class definition for AvailableInventory and SelectedProduct
// List of selected products from user
List<SelectedProduct> SelectedProducts = new List<SelectedProduct> {
new SelectedProduct { ID = 1, Qty = 2 },
new SelectedProduct { ID = 2, Qty = 4 },
new SelectedProduct { ID = 5, Qty = 10 }
};
// populated from service with all dates for all products
List<AvailableInventory> AvailableInventory = new List<AvailableInventory> {
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 01) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 02) },
new AvailableInventory { ID = 3, Date = new DateTime(2014, 04, 02) },
new AvailableInventory { ID = 4, Date = new DateTime(2014, 04, 10) },
new AvailableInventory { ID = 5, Date = new DateTime(2014, 04, 10) }
};
This should give you only the records with ID = 1, ID = 2 and ID = 5 because that's what common between both AvailableInventory and SelectedProducts lists.
It would help if you actually tried something.
Given this:
List<SelectedProduct> SelectedProducts ;
List<AvailableInventory> AvailableInventory ;
Something like this will probably get what you want:
int[] DatesWithAllSelectedProductsAvailable =
AvailableInventory
.GroupBy( x => x.Date )
.Where ( g => g.All( x => SelectedProducts.Any( p => p.ID == x.ID ) ) )
.Select( x => x.Key )
.Distinct()
.OrderBy( x => x )
.ToArray()
;

Need help Linq Query Group By + Count

I have following Table with records for ActivityLog:
ID, UserId, Category, Created
1 11 DeAssigned 05/10/2012
2 11 LogIn 05/11/2012
3 20 Assigned 06/15/2012
4 11 Assigned 06/10/2012
5 20 DeAssigned 06/13/2012
6 20 Assigned 07/12/2012
7 11 DeAssigned 07/16/2012
8 20 Assigned 08/15/2012
...
now i want to query the Table to create same struct the same Result such as this:
var data = new[] {
new { Month = "05", Assigned = 14, DeAssigned = 5, LogIn=1 },
new { Month = "06", Assigned = 5, DeAssigned = 2, LogIn=0 },
new { Month = "07", Assigned = 50, DeAssigned = 8, LogIn=0 },
new { Month = "08", Assigned = 15, DeAssigned = 1, LogIn=0 }
};
what i have achieved:
var result = (from l in repoA.GetAll()
where l.Created >= System.DateTime.Now.AddYears(-1)
group l by new { l.Created.Value.Month, l.Category }
into groups
orderby groups.Key.Month
select new
{
Month = groups.Key.Month,
Category = groups.Key.Category,
Count = groups.Count()
});
there is not optimal result but count all Activity's grouped by Month:
[0] {Month = 6, Category = Assigned, Count = 2}
[0] {Month = 6, Category = Designed, Count = 1}
[0] {Month = 6, Category = LogIn, Count = 1}
[0] {Month = 7, Category = Assigned, Count = 3}
How can i query my Table to Format my Result in "Horizontal Counted" format?
Or simplier:
var result = (from l in repoA.GetAll()
where l.Created >= System.DateTime.Now.AddYears(-1)
group l by l.Created.Month into groups
orderby groups.Key ascending
select new
{
Month = groups.Key,
Assigned = groups.Where(g => g.Category == "Assigned").Count(),
Deassigned = groups.Where(g => g.Category == "DeAssigned").Count(),
LogIn = groups.Where(g => g.Category == "LogIn").Count()
});

Could you help filtering a collection and creating another in linq

I have created the following to explain my problem.
Given a list with PayDates and periods. I Need to create a new list containing 1 item for each period that is nearest to its Pay Date.
So given the example the list should return 2 items "1st of the month" and "17th of the month" as they are the closest to their paydate in their period
any suggestions?
private static List<Schedule> GetFilteredScheduleList()
{
List<Schedule>oldScheduleList=new List<Schedule>();
oldScheduleList.Add(new Schedule { PayDate = new DateTime(2011, 1, 1), Id = 1, Name = "1st of the month", Period = SchedulePeriod.Monthly });
oldScheduleList.Add(new Schedule { PayDate = new DateTime(2011, 1, 4), Id = 1, Name = "4th of the month", Period = SchedulePeriod.Monthly });
oldScheduleList.Add(new Schedule { PayDate = new DateTime(2011, 1, 19), Id = 1, Name = "19th of the month", Period = SchedulePeriod.Monthly });
oldScheduleList.Add(new Schedule { PayDate = new DateTime(2012, 1, 3), Id = 1, Name = "3rd of each quarter", Period = SchedulePeriod.Quarterly });
oldScheduleList.Add(new Schedule { PayDate = new DateTime(2013, 1, 8), Id = 1, Name = "8th each quarter", Period = SchedulePeriod.Quarterly });
oldScheduleList.Add(new Schedule { PayDate = new DateTime(2011, 1, 17), Id = 1, Name = "17th of the month", Period = SchedulePeriod.Quarterly });
// Need to create a new list containing 1 item for each period that is nearest to its Pay Date.Starting point it's today's date.
// so the list should return 2 items "1st of the month" and "17th of the month" as they are the closest to their paydate in their period
List<Schedule> newScheduleList = oldScheduleList.Where(x=>x.PayDate)
}
This should select the Schedule item from each period that is closest to now. Is this what you meant?
oldScheduleList
.GroupBy(s => s.Period)
.Select(
g => g
.OrderBy(s => Math.Abs((s.PayDate - DateTime.Now).TotalSeconds))
.First()
)
Edit:
In response to your comment, to get the first item for the period is a little simpler. I've commented a Where clause. This will get the first in period from now (i.e. dates that have expired are ignored)
oldScheduleList
.GroupBy(s => s.Period)
.Select(
g => g
//.Where(s => s.Paydate >= DateTime.Now) //filter expired dates
.OrderBy(s => s.PayDate)
.First()
)

Categories

Resources