Recursion Help required with C# - c#

On our windows application, We have startDate and EndDate. On click of Execute button event, we need to call a third party web service with our search string + daterange( date from 01/01/2010 to 12/31/2010). Now our search criteria can return us thousands of records but web service have limitation of able to return only 10K records per transaction.
Which required us to break down our dateRange. So basically we need following;
For (X dateRange if RecordCount > 10000) then
X dateRange/2 which will be 01/01/2010 to 06/01/2010 in our case and check condition again and do this recursively until we get daterange block where RecordCount is < 10000.
Then start with Next date, for example, if we get 9999 records for 01/01/2010 to 03/30/2010 then we need to get records for next block starting 04/01/2010
Is this possible with Recursion?
RecursionFunction(dtStart, dtEnd)
{
if (WebService.RecordCount > 9999)
{
TimeSpan timeSpan = dtEnd.Subtract(dtStart);
DateTime mStart = dtStart;
DateTime mEnd = dtStart.AddDays(timeSpan.Days / 2);
RecursionFunction(dtStart,dtEnd);
}
else
{
Get Records here
}
}
But with above code, recursion will have following blocks
01/01/2010, 12/31/2010 > 10000
01/01/2010, 07/03/2010 > 10000
01/01/2010, 04/02/2010 < 10000
So after finishing getting record, recursion will start again with block 01/01/2010,07/03/2010 which we don't need. We need to start next recursion with 04/03/2010,12/31/2010
Thanks in advance for help.

It looks like you are trying to split the input range until it is small enough to handle. Try calling it for both ranges:
RecursionFunction(mStart, mEnd);
RecursionFunction(mEnd.AddDays(1), dtEnd);

The first step is to change the RecursionFunction call (at line 8 of your example) to:
RecursionFunction(mStart, mEnd);
But, then, you'll also need to call it again with the other half of the date range.
RecursionFunction(mEnd + AddDays(1), dtEnd);
Also, you need to handle the results (presumably combining the two answers).
var set1 = RecurseFunction(...);
var set2 = RecurseFunction(...);
return set1.Concat(set2);

This is like divide and conquer. You need to get results from the left and the right of the split and combine them and return that value. So you can keep getting smaller until you have enough data you can deal with and just return that. Then keep joining the result sets together.
public IList<Data> GetRecords(DateTime start, DateTime end)
{
var RecordCount = WebService.RecordCount(start, end);
if (RecordCount < 10000) return WebService.GetRecords(start, end);
DateTime l, m, e;
l = start;
e = end;
var midDay = end.Subtract(start).TotalDays / 2;
m = start.AddDays(midDay);
var left = GetRecords(l, m);
var right = GetRecords(m.AddDays(1), e);
return left.Concat(right);
}

This is how I would do it
static List<string> RecursiveGet(DateTime StartDate, DateTime EndDate, List<string> Output)
{
if (Webservice.RecordCount > 9999)
{
TimeSpan T = EndDate.Subtract(StartDate);
T = new TimeSpan((long)(T.Ticks / 2));
DateTime MidDate = StartDate.Add(T);
Output.AddRange(RecursiveGet(StartDate, MidDate, Output));
Output.AddRange(RecursiveGet(MidDate.AddMilliseconds(1), EndDate, Output));
}
else
{
//Get Records here, return them in array
Output.Add("Test");
}
return Output;
}
static List<string> GetRecords(DateTime StartDate, DateTime EndDate)
{
return RecursiveGet (StartDate, EndDate, new List<string>());
}
Note, Couldn't test it
It works by dividing the dates in half, then searching each of them, and if one is still bigger than 9999, then doing it again.

An easy way would be a form of pagination. If your using JSON or XML, you can put the amount of total results and just return a set number of results (return the offset too). This way you can do a loop to check if your on the last page and after you get the last results page, break out of it.
Don't forget to put checks in if a particular transaction fails though. It's not an ideal solution on such a large dataset but it is a workaround

