Condensing list of changes to a smaller list - c#

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?

Related

How to get the closing time of day in timing schedule that spans multiple days

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).

Get the closest time to 24:00:00 between less 23:00:00 to greater 24:00:00 in C#

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";

How do I use LINQ to compare a date and a datetime?

I'm trying to seed a database with two lists. The first list is just a bunch of items. The second list is a bunch of junk that references the first list. I'm trying to reference items from first list in the second list of junk, via LINQ, but I'm fairly certain I'm not doing it right:
For example, List 1:
var items = new List<Item>()
{
new Item { ItemtId = 1, DateTime = new DateTime(2014, 09, 05, 12, 30, 30), Text = "Cheese" },
new Item { ItemtId = 1, DateTime = new DateTime(2014, 09, 05, 12, 30, 30), Text = "Lettuce" },
new Item { ItemtId = 1, DateTime = new DateTime(2014, 09, 05, 12, 30, 30), Text = "Ground Beef" },
new Item { ItemtId = 1, DateTime = new DateTime(2014, 09, 03, 12, 30, 30), Text = "Ketchup" },
new Item { ItemtId = 1, DateTime = new DateTime(2014, 09, 03, 12, 30, 30), Text = "Mustard" },
};
var junk= new List<Junk>()
{
new Junk { JunkId = 1, DateTime = new DateTime(2014, 09, 05, 10, 00, 00), Items = items.Where(d => d.DateTime.ToShortDateString() == new DateTime(2014, 09, 05).ToShortDateString()},
new Junk { JunkId = 2, DateTime = new DateTime(2014, 09, 03, 11, 00, 00), Items = items.Where(d => d.DateTime.ToShortDateString() == new DateTime(2014, 09, 03).ToShortDateString()}
};
This seems like it should be the answer to me, because I'm only interested in the date and not the time, but it doesn't seed like this. I get the error:
LINQ to Entities does not recognize the method 'System.String
ToShortDateString()' method, and this method cannot be translated into
a store expression.
Any thoughts on how to build a better solution?
UPDATE
Looks like I didn't transcribe my code accurately to the site, and ended up finding my problem.
In my original code, I had:
new Junk { JunkId = 1, DateTime = new DateTime(2014, 09, 05, 10, 00, 00), Items = context.Items.Where(d => d.DateTime.Date == new DateTime(2014, 09, 05) }
When I should have had
new Junk { JunkId = 1, DateTime = new DateTime(2014, 09, 05, 10, 00, 00), Items = items.Where(d => d.DateTime.Date == new DateTime(2014, 09, 05) }
I'd been flirting with .ToShortDateString() unnecessarily
Change filter to:
.Where(d => d.DateTime.Date == new DateTime(2014, 09, 05))
You can use TruncateTime function of EntityFunctions Class.
new Junk { JunkId = 1, DateTime = new DateTime(2014, 09, 05, 10, 00, 00), Items = items.Where(d => EntityFunctions.TruncateTime(d.DateTime) == EntityFunctions.TruncateTime(new DateTime(2014, 09, 05))}
Note: If you using EntityFramework 6 then it should be System.Data.Entity.DbFunctions.TruncateTime(...) method from EntityFramework.dll.
Your subquery (the Items = part of the Junk variable) isn't a list but an enumerable, so it doesn't get materialized untill it's enumerated, and it's not enumerated untill you use it, down the road, in your linq to entities query.
If you change it as such :
var junk= new List<Junk>()
{
new Junk { JunkId = 1, DateTime = new DateTime(2014, 09, 05, 10, 00, 00), Items = items.Where(d => d.DateTime.ToShortDateString() == new DateTime(2014, 09, 05).ToShortDateString()).ToList()}, // this one will work as it gets materialized right now, not later, so by the time you pass it to linq to entities later it will already be a simple list of diferences, it won't try to compute "toshortdatestring" later, that will already be done
new Junk { JunkId = 2, DateTime = new DateTime(2014, 09, 03, 11, 00, 00), Items = items.Where(d => d.DateTime.ToShortDateString() == new DateTime(2014, 09, 03).ToShortDateString()}
};

How do I use LINQ to get the Average Value over a Date Range

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
}

LINQ group by problem

I am facing a problem with LINQ.
Here is the code,
public class TimeObject
{
public DateTime Time { get; set; }
}
private void TestLINQ()
{
List<TimeObject> results = new List<TimeObject>();
results.Add(new TimeObject() { Time = new DateTime(2010, 8, 1, 0, 10, 0)});
results.Add(new TimeObject() { Time = new DateTime(2010, 8, 1, 0, 20, 0)});
results.Add(new TimeObject() { Time = new DateTime(2010, 8, 1, 0, 30, 0)});
results.Add(new TimeObject() { Time = new DateTime(2010, 8, 1, 0, 40, 0)});
results.Add(new TimeObject() { Time = new DateTime(2010, 8, 1, 0, 50, 0)});
results.Add(new TimeObject() { Time = new DateTime(2010, 8, 1, 1, 10, 0)});
results.Add(new TimeObject() { Time = new DateTime(2010, 8, 1, 1, 20, 0)});
results.Add(new TimeObject() { Time = new DateTime(2010, 8, 1, 1, 30, 0)});
results.Add(new TimeObject() { Time = new DateTime(2010, 8, 1, 1, 40, 0)});
results.Add(new TimeObject() { Time = new DateTime(2010, 8, 1, 2, 15, 0)});
results.Add(new TimeObject() { Time = new DateTime(2010, 8, 1, 2, 30, 0)});
var counts = from result in results
group result by result.Time.Date.Hour into groupedResult
select new { Hour = groupedResult.Key, Count = groupedResult.Count() };
foreach (var count in counts)
{
MessageBox.Show(count.Hour + " - " + count.Count);
}
}
The output I expect is
0 - 5,
1 - 4,
2 - 2
But I am always getting 0 - 12. Why it is not grouping by hour?
Please help me. Thanks.
.Date trims off the hour etc. portion, giving you just a date. Try grouping by result.Time.Hour instead.
When you use .Date on a DateTime the time will be set to zero. So use this:
var counts = from result in results
group result by result.Time.Hour into groupedResult
select new { Hour = groupedResult.Key, Count = groupedResult.Count() };
change result.Time.Date.Hour to result.Time.Hour
Try converting group result by result.Time.Date.Hour into the hour value directly using a DateTime conversion rather than using a property of the Time object. The LINQ statement may be stopping the grouping operation at the Time property.

Categories

Resources