What is the best way to make this code recursive - c#

I have to edit some code that has a proposedDate (as a DateTime object called minDate) and an array of blackout dates. Given a proposed Date, it tries to see if this is valid (NOT a blackout dates). If it is a blackout date, then keep checking the next day until you find a date that is not a valid checkout date. The existing code looks like this
if ( blackoutDates.Contains(minDate))
{
minDate = minDate.AddDays(1);
dateOffset = dateOffset + 1;
if ( blackoutDates.Contains(minDate))
{
minDate = minDate.AddDays(1);
dateOffset = dateOffset + 1;
if (blackoutDates.Contains(minDate))
{
minDate = minDate.AddDays(1);
dateOffset = dateOffset + 1;
}
}
}
Clearly there is a repeated pattern here and I am trying to figure out the best way to clean up this code and make it elegant.

No need for recursion. You can do this in a loop.
while(blackoutDates.Contains(minData)){
minData = minData.AddDays(1);
++dataOffset;
}
I don't know what language is this, but check if there is already a standard API for doing what you need first.

I wouldn't make it recursive. I would make it a while loop:
while(blackoutDates.Contains(minDate))
{
minDate = minDate.AddDays(1);
dateOffset = dateOffset + 1;
}
Recursion can express loops, but looping constructs are usually clearer when used in the context they are designed for. They also make it a bit simpler to reach data that is outside the scope of the loop than recursion does (specifically local variables).

Related

Constantly update Datetime variable in C# console application

I am trying to run a piece of code on user defined time but the datetime variable stores the value of time when i hit run and does not update to check if the time has changed.
static void Main(string[] args)
{
Console.WriteLine("please enter the date in m/d/y h/m/s am/pm format ");
DateTime bombDate = Convert.ToDateTime(Console.ReadLine());
repeat:
//DateTime bombDate = "7/27/2016 4:13:16 AM";
DateTime now = DateTime.Now;
if (bombDate == now)
{
string _str = " this is a virus, you have been hit by a logic bomb ...";
while (true)
{
foreach (char c in _str)
{
SendKeys.SendWait(c.ToString());
Thread.Sleep(200);
}
Thread.Sleep(3000);
}
}
else {
goto repeat;
}
You can't match the two dates exactly, you're comparing the ticks here and the chances the two dates execute exactly at the same time Now matches the date is not that high. Since you're only specifying seconds from input then use this:
if ((bombDate - now).TotalSeconds < 0)
This matches the two dates even if there is it passed by up to less that 1 second.
Or you can just check if the needed time has passed:
if ((bombDate <= now))
I would post this as a comment but I can't.
Depending on what you're trying to do it might be worth to consider to write the program and just use a standard facility like the windows task scheduler to actually run it on specific conditions/times.
In addition goto is frowned up on by a lot of people for a good reason. Essentially you're creating a loop. Why not declare it as such? One possibility would be to use a loop which just counts the time (which is essentially what you're doing in your outer loop).
DateTime now = NULL;
while(bombDate != now){
now = DateTime.Now;
}
// Other Code beginning with string _str =
On way to circumvent the timing problem would be to add another sleep after your initial assignment/before the loop. That way you would be sure that some time passed between the successive calls of DateTime.Now. Going for a finer resolution like user3185569 suggested might be better though.

Increment DateTime Variable