It sounds much easier to just reuse the last date for the data you actually got back in a while-loop than to home in with recursion like this.
Then start with Next date, for example, if we get 9999 records for 01/01/2010 to 03/30/2010 then we need to get records for next block starting 04/01/2010
March has 31 days.
Pseudo-C# code
var dtStart = DateTime.Parse("2010-01-01");
var dtEnd = DateTime.Parse("2010-12-31");
var totalRecords = new List<RecordType>();
var records = WebService.Get(dtStart, dtEnd);
totalRecords.Add(records);
while (dtStart < dtEnd && records.Count > 9999)
{
dtStart=records.Last().Date;
records = WebService.Get(dtStart, dtEnd);
totalRecords.Add(records);
}
To ease the load on the service you could calculate the timespan for the previous run and only get that many days for the next run in the while-loop.
How you should handle the inevitable doublets depends on the data on the records.
I just realized I presumed you had a date in the returned data. If not, then disregard this answer.

Related

How to find given Event datetime is past event or Future event while considering hours aslo

I have Events page, In that displaying past present and future events counts and list. Here I'm facing a issue form QA team.
Example:
**Event date** is : 2022-01-20 08:00:00.000
**Current datetime** is: 2022-01-20 10:00:00.000
now based on above dates , we need to display event as past event because of two hours less than the Current datetime
if (planEvents.Count > 0)
{
switch (statusID)
{
case (int)GenericEnum.EventStatus.TodaysEvents:
events = planEvents.Where(o => o.EventDate.Date == DateTime.Today.Date).Skip(startIndex - 1).Take(pageSize).ToList();
totalCount = planEvents.Where(o => o.EventDate.Date == DateTime.Today.Date).ToList().Count();
break;
}
}
I tried above code but it's only returning date matching records but not hours comparison. can any once please help me.
First off, you should probably use UTC for all times, but that's a different issue. I don't really understand the use case you're after, but you can use the current time and add or subtract time units to create a range start and end. Not tested, but you should have something like this to select from a date range:
var start = DateTime.Now.AddHours(-2); // start from 2 hours ago
var end = DateTime.Now.AddHours(2); // end 2 hours from now
events = planEvents.Where(x => x.EventDate > start && x.EventDate < end);
Considering present events are the one falls within the current hour. Not tested, but it should work
if (planEvents.Any()) //Use Any method to improve performance
{
switch (statusID)
{
case (int)GenericEnum.EventStatus.TodaysEvents:
var todaysEvents = planEvents.Where(o => o.EventDate.Date == DateTime.Today.Date);
var todaysTotalEvents = todaysEvents.Count();
var currentHour = DateTime.Now.Hour;
var pastEvents = todaysEvents.Where(e=> e.EventDate.Hour < currentHour);
var presentEvents = todaysEvents.Where(e=> e.EventDate.Hour == currentHour);
var futureEvents = todaysEvents.Where(e=> e.EventDate.Hour > currentHour);
break;
}
}

How to get the last count, or the last time in a day

