Related
I am currently in the lookout for an algorithm that can help me condense a list of changes.
A change class looks like this
public class change
{
DateTime From;
DateTime To;
string[] ChangedProperties;
}
Instances of this is then collected in list like this
string[] changes1 = {"A", "B", "C"};
string[] changes2 = {"D"};
string[] changes3 = {"A","B","C","E"};
string[] changes4 = {"A"};
string[] changes5 = {"B"};
string[] changes6 = {"B"};
Change first = new Change()
{
From = new DateTime(2008, 1, 1, 0, 00, 00),
To = new DateTime(2080, 1, 1, 0, 00, 00),
Active = changes1
};
Change second = new Change()
{
From = new DateTime(2008, 1, 1, 0, 00, 00),
To = new DateTime(2010, 1, 1, 0, 00, 00),
Active = changes2
};
Change third = new Change()
{
From = new DateTime(2008, 1, 1, 0, 00, 00),
To = new DateTime(2020, 1, 1, 0, 00, 00),
Active = changes3
};
Change fourth = new Change()
{
From = new DateTime(2005, 1, 1, 0, 00, 00),
To = new DateTime(2008, 1, 1, 0, 00, 00),
Active = changes4
};
Change fifth = new Change()
{
From = new DateTime(2003, 1, 1, 0, 00, 00),
To = new DateTime(2008, 1, 1, 0, 00, 00),
Active = changes5
};
Change sixth = new Change()
{
From = new DateTime(2008, 1, 1, 0, 00, 00),
To = new DateTime(2015, 1, 1, 0, 00, 00),
Active = changes6
};
List<Change> changes = new List<Change>();
changes.Add(first);
changes.Add(second);
changes.Add(third);
changes.Add(fourth);
changes.Add(fifth);
changes.Add(sixth);
I would like to condense this list such that changes that are reflected fully overlapping timewise.
ex.
first changes attribute {"A", "B", "C"} in Datetime span 2008-1-1T00:00:00 to 2080-1-1T00:00:00 but the sixth change changes attribute B in datetime span 2008-1-1T00:00:00 to 2015-1-1T00:00:00. The information provided by the Sixth change is redundant as it is fully enclosed in the first change
first : |---------|
Sixth : |------|
The condensed list should only contain
Changes: first, Second, (only change E from third), fifth
This what I have so far:
https://dotnetfiddle.net/9ytlh7
This is the way I ended up doing it...
https://dotnetfiddle.net/OliMgt
Not sure whether this is efficient?
Assume I have the following table called Timing:
Obviously each row represents a shift in a specific day.
A day can have non-overlapping multiple shifts.
If a shift spans the next day it will be splitted at midnight, and the second half would have a parent id of the first half (as you can see in row 24 and 31)
I want to query how many minutes until my day ends (the next closing time).
For instance if I'm at day 1, my day ends at day 2 - 2:00 AM (because the shift starts at day 1 - 9:00, and ends at day 2 - 2:00).
I have to be careful if there are gaps (like weekends or so). Notice there is no day 3, so the next closing time would be day 4 - 23:15 (provided that you are at day 3).
I'm mainly looking for a Linq query (Timing.Where(x=> x.close_time< .... etc).
But I'm thinking that it might be super complicated, so I'm ok with having a raw SQL query.
EDIT:
This is what I got so far:
var localTime = DateTime.Now;
var tomorrowDay = ((int)localTime.DayOfWeek + 7 + 1) % 7;
Timing lastShift = Timings.Where(x =>
((int)x.DayOfWeek) == tomorrowDay && x.ParentId != null)
.SingleOrDefault(); // Either it is tomorrow but starts today.
if (lastShift != null)
{
return Convert.ToInt32((lastShift.CloseTime - localTime.TimeOfDay).TotalMinutes);
}
lastShift = Timings
.Where(x => x.DayOfWeek == localTime.DayOfWeek && x.CloseTime >= localTime.TimeOfDay)
.OrderByDescending(x => x.CloseTime)
.Take(1).SingleOrDefault();
return Convert.ToInt32((lastShift.CloseTime - localTime.TimeOfDay).TotalMinutes);
EDIT:
Thanks to #Han, here is a list of the same table above:
var Timings = new []
{
new Timing(22, (DayOfWeek)0, new TimeSpan(9,45,0), new TimeSpan(11, 15, 0),null),
new Timing(23, (DayOfWeek)0, new TimeSpan(13, 0, 0), new TimeSpan( 15, 0, 0), null),
new Timing(24, (DayOfWeek)1, new TimeSpan( 9, 0, 0), new TimeSpan(23, 59, 59), null),
new Timing(31, (DayOfWeek)2, new TimeSpan( 0, 0, 0), new TimeSpan( 2, 0, 0), 24),
new Timing(25, (DayOfWeek)2, new TimeSpan(10, 0, 0), new TimeSpan(12, 0, 0), null),
new Timing(26, (DayOfWeek)2, new TimeSpan(15, 0, 0), new TimeSpan(17, 0, 0), null),
new Timing(28, (DayOfWeek)4, new TimeSpan( 9, 45, 0), new TimeSpan(23, 15, 0), null),
new Timing(29, (DayOfWeek)5, new TimeSpan( 9, 45, 0), new TimeSpan(23, 15, 0), null),
new Timing(30, (DayOfWeek)6, new TimeSpan( 9, 45, 0), new TimeSpan(23, 15, 0), null),
};
class Timing
{
public int Id {get; set;}
public DayOfWeek DayOfWeek {get; set;}
public TimeSpan OpenTime {get; set;}
public TimeSpan CloseTime {get; set;}
public int? ParentId {get; set;}
public Timing(int id, DayOfWeek dow, TimeSpan openTime, TimeSpan closeTime, int? parentId)
{
this.Id = id;
this.DayOfWeek = dow;
this.OpenTime = openTime;
this.CloseTime = closeTime;
this.ParentId = parentId;
}
}
I suggest to left self-join your table to get the close time in the next day. I assume each row has zero or one child row. I don't use table but array, but the query should be the same. I code in LINQPad.
void Main()
{
var Timings = new []
{
new Timing(22, 0, new DateTime(2021, 9, 12, 9, 45, 0), new DateTime(2021, 9, 12, 11, 15, 0), null),
new Timing(23, 0, new DateTime(2021, 9, 12, 13, 0, 0), new DateTime(2021, 9, 12, 15, 0, 0), null),
new Timing(24, 1, new DateTime(2021, 9, 13, 9, 0, 0), new DateTime(2021, 9, 13, 23, 59, 59), null),
new Timing(31, 2, new DateTime(2021, 9, 14, 0, 0, 0), new DateTime(2021, 9, 14, 2, 0, 0), 24),
new Timing(25, 2, new DateTime(2021, 9, 14, 10, 0, 0), new DateTime(2021, 9, 14, 12, 0, 0), null),
new Timing(26, 2, new DateTime(2021, 9, 14, 15, 0, 0), new DateTime(2021, 9, 14, 17, 0, 0), null),
new Timing(28, 4, new DateTime(2021, 9, 16, 9, 45, 0), new DateTime(2021, 9, 16, 23, 15, 0), null),
new Timing(29, 5, new DateTime(2021, 9, 17, 9, 45, 0), new DateTime(2021, 9, 17, 23, 15, 0), null),
new Timing(30, 6, new DateTime(2021, 9, 18, 9, 45, 0), new DateTime(2021, 9, 18, 23, 15, 0), null),
};
var timingGroupedWithChildren = (
from t1 in Timings.Where(x => x.ParentId == null) // parent rows only
join t2 in Timings.Where(x => x.ParentId != null) // childr rows only
on t1.Id equals t2.ParentId // left join parent's Id with child's ParentId
into nextDay
select new {t1, nextDay})
.Dump() //unremark this line to get show the result in LINQPad
;
}
class Timing
{
public int Id {get; set;}
public int DayOfWeek {get; set;}
public DateTime OpenTime {get; set;}
public DateTime CloseTime {get; set;}
public int? ParentId {get; set;}
public Timing(int id, int dow, DateTime openTime, DateTime closeTime, int? parentId)
{
this.Id = id;
this.DayOfWeek = dow;
this.OpenTime = openTime;
this.CloseTime = closeTime;
this.ParentId = parentId;
}
}
The timingGroupedWithChildren looks like this:
Notice that only id = 24 has nextDay, the other rows don't have nextDay. There are 8 items (shown at top left corner), but only Id 23 and 24 are shown in detail (other rows are collapsed to save space because my screen is not large enough).
Now it's easy to get the closing time in next day. First approach is like this.
void Main()
{
var Timings = new []
{
new Timing(22, 0, new DateTime(2021, 9, 12, 9, 45, 0), new DateTime(2021, 9, 12, 11, 15, 0), null),
new Timing(23, 0, new DateTime(2021, 9, 12, 13, 0, 0), new DateTime(2021, 9, 12, 15, 0, 0), null),
new Timing(24, 1, new DateTime(2021, 9, 13, 9, 0, 0), new DateTime(2021, 9, 13, 23, 59, 59), null),
new Timing(31, 2, new DateTime(2021, 9, 14, 0, 0, 0), new DateTime(2021, 9, 14, 2, 0, 0), 24),
new Timing(25, 2, new DateTime(2021, 9, 14, 10, 0, 0), new DateTime(2021, 9, 14, 12, 0, 0), null),
new Timing(26, 2, new DateTime(2021, 9, 14, 15, 0, 0), new DateTime(2021, 9, 14, 17, 0, 0), null),
new Timing(28, 4, new DateTime(2021, 9, 16, 9, 45, 0), new DateTime(2021, 9, 16, 23, 15, 0), null),
new Timing(29, 5, new DateTime(2021, 9, 17, 9, 45, 0), new DateTime(2021, 9, 17, 23, 15, 0), null),
new Timing(30, 6, new DateTime(2021, 9, 18, 9, 45, 0), new DateTime(2021, 9, 18, 23, 15, 0), null),
};
var timingGroupedWithChildren = (
from t1 in Timings.Where(x => x.ParentId == null) // parent rows only
join t2 in Timings.Where(x => x.ParentId != null) // childr rows only
on t1.Id equals t2.ParentId // left join parent's Id with child's ParentId
into nextDay
select new {
t1.Id,
t1.DayOfWeek,
t1.OpenTime,
// if current row's next day is null, then use current row's CloseTime
// otherwise use next day's CloseTime
CloseTime = nextDay.Where(x => x.ParentId == t1.Id).Count() == 0 ? t1.CloseTime : nextDay.Where(x => x.ParentId == t1.Id).Single().CloseTime
})
//.Dump() //unremark this line to get show the result in LINQPad
;
var myShift = timingGroupedWithChildren.Where(x => x.Id == 24).Single();
var myWorkingHours = (myShift.CloseTime - myShift.OpenTime).TotalHours;
Console.WriteLine($"Working hours = {myWorkingHours}");
}
class Timing
{
public int Id {get; set;}
public int DayOfWeek {get; set;}
public DateTime OpenTime {get; set;}
public DateTime CloseTime {get; set;}
public int? ParentId {get; set;}
public Timing(int id, int dow, DateTime openTime, DateTime closeTime, int? parentId)
{
this.Id = id;
this.DayOfWeek = dow;
this.OpenTime = openTime;
this.CloseTime = closeTime;
this.ParentId = parentId;
}
}
You can see in pic below that I substitute the closing day if current row has children. But I don't test this query with an actual database (I'm using an array) and I don't like calling nextDay.Where(x => ...).Count() twice because some methods in LINQ, eg. Count(), iterates all rows. It's filtered with Where(x => ...) but I can't' say anything unless I see the actual SQL statement executed when calling this query. You can see the actual statement if you turn on SQL Profiler in SQL Management Studio or use LINQPad SQL translation. The button is at the top in the pic (Result lambda symbol SQL IL Tree).
Another approach is just take the child row and do the Count() after you fetch from SQL.
void Main()
{
var Timings = new []
{
new Timing(22, 0, new DateTime(2021, 9, 12, 9, 45, 0), new DateTime(2021, 9, 12, 11, 15, 0), null),
new Timing(23, 0, new DateTime(2021, 9, 12, 13, 0, 0), new DateTime(2021, 9, 12, 15, 0, 0), null),
new Timing(24, 1, new DateTime(2021, 9, 13, 9, 0, 0), new DateTime(2021, 9, 13, 23, 59, 59), null),
new Timing(31, 2, new DateTime(2021, 9, 14, 0, 0, 0), new DateTime(2021, 9, 14, 2, 0, 0), 24),
new Timing(25, 2, new DateTime(2021, 9, 14, 10, 0, 0), new DateTime(2021, 9, 14, 12, 0, 0), null),
new Timing(26, 2, new DateTime(2021, 9, 14, 15, 0, 0), new DateTime(2021, 9, 14, 17, 0, 0), null),
new Timing(28, 4, new DateTime(2021, 9, 16, 9, 45, 0), new DateTime(2021, 9, 16, 23, 15, 0), null),
new Timing(29, 5, new DateTime(2021, 9, 17, 9, 45, 0), new DateTime(2021, 9, 17, 23, 15, 0), null),
new Timing(30, 6, new DateTime(2021, 9, 18, 9, 45, 0), new DateTime(2021, 9, 18, 23, 15, 0), null),
};
var timingGroupedWithChildren = (
from t1 in Timings.Where(x => x.ParentId == null) // parent rows only
join t2 in Timings.Where(x => x.ParentId != null) // childr rows only
on t1.Id equals t2.ParentId // left join parent's Id with child's ParentId
into nextDay
select new {
t1.Id,
t1.DayOfWeek,
t1.OpenTime,
t1.CloseTime,
NextDay = nextDay
})
//.Dump() //unremark this line to get show the result in LINQPad
;
var myShift = timingGroupedWithChildren.Where(x => x.Id == 24).Single();
var myWorkingHours = ((myShift.NextDay.Count() == 0 ? myShift.CloseTime : myShift.NextDay.Single().CloseTime) - myShift.OpenTime).TotalHours;
Console.WriteLine($"Working hours = {myWorkingHours}");
}
class Timing
{
public int Id {get; set;}
public int DayOfWeek {get; set;}
public DateTime OpenTime {get; set;}
public DateTime CloseTime {get; set;}
public int? ParentId {get; set;}
public Timing(int id, int dow, DateTime openTime, DateTime closeTime, int? parentId)
{
this.Id = id;
this.DayOfWeek = dow;
this.OpenTime = openTime;
this.CloseTime = closeTime;
this.ParentId = parentId;
}
}
You can see that only row with Id = 24 has NextDay (like pic #1).
I have an array of DateTimes:
public DateTime GetNextGame()
{
DateTime[] dateTimes = new DateTime[]
{
new DateTime(2016, 4, 11, 7, 5, 0),
new DateTime(2016, 4, 12, 7, 5, 0),
new DateTime(2016, 4, 13, 7, 5, 0),
new DateTime(2016, 5, 30, 7, 5, 0),
new DateTime(2016, 5, 31, 7, 5, 0),
new DateTime(2016, 6, 1, 7, 5, 0),
new DateTime(2016, 6, 2, 7, 5, 0),
new DateTime(2016, 6, 14, 7, 5, 0),
new DateTime(2016, 6, 15, 7, 5, 0),
new DateTime(2016, 6, 16, 7, 5, 0),
new DateTime(2016, 8, 16, 7, 5, 0),
new DateTime(2016, 8, 17, 7, 5, 0),
new DateTime(2016, 9, 12, 7, 5, 0),
new DateTime(2016, 9, 13, 7, 5, 0),
new DateTime(2016, 9, 14, 7, 5, 0),
new DateTime(2016, 9, 19, 7, 5, 0),
new DateTime(2016, 9, 20, 7, 5, 0),
new DateTime(2016, 9, 21, 7, 5, 0),
new DateTime(2016, 9, 22, 7, 5, 0)
};
My question is.. how I do return the next value in the array if the current value doesn't meet the condition? I did some research on this and found a similar question but it was in PHP which I didn't understand.
so I have a loop going through the array then the conditional statement:
foreach(DateTime date in dateTimes)
{
if(date.TimeOfDay < DateTime.Today.TimeOfDay)
{
return //next value in array?
}
}
also trying to return the date .ToShortDateString along with the .ToShortTimeString for my view.
I want it to look like this.. "The Next Game is Monday, March 3rd at 7:05 PM"
Any help is appreciated.
The Linq SkipWhile operator could be used:
DateTime firstValid = dateTimes.SkipWhile(d => d.TimeOfDay < DateTime.Today.TimeOfDay).First();
EDIT: Just wanted to mention that we assume the array is already ordered ascending.
You should use a for-loop if you need to access the next(or previous) item:
for(int i = 0; i < dateTimes.Length - 1; i++)
{
(dateTimes[i].TimeOfDay < DateTime.Today.TimeOfDay)
{
return dateTimes[i + 1];
}
}
Apart from that, your comparison is pointless:
if(date.TimeOfDay < DateTime.Today.TimeOfDay)
{
// this will never be true
}
DateTime.TimeOfDay returns the TimeSpan of a DateTime, so the time component. DateTime.Today returns today's midnight so it's TimeOfDay will be TimeSpan.Zero. That's why date.TimeOfDay < DateTime.Today.TimeOfDay will never be true.
I assume that you want the first DateTime which date is before today. Then use this:
if(date.Date < DateTime.Today)
{
// ...
}
Try for loop instead of foreach:
for (int i = 0; i < dateTimes.Length; ++i) {
DateTime date = dateTimes[i];
if (date.TimeOfDay < DateTime.Today.TimeOfDay) {
if (i < dateTimes.Length - 1) // not the last item
return date[i + 1];
else {
//TODO: there's no "next item" for the last one
}
}
}
If you, for some reasons, don't want to use for loop, then try this:
int counter = 0;
foreach(DateTime date in dateTimes)
{
if(date.TimeOfDay < DateTime.Today.TimeOfDay)
{
return dateTimes[counter + 1];
}
counter++;
}
Use a for-loop instead foreach.
for(int i = 0; i < dateTimes.Length; i++)
{
if(dateTimes[i].TimeOfDay < DateTime.Today.TimeOfDay && i < dateTimes.Length - 1)
{
return dateTimes[i + 1];
}
}
Also note you´ll need the check that you´re not out of the bounds of the array by writing if(i < dateTimes.Length).
You can also combine the loop-conditions a bit:
for(int i = 0; i < dateTimes.Length - 1; i++)
{
if(dateTimes[i].TimeOfDay < DateTime.Today.TimeOfDay)
{
return dateTimes[i + 1];
}
}
You could try:
foreach(DateTime date in dateTimes)
{
if(!(date.TimeOfDay < DateTime.Today.TimeOfDay))
{
continue;
}
return date;
}
You could do simply
return dateTimes.OrderBy(dateTime => dateTime).FirstOrDefault(dateTime => dateTime > DateTime.Now);
Or if you want to use Today and TimeOfDay
return dateTimes.OrderBy(dateTime => dateTime).FirstOrDefault(dateTime => dateTime.Date > DateTime.Today && dateTime.TimeOfDay > DateTime.Today.TimeOfDay);
When you know in advance your array is completely sorted, use:
var idx = Array.BinarySearch(dateTimes, DateTime.Now);
return dateTimes[idx >= 0 ? idx : ~idx];
Like many other solutions here, this will fail badly if there is no upcoming "next game". Check that you are within the range of the array if that should be handled more gracefully.
I am trying to get the time closer to 24:00:00 between two values, before midnight and after midnight.
EDIT: This is just an an example of what I am trying to do. In this case I should get both items.
var dt1 = new DateTime(2014, 11, 11, 23, 50, 00);
var dt2 = new DateTime(2014, 12, 11, 00, 50, 00);
var l = new List<DateTime>();
for (int i = 0; i < l.Count - 1; i++)
{
TimeSpan ts1 = new TimeSpan(l[i].Hour, l[i].Minute, l[i].Second);
TimeSpan ts2 = new TimeSpan(l[i + 1].Hour, l[i + 1].Minute, l[i + 1].Second);
if (ts1.TotalHours <= 23 && ts2.TotalHours >= 00)
{
Console.WriteLine("00:00:00 - {0} {1} \n", ts1, ts2);
}
}
Thank you for any help and advise.
Your question is quite confusing and not totally clear what it is you're trying to achieve, but I've made some assumptions, and come up with what I think maybe what you're after:
var l = new List<DateTime> {
new DateTime(2014, 11, 11, 22, 0, 0),
new DateTime(2014, 11, 11, 23, 45, 0),
new DateTime(2014, 11, 11, 23, 55, 0),
new DateTime(2014, 11, 11, 23, 59, 59),
new DateTime(2014, 11, 12, 0, 0, 0),
new DateTime(2014, 11, 12, 0, 4, 0),
new DateTime(2014, 11, 12, 0, 15, 0),
new DateTime(2014, 11, 12, 1, 0, 0),
new DateTime(2014, 11, 12, 10, 0, 0),
};
for (int i = 0; i < l.Count - 1; i++) {
if (l[i].TimeOfDay.TotalMinutes < 5 || l[i].TimeOfDay.TotalMinutes >= 23*60 + 55)
Console.WriteLine("{0} is close to midnight", l[i]);
else
Console.WriteLine("{0} is NOT close to midnight", l[i]);
}
I've loaded the list of dates/times with some test data, and the code simply prints out whether each date/time is within 5 minutes either side of midnight.
Another attempt at answering you're ambiguous question is as follows:
var l = new List<DateTime> {
new DateTime(2014, 11, 11, 15, 0, 0), // 15:00:00
new DateTime(2014, 11, 11, 16, 0, 0), // 16:00:00
new DateTime(2014, 11, 11, 17, 0, 0), // 17:00:00
new DateTime(2014, 11, 11, 17, 20, 0), // 17:20:00
new DateTime(2014, 11, 11, 18, 15, 0), // 18:15:00
new DateTime(2014, 11, 11, 19, 0, 0), // 19:00:00
new DateTime(2014, 11, 11, 22, 0, 0), // 22:00:00
new DateTime(2014, 11, 11, 23, 45, 0), // 23:45:00
new DateTime(2014, 11, 11, 23, 50, 00), // 23:50:00
new DateTime(2014, 12, 11, 00, 50, 00), // 00:50:00
new DateTime(2014, 11, 12, 1, 0, 0), // 01:00:00
new DateTime(2014, 11, 12, 10, 0, 0), // 10:00:00
};
var time = new TimeSpan(18, 0, 0); // <- Set the target time here
var offsetBefore = new TimeSpan(1, 0, 0, 0).TotalMilliseconds - time.TotalMilliseconds;
var offsetAfter = time.TotalMilliseconds * -1;
var closestBefore =
l.Aggregate(
(current, next) =>
next.AddMilliseconds(offsetBefore).TimeOfDay.TotalMilliseconds > current.AddMilliseconds(offsetBefore).TimeOfDay.TotalMilliseconds
? next
: current);
var closestAfter =
l.Aggregate(
(current, next) =>
next.AddMilliseconds(offsetAfter).TimeOfDay.TotalMilliseconds < current.AddMilliseconds(offsetAfter).TimeOfDay.TotalMilliseconds
? next
: current);
Console.WriteLine("{0} is the closest date/time before {1}.", closestBefore, time);
Console.WriteLine("{0} is the closest date/time after {1}.", closestAfter, time);
Console.WriteLine("00:00:00 - {0} {1} \n", closestBefore, closestAfter);
// OUTPUTS:
// 11/11/2014 17:20:00 is the closest date/time before 18:00:00.
// 11/11/2014 18:15:00 is the closest date/time after 18:00:00.
// 00:00:00 - 11/11/2014 17:20:00 11/11/2014 18:15:00
This will return the closest date/time in the list to midnight that is before midnight, and also separately the closest date/time in the list to midnight that is after midnight.
Hope this helps!
Try this:
var dt1 = new DateTime(2014, 11, 11, 23, 50, 00);
var dt2 = new DateTime(2014, 12, 11, 00, 50, 00);
var dt1temp = new DateTime(dt1.Year, dt1.Month, dt1.Day, 00, 00, 00);
var dt2temp = new DateTime(dt2.Year, dt2.Month, dt2.Day, 00, 00, 00);
TimeSpan time1 = new TimeSpan();
TimeSpan time2 = new TimeSpan();
TimeSpan time24 = new TimeSpan(24, 0, 0);
time1 = dt1 - dt1temp;
time2 = dt2 - dt2temp;
if (time1.Hours >= 12) time1 = time24 - time1;
if (time2.Hours >= 12) time2 = time24 - time2;
string result = "";
if (time1 < time2) result = "Time1 nearer to 00:00";
else result = "Time2 nearer to 00:00";
I am trying to work out if the following can be done in a LINQ to Objects statement.
I have a dictionary with the key as a DateTime (keys are values that are on multiple days) and a double value. I have too much data to plot on a graph so would like to the average value of each 5 minutes.
Sample Input
01/01/2012 23:53 5
01/01/2012 23:54 2
01/01/2012 23:55 1
01/01/2012 23:56 3
01/01/2012 23:57 4
01/01/2012 23:58 5
01/01/2012 23:59 6
02/01/2012 00:00 2
02/01/2012 00:01 4
02/01/2012 00:02 5
Expected Output
01/01/2012 23:55 3
02/01/2012 00:00 4.4
Using this helper method:
static DateTime RoundToNearestInterval(DateTime dt, TimeSpan d)
{
int f=0;
double m = (double)(dt.Ticks % d.Ticks) / d.Ticks;
if (m >= 0.5)
f=1;
return new DateTime(((dt.Ticks/ d.Ticks)+f) * d.Ticks);
}
it's as simple as
var result = from kvp in data
let key = RoundToNearestInterval(kvp.Key, TimeSpan.FromMinutes(5))
group kvp by key into g
select new { g.Key, Value = g.Average(x => x.Value) };
or
var result = data.GroupBy(kvp => RoundToNearestInterval(kvp.Key, TimeSpan.FromMinutes(5)), kvp => kvp.Value)
.Select(g => new { g.Key, Value = g.Average() });
LINQPad example:
void Main()
{
var tmp = new Dictionary<string, int>
{
{"01/01/2012 23:53", 5},
{"01/01/2012 23:54", 2},
{"01/01/2012 23:55", 1},
{"01/01/2012 23:56", 3},
{"01/01/2012 23:57", 4},
{"01/01/2012 23:58", 5},
{"01/01/2012 23:59", 6},
{"02/01/2012 00:00", 2},
{"02/01/2012 00:01", 4},
{"02/01/2012 00:02", 5}
};
var data = tmp.ToDictionary(d => DateTime.Parse(d.Key), d=>d.Value);
var result = from kvp in data
let key = RoundToNearestInterval(kvp.Key, TimeSpan.FromMinutes(5))
group kvp by key into g
select new {g.Key, Value = g.Average (x => x.Value) };
result.ToDictionary(r => r.Key, v => v.Value).Dump();
}
Here's a LINQ query that will do what you want, you can test this in LINQPad:
void Main()
{
var points = new[]
{
new { dt = new DateTime(2012, 1, 1, 23, 53, 00), value = 5 },
new { dt = new DateTime(2012, 1, 1, 23, 54, 00), value = 2 },
new { dt = new DateTime(2012, 1, 1, 23, 55, 00), value = 1 },
new { dt = new DateTime(2012, 1, 1, 23, 56, 00), value = 3 },
new { dt = new DateTime(2012, 1, 1, 23, 57, 00), value = 4 },
new { dt = new DateTime(2012, 1, 1, 23, 58, 00), value = 5 },
new { dt = new DateTime(2012, 1, 1, 23, 59, 00), value = 6 },
new { dt = new DateTime(2012, 1, 2, 00, 00, 00), value = 2 },
new { dt = new DateTime(2012, 1, 2, 00, 01, 00), value = 4 },
new { dt = new DateTime(2012, 1, 2, 00, 01, 00), value = 5 }
};
var interval = TimeSpan.FromMinutes(5);
var averageByInterval =
from point in points
let intervalStart = new DateTime(((int)((point.dt.Ticks + interval.Ticks / 2) / interval.Ticks)) * interval.Ticks)
group point.value by intervalStart into g
select new { g.Key, average = g.Average() };
averageByInterval.Dump();
}
Output:
Looks like your dictionary contains the ordered elements so we can do something like this:
var firstDate = yourDict.First().Key;
var output = yourDict.GroupBy(e=> (int)(e.Key - firstDate).TotalMinutes / 5)
.ToDictionary(g => g.First().Key
.AddMinutes(g.Average(e=>(e.Key - g.First().Key).TotalMinutes)),
g => g.Average(e=>e.Value));
NOTE: The input data of the OP uses a different cutlure than en-US, the month goes after the day. That's the noticeable point to take some test. otherwise the test won't be correct.
Try this:
var results =
data
.GroupBy(
x => (x.Key.Ticks / TimeSpan.TicksPerMinute + 2) / 5,
x => x.Value)
.Select(x => new
{
Key = new DateTime(x.Key * TimeSpan.TicksPerMinute * 5),
Value = x.Average()
});
var data = new Dictionary<DateTime, double>();
data.Add(new DateTime(2012, 1, 1, 23, 53, 0), 5);
data.Add(new DateTime(2012, 1, 1, 23, 54, 0), 2);
data.Add(new DateTime(2012, 1, 1, 23, 55, 0), 1);
data.Add(new DateTime(2012, 1, 1, 23, 56, 0), 3);
data.Add(new DateTime(2012, 1, 1, 23, 57, 0), 4);
data.Add(new DateTime(2012, 1, 1, 23, 58, 0), 5);
data.Add(new DateTime(2012, 1, 1, 23, 59, 0), 6);
data.Add(new DateTime(2012, 1, 2, 0, 0, 0), 2);
data.Add(new DateTime(2012, 1, 2, 0, 1, 0), 4);
data.Add(new DateTime(2012, 1, 2, 0, 2, 0), 5);
var result = data.GroupBy(kvp =>
{
var dt = kvp.Key;
var nearest5 = (int)Math.Round(dt.Minute / 5.0) * 5;
//Add the minutes after inital date creation to deal with minutes=60
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0).AddMinutes(nearest5);
})
.Select(g =>
{
return new KeyValuePair<DateTime, double>(g.Key, g.Average(row => row.Value));
});
foreach (var r in result)
{
Console.WriteLine(r.Key + " " + r.Value);
// 1/01/2012 11:55:00 PM 3
// 2/01/2012 12:00:00 AM 4.4
}