How to get groups of Datetimes by interval - c#

I want to cluster the list of DateTimes in the groups.
Every times that are near each other in interval of 30 sec for ins.
12:00:05,
12:00:10,
12:00:15,
12:30:15,
12:30:25
I have a list of the times- MainBookmarksList
MainBookmarksList.Add(dt1);
MainBookmarksList.Add(dt2);
MainBookmarksList.Add(dt3);
MainBookmarksList.Add(dt4);
MainBookmarksList.Add(dt5);
now I expect to have a groups like this
12:00:05,
12:00:10,
12:00:15,
********
12:30:15,
12:30:25
I tried this :
TimeSpan interval = new TimeSpan(0, 0, 15);
var groupedTimes = from dt in MainBookmarksList
group dt by dt.Ticks / interval.Ticks
into g
select new { Begin = new DateTime(g.Key * interval.Ticks), Values = g.ToList() };
but it doesn't return the correct results.

One possible solution would be this:
static void Main(string[] args)
{
var MainBookmarksList = new List<DateTime>();
MainBookmarksList.Add(new DateTime(1900, 1, 1, 12, 0, 5));
MainBookmarksList.Add(new DateTime(1900, 1, 1, 12, 0, 10));
MainBookmarksList.Add(new DateTime(1900, 1, 1, 12, 0, 15));
MainBookmarksList.Add(new DateTime(1900, 1, 1, 12, 30, 15));
MainBookmarksList.Add(new DateTime(1900, 1, 1, 12, 30, 25));
var interval = new TimeSpan(0, 0, 15);
var groupedTimes = new List<TimeGroup>();
var currentTimeGroup = new TimeGroup(MainBookmarksList[0]);
groupedTimes.Add(currentTimeGroup);
for (var i = 1; i < MainBookmarksList.Count; i++)
{
var time = MainBookmarksList[i];
if (time-currentTimeGroup.Begin > interval)
{
currentTimeGroup = new TimeGroup(time);
groupedTimes.Add(currentTimeGroup);
}
else
{
currentTimeGroup.Values.Add(time);
}
}
}
class TimeGroup
{
public TimeGroup(DateTime dateTime)
{
Begin = dateTime;
Values = new List<DateTime>() { dateTime };
}
public DateTime Begin { get; }
public List<DateTime> Values { get; }
}

Related

Calculate biggest positive sequnces in numbers

I am trying to find the biggest positive changes in the account transactions. It starts with opening balance and keep changing based on spending and deposits.
We need to find in which two dates the account contains the biggest or highest positive cash flow. I am not able to calculate and my code fails. I think my logic is wrong it gives a wrong output. Because between 8 Dec - 10 Dec account seen highest deposit or positive changes
12/11/2015 12:00:00 AM, 12/12/2015 12:00:00 AM, 23000
Instead
12/8/2015 12:00:00 AM, 12/10/2015 12:00:00 AM, 10000
dotnet fiddle
Below is the code
using System;
using System.Collections.Generic;
public class Test
{
public static void Main()
{
var transactions = new List<Transaction>()
{
new Transaction() {Date = new DateTime(2015, 12, 04), Balance = -4000}, // Open with negative 4000
new Transaction() {Date = new DateTime(2015, 12, 05), Balance = 2000}, // Settled 6000, so balance 2000
new Transaction() {Date = new DateTime(2015, 12, 06), Balance = 0}, // Spent 2000
new Transaction() {Date = new DateTime(2015, 12, 07), Balance = 2000}, // Deposited 2000
new Transaction() {Date = new DateTime(2015, 12, 08), Balance = -5000}, // Spent 7000
new Transaction() {Date = new DateTime(2015, 12, 09), Balance = 0}, // Deposited 5000
new Transaction() {Date = new DateTime(2015, 12, 10), Balance = 5000}, // Deposited 5000
new Transaction() {Date = new DateTime(2015, 12, 11), Balance = 1000}, // Spent 4000
new Transaction() {Date = new DateTime(2015, 12, 12), Balance = 6000}, // Deposited 5000
};
var (start, end, biggestAmountChangePositive) = GetBiggestBalanceChangeInPositive(transactions);
Console.WriteLine(start); //2015, 12, 08
Console.WriteLine(end); //2015, 12, 10
Console.WriteLine(biggestAmountChangePositive); //10000
}
public static (DateTime? start, DateTime? end, decimal biggestAmountChangePositive) GetBiggestBalanceChangeInPositive(List<Transaction> transactions)
{
decimal biggestAmountChangePositive = 0;
DateTime? startDate = null;
DateTime? endDate = null;
for (var i = 1; i < transactions.Count; i++)
{
if (transactions[i].Balance > transactions[i - 1].Balance)
{
var change = Math.Abs(transactions[i - 1].Balance - transactions[i].Balance);
biggestAmountChangePositive = biggestAmountChangePositive + change;
startDate = transactions[i - 1].Date;
endDate = transactions[i].Date;
}
}
return (startDate, endDate, biggestAmountChangePositive);
}
}
public class Transaction
{
public DateTime Date { get; set; }
public decimal Balance { get; set; }
}
I made what you asked.
I created one method and one class to remove some code.
public static (DateTime? start, DateTime? end, decimal highestPositiveBalanceChange) GetBiggestBalanceChangeInPositive(List<Transaction> transactions)
{
DateTime? startDate = null;
DateTime? endDate = null;
var highestPositiveBalanceChange = decimal.MinValue;
DateTime? tempStart = transactions[0].Date;
DateTime? tempEnd = transactions[0].Date;
var tempLast = transactions[0].Balance;
decimal tempHighestPositiveBalanceChange = 0;
for (var index = 1; index < transactions.Count; index++)
{
var transaction = transactions[index];
if (transaction.Balance >= tempLast)
{
tempHighestPositiveBalanceChange += transaction.Balance - tempLast;
tempLast = transaction.Balance;
tempEnd = transaction.Date;
}
else
{
if (tempHighestPositiveBalanceChange > highestPositiveBalanceChange)
{
highestPositiveBalanceChange = tempHighestPositiveBalanceChange;
startDate = tempStart;
endDate = tempEnd;
}
tempStart = transaction.Date;
tempEnd = transaction.Date;
tempLast = transaction.Balance;
tempHighestPositiveBalanceChange = 0;
}
}
if (tempHighestPositiveBalanceChange > highestPositiveBalanceChange)
{
highestPositiveBalanceChange = tempHighestPositiveBalanceChange;
startDate = tempStart;
endDate = tempEnd;
}
return highestPositiveBalanceChange == 0 ? (null, null, 0) : (startDate, endDate, highestPositiveBalanceChange);
}
With this, you will get the 10000 that you wanted