I have a problem finding the last time in the day (the time it's the biggest) picture below, how can I get that time?
I have to compare this time with his shift, but when I do it, I always read for the first time.
This is my code:
foreach (var shift in shifts)
{
if (von.ZPZ_Von <= shift.Arbeitsbeginn.AddMinutes(-20) &&
bis.ZPZ_Bis >= shift.Arbetsende.AddMinutes(-10))
return null;
else if (von.ZPZ_Von >= shift.Arbeitsbeginn.AddMinutes(20) &&
bis.ZPZ_Bis >= shift.Arbetsende.AddMinutes(10))
return null;
else if (von.ZPZ_Von <= shift.Arbeitsbeginn.AddMinutes(5)
&& bis.ZPZ_Bis <= shift.Arbetsende.AddMinutes(10)
)
return shift;
}
It is a method that finds the shift of workers, and if in the correct shift the worker returns the shift, if the worker comes 20 minutes or works more than 10 minutes then returns null.
This looks like data for one day:
So I need to compare the ZPZ_Bis with the last, or rather, the time.
At the moment, my method always compares ZPZ_Bis with the first departure time, i. 1899-12-30 09:52:00.000 in this case.
I would be grateful if somebody could help me with this problem, I have not really known how to handle this in the last few days.
this is my whole method:
private A_Arbeitszeitplan DetectShift(List<A_Arbeitszeitplan> shifts, PRAESENZZEIT von, PRAESENZZEIT bis, List<PRAESENZZEIT>arrivals)
If you only wish to use the TimeSpan of your DateTime you can get it like so:
From a DateTime, you can use .TimeOfDay - but that gives you a
TimeSpan representing the time into the day (10 hours).
Of course you need to compare TimeSpans with eachother:
if (von.ZPZ_Von.TimeOfDay <= shift.Arbeitsbeginn.AddMinutes(-20).TimeOfDay &&
bis.ZPZ_Bis.TimeOfDay >= shift.Arbetsende.AddMinutes(-10).TimeOfDay)
return null;

How do I get the closest DateTime from a List<DateTime>

Say I have the most recent DateTime and a List of all the possible dates. How would I efficiently go about finding the closest date time to last year's date in the list?
Say my list is comprised of the following:
2014-03-07
2014-03-14
2014-03-21
2014-03-28
...
2015-03-06
2015-03-13
2015-03-20
My most recent date is 2015-03-20, but I want to retrieve last year's date, 2014-03-21.
This is what I have currently, but it won't work if last year's date is one day off (eg; my time periods are stored weekly).
public DateTime LastYearDate()
{
List<DateTime> times = GetAllDates();
times.Sort();
times.Reverse();
DateTime currentDate = times.First();
return times.Where(dt => dt == currentDate.AddYears(-1)).First();
}
I'm not sure what I would use to recursively calculate the closest date, so if you have any ideas of what direction I should take (reference to any Linq functions to check out), that would be appreciated.
Just order by the difference between the date in the list and the date you're looking for:
var dateToFind = currentDate.AddYears(-1);
times.OrderBy(t => (t - dateToFind).Duration).FirstOrDefault();
(The difference between two date is an instance of TimeSpan; the Duration property returns the absolute value)
As it's sorted, you can use a binary search to try to find an exact match. If List<T>.BinarySearch returns a non-negative number, you know you've found an exact match. Otherwise, you can apply the bitwise complement operator to find the index that the value would be inserted at. You then need to check whether the value before or at that index is further from the target. So something like this:
var target = currentDate.AddYears(-1);
List<DateTime> times = GetAllDates();
if (times.Count == 0)
{
// ??? Work out what you want to do here, e.g. throw an exception
}
times.Sort();
var index = times.BinarySearch(target);
if (index >= 0)
{
return times[index];
}
int insertIndex = ~index;
// Handle boundary cases
if (insertIndex == 0)
{
return times[0];
}
if (insertIndex == times.Count)
{
return times[insertIndex - 1];
}
// Okay, two options - find the closest
var timeBefore = times[insertIndex - 1];
var timeAfter = times[insertIndex];
// TODO: Work out what you want to do if they're equidistant.
return target - timeBefore > timeAfter - target ? timeAfter : timeBefore;
Having said that, spender's comment on Thomas Levesque's answer gives a very simple solution:
var target = currentDate.AddYears(-1);
List<DateTime> times = GetAllDates();
if (times.Count == 0)
{
// ??? Work out what you want to do here, e.g. throw an exception
}
return times.OrderBy(t => (target - t).Duration).First();
Note that TimeSpan.Duration is always non-negative; it's like Math.Abs but for TimeSpan values.

In C#, What is the best way to aggregate and transform one DateTime collection into another?

I have a little calendar tool in C# and I am trying to figure out how to do a conversion from one array of DateTime objects to another. Here are the details:
I start off with collection of DateTime object
IEnumerable<DateTime> slots = GetSlots();
where each DateTime represents that starting time of an available slot (think open slot in calendar) All slots are for 30 minutes This is a given. So for example:
var slots = new List<DateTime>()
slots.Add(DateTime.Today + new TimeSpan(5,00, 0));
slots.Add(DateTime.Today + new TimeSpan(9,00, 0));
slots.Add(DateTime.Today + new TimeSpan(9,30, 0));
slots.Add(DateTime.Today + new TimeSpan(10,00, 0));
slots.Add(DateTime.Today + new TimeSpan(10,30, 0));
slots.Add(DateTime.Today + new TimeSpan(11,00, 0));
slots.Add(DateTime.Today + new TimeSpan(16,30, 0));
in the above example, it means i am free:
From 5:00 - 5:30
From 9:00 - 9:30
From 9:30 - 10:00
From 10:00 - 10:30
From 10:30 - 11:00
From 11:00 - 11:30
From 4:30 - 5:00
because i take the time from the item in the collection as the start time and simply add 30 minutes to it and that is considered a free slot.
I now have the requirement to take a larger time window (lets use 2 hours) and find out how many 2 hour slots free i have so I now need to take this array of dates and "merge" into into bigger buckets. Given the bigger bucket is 2 hours (120 minutes), I want a function like this
IEnumerable<DateTime> aggregateArray = MergeIntoLargerSlots(slots, 120);
I would basically have to loop through the slots array above and "merge" items that are lined up next to each out to make bigger buckets. If any of the merged items is 2 hours long then that should show up as an entry in the resulting array. Using the example above the resulting aggregateArray would have 2 items in the collection it that would have the times:
9AM (because i have a free slot from 9-11 AM (120 mins).
9:30AM (because i have a free slot from 9:30-11:30 AM (120 mins).
NOTE: 30 minutes "chunks" are the smallest interval so DON'T need to include 9:05 to 11:05 as an example
So given the previous array I have two 2 hour windows of time free in the day
I am struggling to figure out how this MergeIntoLargerSlots function would work so i would hoping to get some suggestion for how to approach this problem.
This only works for half hour intervals, you can figure out to make it work for others if you need to.
public List<DateTime> MergeIntoLargerSlots(List<DateTime> slots, int minutes)
{
int count = minutes/30;
List<DateTime> retVal = new List<DateTime>();
foreach (DateTime slot in slots)
{
DateTime end = slot.AddMinutes(minutes);
if (slots.Where(x => x >= slot && x < end).Count() == count)
{
retVal.Add(slot);
}
}
return retVal;
}
Here's a brief explanation of my problem solving approach; I take in the minutes and the slots list. I add minutes to get an end time which gives me range. From there, I use the Where operator to produce and IEnumerable<DateTime> from slots that has the slots in that range. I compare the result to the count variable I got from doing minutes/slotLength if the numbers match then you have the necessary slots. With your sample data the result of the Where for 9 AM would have 4 values in it; 9, 9:30, 10 and 10:30, ofc the count is 4, 120/30 == 4, so that gets added to retVal. The same would be true for 9:30, no other times would be returned.
Evan beat me to and did it with one less loop, but here was my solution:
private List<DateTime> MergeArray(List<DateTime> slots, int minutes)
{
var segments = minutes / InitialSegment;
var validSegments = new List<DateTime>();
foreach (var slot in slots.OrderBy(x => x))
{
var validSegment = true;
for (var i = 0; i < segments-1; i++)
{
var next = slot.AddMinutes(InitialSegment * (i + 1));
if (slots.All(x => x != next))
{
validSegment = false;
break;
}
}
if (validSegment)
validSegments.Add(slot);
}
return validSegments;
}
Assuming that your original list is sorted (if it is not, make it so it is), you could loop through your original list and check whether adjacent items are consecutive (i.e. whether the start times have a distance of exactly 30 minutes). Always keep track of the first item in the current series of consecutive timeslots - once you reach four of them (with 4 consecutive 30 minutes timeslots adding up to a possible two-hour timeslot; other timeslot sizes obviously require different factors), save a new two-hour timeslot into your resulting list and update your reference to the beginning of the current series of consecutive items.
Untested, so please consider this as pseudocode:
var twoHourSlots = new List<DateTime>();
int consecutiveSlotsCount = 0;
DateTime? previousSlot;
foreach (DateTime smallSlotStart in slots) {
if (previousSlot.HasValue) {
if (smallSlotStart - previousSlot.Value == new TimeSpan(0, 30, 0)) {
consecutiveSlotsCount++;
} else {
consecutiveSlotsCount = 0;
}
}
if (consecutiveSlotsCount == 4) {
twoHourSlots.Add(smallSlotStart - new TimeSpan(1, 30, 0));
consecutiveSlots = 0;
previousSlot = null;
} else {
previousSlot = smallSlotStart;
}
}
Some things to note:
I am using arithmetic operators on DateTime values. Check the docs to find out more; they do handy things and often let you work with TimeSpan values automatically.
I am using a TimeSpan constructor that takes hours, minutes and seconds several times. So that's what the three numbers mean.
I have declared previousSlot, a variable that keeps track of the last slot looked at (to compare to the current one), as DateTime? (again, check the docs if you are not sure what a nullable type is). That is because in the first iteration of the foreach loop, there is no previous slot to look at and the loop has to behave differently.
Likewise, previousSlot is set to null when we have found a 2-hour slot, as the last 30-minute slot of the found 2-hour slot should not be counted to the next possible 2-hour slot.
Once four consecutive 30-minute slots have been found, one hour and thirty minutes are subtracted from the beginning of the last one. That is because the thirty minutes after the beginning of the last 30-minute slot will be part of the resulting 2-hour slot.
I would create a TimeInterval class since there are a lot of other interesting things you can do with it.
public sealed class TimeInterval
{
public DateTime Start { get; private set; }
public DateTime End { get { return Start.AddMinutes(Duration); } }
public double Duration { get; private set; }
public TimeInterval(DateTime start, int duration)
{
Start = start;
Duration = duration;
}
public IEnumerable<TimeInterval> Merge(TimeInterval that)
{
if(that.Start >= this.Start && that.Start <= this.End)
{
if(that.End > this.End)
Duration += (that.Duration - (this.End - that.Start).TotalMinutes);
yield return this;
}
else
{
yield return this;
yield return that;
}
}
}
And this is an O(n) merge algorithm that will work for intervals of arbitrary sizes (in minutes).
//the `spans` parameter must be presorted
public IEnumerable<TimeInterval> Merge(IEnumerable<TimeInterval> spans, int duration)
{
var stack = new Stack<TimeInterval>();
stack.Push(spans.First());
foreach (var span in spans.Skip(1))
foreach(var interval in stack.Pop().Merge(span)) //this enumeration is guaranteed to have either one element or two elements.
stack.Push(interval);
return from interval in stack where interval.Duration >= duration select interval;
}

C# start with certain value in foreach

Is there a way to start with a certain value in the foreach? In specific I would like to start with 7:30AM.
DateTime start = DateTime.Today;
var clock = from offset in Enumerable.Range(0, 48)
select start.AddMinutes(30 * offset);
foreach (DateTime time in clock)
{
string timeValue = time.ToString("hh:mmtt");
}
In short, no. Just make your Enumerable.Range cover the right range (might want to double-check):
var clock = from offset in Enumerable.Range(15, 48)
select start.AddMinutes(30 * offset);
Edit
A cleaner solution might be something like:
DateTime start = // set start
DateTime end = // set end
while (start <= end) {
// do stuff here
start = start.AddMinutes(30);
}
It is not possible to "start" at a different value in the collection with the foreach syntax directly (wrapping is an option and the following example could even be considered a form of wrapping -- LINQ generation is often lazy), but it is possible to iterate over an appropriately modified collection and obtain the desired semantics.
Enumerable.Range returns something IEnumerable<T> -- you can always Where-limit or Skip-through it (e.g., see the IEnumerable/LINQ (extension) methods) and then use the resultant as the "for each" collection.
For instance (this relies on the fact that time is increasing and somewhat "inefficient", but chances of it even mattering are close to nil in most applications):
foreach (DateTime time in clock.Where(t => t >= someStartTime)) {
string timeValue = time.ToString("hh:mmtt");
}
Another solution based on ide's comment (which is, in my mind, cleaner than the above -- it also has to evaluate the conditional less):
foreach (DateTime time in clock.SkipWhile(t => t < someStartTime)) {
...
}
Note that these IEnumerable results are lazily evaluated streams.
However, some of the other answers provide nice alternative solutions and different ways of approaching the problem. I would only use the above if the initial sequence could not be altered and/or was used for different purposes requiring the original data.
Happy coding.
You can set your start to 7:30AM today, like this:
DateTime start = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 7, 30, 0);
Just replace this, It'll start with 7:30 AM.
var clock = from offset in Enumerable.Range(15, 48)
Good luck!
DateTime start = DateTime.Today.Add(new TimeSpan(7, 30, 0));
var clock = from offset in Enumerable.Range(0, 48)
select start.AddMinutes(30 * offset);
foreach (DateTime time in clock)
{
string timeValue = time.ToString("hh:mmtt");
}
So, to start from 7:30 today, the key line is the first one:
DateTime start = DateTime.Today.Add(new TimeSpan(7, 30, 0));
It would be clearest is you set the time specifically and add to that. By simply changing the starting point of the enumeration, you'll get the desired starting times but it is unclear what the value is by inspection.
var start = DateTime.Today.Add(new TimeSpan(7, 30, 0));
var times = Enumerable.Range(0, 48)
.Select(offset => start.AddMinutes(30 * offset));
foreach (var time in times)
{
string timeValue = time.ToString("hh:mmtt");
// ...
}

Categories

Resources