I'm trying to create a loop where my date increments by 1 month while it's in the for loop. Currently It's only displaying today's date. And is not incrementing. I want to change the display date to selected display date instead of today/default
for (int i = 1; i <= 15; i++)
{
DateTime initialdate = InitialDate.DisplayDate;
InitialDate.DisplayDate.AddMonths(1);
initialdate = InitialDate.DisplayDate;
}
I didn't show any of the initialdate being used because I don't think it's necessary.
InitialDate is a DateTimePicker
Initialization of the Datepicker
<DatePicker x:Name="InitialDate"></DatePicker>
Problem : You need to assign the return value of the InitialDate.DisplayDate.AddMonths(1).
From MSDN: DateTime.AddMonths()
Returns a new DateTime that adds the specified number of months to the
value of this instance.
Replace This:
InitialDate.DisplayDate.AddMonths(1);
With This:
InitialDate.DisplayDate = InitialDate.DisplayDate.AddMonths(1);
DateTime.AddMonths doesn't change the value you call it on - it returns a new value. This is true of all the DateTime methods. There's nothing which changes the value in place, which is a good job as it's a value type and changes would be lost anyway if they were made to a copy of the variable (e.g. due to being called on the value returned by a property).
You want:
InitialDate.DisplayDate = InitialDate.DisplayDate.AddMonths(1);
Assuming you use initialdate in the rest of the body of the loop, it would be clearer if you just declared it after the increment:
for (int i = 1; i <= 15; i++)
{
InitialDate.DisplayDate = InitialDate.DisplayDate.AddMonths(1);
DateTime initialDate = InitialDate.DisplayDate;
// Use initialDate here
}
(I've renamed the variable to have a capital D for the sake of convention.)
do this:
DateTime initialdate = InitialDate.DisplayDate.AddMonth(1);
You problem stems from the fact that InitialDate.DisplayDate.AddMonths(1); returns a new DateTime rather than modifying the instance it's being called on. You do nothing with the return value emitted by that call. Instead you want;
InitialDate.DisplayDate = InitialDate.DisplayDate.AddMonths(1);
Just FYI you will see this a lot in C#. I would say more often than not, methods that would change the values of an object return a new instance rather than mutating the instance they're called on.

How to perform a recursive search?

I have a Task class which can have sub tasks of the same type
public class Task
{
public DateTime Start { get; set;}
public DateTime Finish { get; set;}
public List<Task> Tasks {get; set;}
public DateTime FindTaskStartDate(Task task)
{}
}
How should i perform a recursive search (linq perhaps) to find the task with the earliest start date?
My initial approach involved too many for loops and it ended becoming a bit of a mess and quickly spiraling out of control. Here's my second attempt:
public DateTime FindTaskStartDate(Task task)
{
DateTime startDate = task.Start;
if(task.HasSubTasks())
{
foreach (var t in task.Tasks)
{
if (t.Start < startDate)
{
startDate = t.Start;
if (t.HasSubTasks())
{
//What next?
//FindTaskStartDate(t);
}
}
}
}
return startDate;
}
Any nicer solutions out there to solve this problem?
Thanks
Svick's solution is fine, but I thought I'd add a bit more general advice. It seems like you are new to writing recursive methods and were struggling a bit there. The easiest way to write a recursive method is to strictly follow a pattern:
Result M(Problem prob)
{
if (<problem can be solved easily>)
return <easy solution>;
// The problem cannot be solved easily.
Problem smaller1 = <reduce problem to smaller problem>
Result result1 = M(smaller1);
Problem smaller2 = <reduce problem to smaller problem>
Result result2 = M(smaller2);
...
Result finalResult = <combine all results of smaller problem to solve large problem>
return finalResult;
}
So suppose you want to solve the problem "what is the maximum depth of my binary tree?"
int Depth(Tree tree)
{
// Start with the trivial case. Is the tree empty?
if (tree.IsEmpty) return 0;
// The tree is not empty.
// Reduce the problem to two smaller problems and solve them:
int depthLeft = Depth(tree.Left);
int depthRight = Depth(tree.Right);
// Now combine the two solutions to solve the larger problem.
return Math.Max(depthLeft, depthRight) + 1;
}
You need three things to make recursion work:
The problem has to get smaller every time you recurse.
The problem has to eventually get so small that it can be solved without recursion
The problem has to be solvable by breaking it down into a series of smaller problems, solving each one, and combining the results.
If you cannot guarantee those three things then do not use a recursive solution.
You're right, recursion is the right approach here. Something like this should work:
public DateTime FindTaskStartDate(Task task)
{
DateTime startDate = task.Start;
foreach (var t in task.Tasks)
{
var subTaskDate = FindTaskStartDate(t);
if (subTaskDate < startDate)
startDate = subTaskDate;
}
return startDate;
}
I removed the check for task.HasSubTasks(), because it only makes the code more complicated without any added benefit.
If you find your often write code that needs to walk all of the tasks in the tree, you might want to make this more general. For example, you could have a method that returns IEnumerable<Task> that returns all the tasks in the tree. Finding the smallest start date would then be as easy as:
IterateSubTasks(task).Min(t => t.Start)
Separating iteration over tree from search may be beneficial if there are other tasks you want to do on all items. I.e. if you implement IEnumerable over the tree items you can use LINQ queries to search for anything you want or perform other operations on all tasks in you tree.
Check out Implementing IEnumerable on a tree structure for a way to do so.

System.OutOfMemoryException thrown when computing date ranges

It might be a simple fix, but I can't for the life of me think of how to do this. I compute a bunch of StartDates and End Dates into a bunch of arrays of dates using this query:
this.Reserved = unit.Reservations.Where(r => r.Active.HasValue && r.Active.Value).SelectMany(r => Utilities.DateRangeToArray(r.StartDate, r.EndDate)).ToArray();
Utilities.DateRangeToArray() is defined as follows:
public static IEnumerable<DateTime> DateRangeToArray(DateTime start, DateTime end) {
DateTime curDate = start;
while (curDate <= end) {
yield return curDate;
curDate.AddDays(1);
}
}
Is there a way to make this less memory intensive?
Thanks!
Your code is broken - AddDays doesn't change the existing value, it returns a new value. You're ignoring that new value, thus creating an infinite loop.
Change your code to:
public static IEnumerable<DateTime> DateRangeToArray(DateTime start,
DateTime end) {
DateTime curDate = start;
while (curDate <= end) {
yield return curDate;
curDate = curDate.AddDays(1);
}
}
Another hint: unit testing can help you find this sort of problem long before you try to use the method in a LINQ query. I'd also change the name, given that it's not returning an array.
You're sure you don't have any reservations where r.StartDate > r.EndDate, right? If you do, you'll get an infinite loop, I think.
I assume the out of memory is when converting the result to the array. Two points:
The output will contain duplicate dates for overlapping reservations.
Perhaps Reserved should be a collection of date ranges (start,end) rather than containing every date?

Checking if Var from LINQ query is Null and returning values older than x

Hello everyone I'm currently having 2 issues with the code below:
Upon return of result1 I'm trying to complete a check to see if it is != null and if it is't it will begin to delete the records selected. The issue is that even when result1 returns nothing and defaults the if statement doesn't pick this up so I guess I'm missing something but what?
I'm wishing to return only values which are over 10 mintues old (this will later be scaled to 12 hours) to do this I'm checking against a.DateTime which is a DateTime value stored in a database. However if i use the <= or >= operators it doesn't work so again what am I missing?
DateTime dateTime = DateTime.Now.Subtract(new TimeSpan(0, 0, 10, 0));
var result1 = (from a in cpuInfo
where a.DateTime <= dateTime
select a).DefaultIfEmpty(null);
if (result1 != null)
{
foreach (TblCPUInfo record1 in result1)
{
localDB.TblCPUInfo.DeleteOnSubmit(record1);
localDB.SubmitChanges();
}
}
Philippe has talked about the sequence side of things - although you don't even need the call to Any(). After all, if there are no changes the loop just won't do anything.
Do you really want to submit the changes on each iteration? It would probably make more sense to do this once at the end. Additionally, you can use DateTime.AddMinutes to make the initial "10 minutes ago" simpler, and if you're only filtering by a Where clause I'd use dot notation.
After all these changes (and making the variable names more useful), the code would look like this:
DateTime tenMinutesAgo = DateTime.Now.AddMinutes(-10);
var entriesToDelete = cpuInfo.Where(entry => entry.DateTime <= tenMinutesAgo);
foreach (var entry in entriesToDelete)
{
localDB.TblCPUInfo.DeleteOnSubmit(entry);
}
localDB.SubmitChanges();
Now, as for why <= isn't working for you... is it possible that you need the UTC time instead of the local time? For example:
DateTime tenMinutesAgo = DateTime.UtcNow.AddMinutes(-10);
If that still isn't working, I suggest you have a look at the generated query and play with it in a SQL tool (e.g. Enterprise Manager or SQL Server Management Studio) to work out why it's not returning any results.
DefaultIfEmpty will return a single item with the content you provided, so in your case a collection with a single value "null".
You should check for elements in the collection using the Any() extension method. In your case:
DateTime dateTime = DateTime.Now.Subtract(new TimeSpan(0, 0, 10, 0));
var result1 = from a in cpuInfo
where a.DateTime <= dateTime
select a;
if (result1.Any())
{
foreach (TblCPUInfo record1 in result1)
{
localDB.TblCPUInfo.DeleteOnSubmit(record1);
localDB.SubmitChanges();
}
}
But if this is really your code, you can skip the Any() check completely, because the foreach loop will not run if there are no elements in result1.

Categories

Resources