Migrating to iCal.net Period - Matches Date Only

I'm trying to simulate the MatchesDateOnly for my recurrence rule "ExcludeDates" which I'm storing as date only values. I want my RRULE to ignore certain date only periods.
Previously in DDay we could specify:
if (!recurrenceOptions.ExcludeDates.IsNullOrEmpty ())
{
var periodList = new PeriodList ();
recurrenceOptions.ExcludeDates.ForEach (d =>
{
var period = new Period (new iCalDateTime (d), TimeSpan.FromDays (1)) { MatchesDateOnly = true };
periodList.Add (period);
});
iCalEvent.ExceptionDates.Add (periodList);
}
var occurences = iCalEvent.GetOccurrences (range.StartDate, range.EndDate);
How can I mimic this functionality in iCal.net?
You can try with RecurrencePatternEvaluator
var vEvent = new Event {
DtStart = new CalDateTime(newDateTime(2017, 3, 1, 9, 0, 0)),
DtEnd = new CalDateTime(newDateTime(2017, 3, 1, 10, 0, 0))
};
var recurrenceRule = new RecurrencePattern(FrequencyDayType.Weekly, 1) {
ByDay = new IList<IWeekday> { new WeekDay(DayOfWeek.Thursday) }
};
var recurrenceEvaluator = new RecurrencePatternEvaluator(recurrenceRule);
var searchStart = new DateTime(2017, 3, 1, 0, 0, 0);
var searchEnd = new DateTime(2017, 3, 17, 0, 0, 0);
var correctOccurrences = recurrenceEvaluator.Evaluate(vEvent.DtStart, searchStart, searchEnd, false);

Removing Overlapping Dates in LINQ

I have a really ghetto implementation of this, but I imagine there is some clean way to do this with Linq. I have a list of objects with a start and stop date.
class Thing
{
int ID;
...
DateTime? StartDate;
DateTime EndDate;
}
In some cases there is no start date, its null. What I want the algorithm to do is remove items from the list until there is no overlapping of dates; I'm just using years to illustrate the concept:
List<Thing> x = new List<Thing>()
{
{1, 2012, 2014}
{2, 2013, 2015}
{3, 2014, 2016}
{4, <null>, 2015}
{5, 2016, 2017}
}
Running this list through the algo should yield:
var y = Do(x);
y =
{
{1, 2012, 2014}
{2, <null>, 2015}
{3, 2016, 2017}
}
There is the possibilities for cycles where, where there are different optimal solutions. My data doesn't have these edge cases.
I think this should work for you:
List<Thing> x = new List<Thing>()
{
new Thing () { ID = 1, StartDate = new DateTime(2012, 1, 1), EndDate = new DateTime(2014, 1, 1) },
new Thing () { ID = 2, StartDate = new DateTime(2013, 1, 1), EndDate = new DateTime(2015, 1, 1) },
new Thing () { ID = 3, StartDate = new DateTime(2014, 1, 1), EndDate = new DateTime(2016, 1, 1) },
new Thing () { ID = 4, StartDate = null, EndDate = new DateTime(2015, 1, 1) },
new Thing () { ID = 5, StartDate = new DateTime(2016, 1, 1), EndDate = new DateTime(2017, 1, 1) },
};
Func<Thing, Thing, bool> overlaps =
(t0, t1) =>
(t0.StartDate.HasValue ? t0.StartDate.Value <= t1.EndDate : false)
&& (t1.StartDate.HasValue ? t0.EndDate >= t1.StartDate : false);
var y = x.Skip(1).Aggregate(x.Take(1).ToList(), (a, b) =>
{
if (a.All(c => !overlaps(b, c)))
{
a.Add(b);
}
return a;
});
This gives me:
If your class Thing implements IEquatable<Thing> and you properly fill out the Equals, and GetHashCode methods you can then just do
var results = x.Distinct();

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