Group the results by calender week and get the average - c#

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.

Related

Count occurrences of an event by date with 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() };

Get Month name and year from list

I am trying to retrieve the month name and the year using LINQ from a list that has 2 properties without repeating the name of the months and the year.
public class Record
{
public int Id { get; set; }
public DateTime Date { get; set; }
}
DateTime d1 = new DateTime(2015, 1, 14);
DateTime d2 = new DateTime(2016, 3, 12);
DateTime d3 = new DateTime(2016, 4, 17);
DateTime d4 = new DateTime(2015, 5, 19);
DateTime d5 = new DateTime(2016, 6, 10);
List<Record> dates = new List<Record>
{
new Record { Id= 1, Date = d1 },
new Record { Id= 2, Date = d2 },
new Record { Id= 3, Date = d3 },
new Record { Id= 4, Date = d4 },
new Record { Id= 5, Date = d5 }
};
//Month should be in string format (January,June, etc)
// Get Year and Months from that list withour repeating the names
//List<string> months =
//List < string > years =
For months and using Linq:
List<string> months = dates.Select(d => d.Date.ToString("MMMM"))
.Distinct()
.ToArray();
Information on the ToStirng format for the month name can be found on MSDN here.
and for years:
List<string> years = dates.Select(d => d.Date.Year.ToString())
.Distinct()
.ToArray();
Although it is unclear how you want the list of years to look.
Information on Distinct can be found on MSDN here.
With an extension method to simplify it (taken from here):
static class DateTimeExtensions
{
public static string ToMonthName(this DateTime dateTime)
{
return CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(dateTime.Month);
}
}
You can do this:
var months = dates.Select(r => r.Date.ToMonthName())
.Distinct();
var years = dates.Select(r => r.Date.Year)
.Distinct();
Note that I've given years as int here, if you want strings, then just add ToString().

Translate foreach to linq to solve running balance

I'm new in linq and read some stuff on the web about them.
Now, below is a query works fine which is to calculate the project 12-month running balance from the current date. Is it possible to translate this to linq?
It would help me understand the linq better.
var firstDayMonth = new DateTimeOffset(new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1));
var months = Enumerable.Range(0, 12)
.Select(m => firstDayMonth.AddMonths(m));
List<SomeDate> SomeDates = new List<SomeDate>()
{
new SomeDate { Id = 7, Month = firstDayMonth.AddMonths(0), Balance = 1m },
new SomeDate { Id = 7, Month = firstDayMonth.AddMonths(0), Balance = 3m },
new SomeDate { Id = 8, Month = firstDayMonth.AddMonths(1), Balance = 6m },
new SomeDate { Id = 8, Month = firstDayMonth.AddMonths(1), Balance = 5m },
new SomeDate { Id = 8, Month = firstDayMonth.AddMonths(1), Balance = 3m },
new SomeDate { Id = 9, Month = firstDayMonth.AddMonths(2), Balance = 5m },
new SomeDate { Id = 10, Month = firstDayMonth.AddMonths(3), Balance = 3m },
new SomeDate { Id = 12, Month = firstDayMonth.AddMonths(5), Balance = 15m },
new SomeDate { Id = 13, Month = firstDayMonth.AddMonths(6), Balance = 16m },
new SomeDate { Id = 13, Month = firstDayMonth.AddMonths(6), Balance = 12m },
};
var projected12MonthsBalance = new List<SomeDate>();
foreach(var month in months)
{
projected12MonthsBalance.Add(new SomeDate { Month = month, Balance = SomeDates.TakeWhile(s => s.Month <= month).Sum(s => s.Balance) });
}
Console.WriteLine(projected12MonthsBalance);
public class SomeDate
{
public int Id { get; set; }
public DateTimeOffset Month { get; set; }
public decimal Balance { get; set; }
}
Try this:
var projected12MonthsBalance = months.Select(x => new SomeDate
{
Month = x,
Balance = SomeDates.TakeWhile(s => s.Month <= x).Sum(s => s.Balance)
}).ToList();

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

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