LINQ Where clause inline for loop? - c#

I have database values called start and length.
start is the start time of a booking (1->09:00, 2->10:00 etc) and length is the length in hours.
i then have an array of start times and end times. I want to be able to check whether each start and end pair are already booked. I so far have it figured that if the start times are the same, it is booked, or if the end times are the same, it is also booked. But if the start and end time are inbetween the comparison times, it will return not booked, which is false.
I am trying to write a LINQ query to test whether a booking is already in the database. So far I have
var proposedRequest = db.requests.Include(r => r.rooms);
proposedRequest = proposedRequest.Where(r => r.booked.Equals(1));
proposedRequest = proposedRequest.Where(r => r.roundID.Equals(roundID));
proposedRequest = proposedRequest.Where(r => r.day.Equals(day));
int[] startTimes = new int[length];
int[] endTimes = new int[length];
for(var q=0;q<length;q++)
{
startTimes[q] = time + q;
endTimes[q] = time + q + 1;
}
proposedRequest = proposedRequest.Where(s => startTimes.Contains(s.start) || endTimes.Contains(s.start+s.length));
Now, this only works for if the new booking starts at the same time as the booking already in the DB, or if it ends at the same time. This doesn't look at the following case
there is a records in the db where start -> 2 and length ->3.
so this booking runs from 10:00->13:00.
but say I am checking this against an entry that starts at 11:00 and ends at 12. It would not come back as booked already because the start and end times do not match.
What is the best way to solve this?
the only way i could see fit is to loop through my startTime and endTime arrays and have another clause for each pair that would produce something like the following:
.Where((s => s.startTime<startTime[i] && (s.startTime + s.Length) > endTime[i]) || (s => s.startTime<startTime[i+1] && (s.startTime + s.Length) > endTime[i+1]))
but i dont think this is possible.

Based on this answer, two ranges overlap if (StartA <= EndB) and (EndA >= StartB)
In your case:
StartA = s.start
EndA = s.start + s.length
StartB = time
EndB = time + length
So your last condition should be like this:
proposedRequest = proposedRequest.Where(s => s.start <= time + length &&
s.start + s.length >= time);

This will return objects that have your StartTime, EndTime, and a boolean that signifies if it booked already.
var proposedRequest = db.requests
.Include(r => r.rooms)
.Where(r => r.booked.Equals(1))
.Where(r => r.roundID.Equals(roundID))
.Where(r => r.day.Equals(day))
.ToList();
//int[] startTimes = new int[length];
//int[] endTimes = new int[length];
//for(var q=0;q<length;q++)
//{
// startTimes[q] = time + q;
// endTimes[q] = time + q + 1;
//}
var times=Enumerable
.Range(time,length)
.Select(r=>
new {
StartTime=r,
EndTime=r+1,
Booked=proposedRequest.Any(pr=>pr.StartTime<=r && pr.StartTime+pr.Length>r)
}).ToList();

Related

How to execute while loop given number of times?

I have this while loop to get next working day excluding holidays and sundays.
But it calculates by adding 1 day. And i want that number of day to be given by the user. I get that input from the below TextBox (TboxIzin).
How can execute that while loop to do the calculation for given number of times?
int i = 1;
int sayi;
IzinIslem i1 = new IzinIslem();
int.TryParse(i1.TboxIzin.Text, out sayi);
public static DateTime GetNextWeekDay(DateTime date,
IList<Holiday> holidays, IList<DayOfWeek> weekendDays)
{
int i = 1;
int sayi;
IzinIslem i1 = new IzinIslem();
int.TryParse(i1.TboxIzin.Text, out sayi);
// always start with tomorrow, and truncate time to be safe
date = date.Date.AddDays(1);
// calculate holidays for both this year and next year
var holidayDates = holidays.Select(x => x.GetDate(date.Year))
.Union(holidays.Select(x => x.GetDate(date.Year + 1)))
.Where(x => x != null)
.Select(x => x.Value)
.OrderBy(x => x).ToArray();
// increment to get working day
while (true)
{
if (weekendDays.Contains(date.DayOfWeek) ||
holidayDates.Contains(date))
date = date.AddDays(1);
else
return date;
}
}
I get not all code paths return a value
error when i try nesting while loops.
while is a conditional loop. Here you put a non-condition in the clause and immediately follow up with a condition. Put the condition in the while clause:
while(weekendDays.Contains(date.DayOfWeek) || holidayDates.Contains(date)) {
date = date.AddDays(1);
}
return date;
The actual reason you're getting the error is that the compiler cannot predict if your if condition will ever resolve to false. If it doesn't, then your function never returns.
With the modified while loop, that may still happen, but it will result in an infinite loop, and the compiler is fine if you shoot yourself in the foot that way.
You can change your else clause to break out of the loop. And then return out of the loop.
while (true)
{
if (weekendDays.Contains(date.DayOfWeek) ||
holidayDates.Contains(date))
date = date.AddDays(1);
else
break;
}
return date;
Let's get rif of all UI in the GetNextWeekDay (like int.TryParse(i1.TboxIzin.Text, out sayi);):
public static DateTime GetNextWeekDay(DateTime date,
IEnumerable<Holiday> holidays,
IEnumerable<DayOfWeek> weekendDays) {
// public method arguments validation
if (null == holidays)
throw new ArgumentNullException(nameof(holidays));
else if (null == weekendDays)
throw new ArgumentNullException(nameof(weekendDays));
HashSet<DayOfWeek> wends = new HashSet<DayOfWeek>(weekendDays);
// Current Year and Next years - .AddYear(1)
HashSet<DateTime> hds = new HashSet<DateTime>(holidays
.Select(item => item.Date)
.Concate(holidays.Select(item => item.Date.AddYear(1))));
for (var day = date.Date.AddDays(1); ; day = day.AddDays(1))
if (!wends.Contains(day.DayOfWeek) && ! hds.Contains(day))
return day;
}
Or if you prefer Linq, the loop can be rewritten as
return Enumerable
.Range(1, 1000)
.Select(day => date.Date.AddDays(day))
.TakeWhile(item => item.Year - date.Year <= 1)
.First(item => !wends.Contains(item.DayOfWeek) && !hds.Contains(item));

Render a Calendar with Timeslots and Overlapping Appointments

I'm struggling to get my head round this one.
I have a List<Appointment> which contains DateTime Start and DateTime End along with a few others bits.
I want to draw a day planner which lists all of the appointments in order with placeholders/gaps where appropriate.
I have managed this, but now I need to handle the scenario where appointments overlap. It seems this could be;
appointment a starts before and ends after appointment b
appointment a starts before and ends during appointment b
appointment a starts during and end after appointment b
appointment a start during and ends during appointment b
This feels like a problem which must have been tackled before. Any pointers?
Here's an example of some horrendous code i'm currently working on;
List<TechActivityModel> techActivity = new TechService().GetTechActivity(7, new DateTime(2018, 4, 11), new DateTime(2018, 4, 11).AddDays(1))
.OrderBy(t => t.Start).ToList();
for (int i = techActivity.Count - 1; i >= 0; i--)
{
//something starts before and ends after
TechActivityModel clash = techActivity.Where(t => t.Id != techActivity[i].Id && t.Start < techActivity[i].Start && t.End > techActivity[i].End).FirstOrDefault();
while (clash != null)
{
//split the clashing activity into a task before and another task after the existing on
//first create the 2nd part of the task (after the existing one)
TechActivityModel activityContinued = new TechActivityModel()
{
Start = techActivity[i].End,
End = clash.End,
Subject = "++" + clash.Subject
};
activityContinued.Length = (int)(activityContinued.End - activityContinued.Start).TotalMinutes;
techActivity.Add(activityContinued);
//update the clashing task to finish when the existing task starts
clash.Subject = "+" + clash.Subject;
clash.End = techActivity[i].Start;
clash.Length = (int)(clash.End - clash.Start).TotalMinutes;
clash = techActivity.Where(t => t.Id != techActivity[i].Id && t.Start < techActivity[i].Start && t.End > techActivity[i].End).FirstOrDefault();
}
}
for (int i = techActivity.Count - 1; i >= 0; i--)
{
//something starts before and ends during
TechActivityModel clash = techActivity.Where(t => t.Id != techActivity[i].Id && t.Start <= techActivity[i].Start && t.End > techActivity[i].Start).FirstOrDefault();
while (clash != null)
{
//update the clashing task to finish when the existing task starts
clash.Subject = "/" + clash.Subject;
clash.End = techActivity[i].Start;
clash.Length = (int)(clash.End - clash.Start).TotalMinutes;
clash = techActivity.Where(t => t.Id != techActivity[i].Id && t.Start < techActivity[i].Start && t.End > techActivity[i].Start).FirstOrDefault();
}
}
techActivity = techActivity.OrderBy(t => t.Start).ToList();
//now we're going to pad all the gaps
List<TechActivityModel> newList = new List<TechActivityModel>();
DateTime LastEnd = techActivity[0].End;
//start with the gap from midnight till the first task
newList.Add(new TechActivityModel()
{
Start = new DateTime(2018, 4, 10),
End = techActivity[0].Start,
TicketNumber = 0,
Note = "",
TicketSubject = "",
TimeLogged = 0,
Id = 0
}
);
//pad all the gaps
for (int i = 1; i < techActivity.Count; i++)
{
if (LastEnd < techActivity[i].Start.AddMinutes(-2))
{
TechActivityModel gap = new TechActivityModel()
{
Start = LastEnd.AddMinutes(1),
End = techActivity[i].Start,
Subject = "",
Id = 0
};
gap.Length = (int)(gap.End - gap.Start).TotalMinutes;
newList.Add(gap);
}
LastEnd = techActivity[i].End;
}
//and finally fill the gap from the last task till midnight
newList.Add(new TechActivityModel()
{
Start = LastEnd,
End = new DateTime(2018, 4, 11),
Subject = "",
Length = 0
}
);
newList.AddRange(techActivity);
string content = "";
foreach (TechActivityModel techActivityModel in newList.OrderBy(t => t.Start))
{
content +=
techActivityModel.Start.ToShortTimeString()
+ " - " + techActivityModel.End.ToShortTimeString()
+ " (" + techActivityModel.Length + "mins)"
+ " : " + techActivityModel.Subject
+ Environment.NewLine;
}
Here's how I'd approach you problem, if I understand you correctly. I'd start by finding all of the distinct Start and End values, all together in a single list. We want them distinct and in order. Then we zip that list with itself to generate pairs:
var l = new List<Appointment>();
var ex = l.SelectMany(a => new[] { a.Start, a.End }).Distinct().OrderBy(dt => dt);
var pairs = ex.Zip(ex.Skip(1), (First, Second) => new { First, Second });
Now, using the rule I mentioned in the comments:
Two time periods overlap if period a starts before period b ends and period b starts before period a ends.
We work through each pair of datetimes and re-query our appointments list for all appointments that overlap the period described by the pair. If you get no results, this time period is currently free. If you get one result, you have (for this period) an appointment that's not overlapped by anything else. If you get multiple results, you have a clash and can work out how to display that. But note that you don't have to keep revisiting this time period and extending/shortening it, updating its text, etc.

No data return when performing a self join

I have a table contain the payment records of agencies. I want to sum total payment of each agency into 2 columns, first is current day payment and second is the day before payment.
So I try the SQL like this.
select p1.UserName, p1.PaymentAmount, p2.PaymentAmount
from vw_Agency_Payment p1
join vw_Agency_Payment p2 on p1.UserName=p2.UserName
where p1.PaymentDate = '2014-08-07'
and p2.PaymentDate = '2014-08-08'
It is successful and return the data.
But when I convert it to Linq like below:
var yesterday = DateTime.Today.AddDays(-1);
var tomorrow = DateTime.Today.AddDays(1);
var agencyPayment = from y in db2.vw_Agency_Payment
join t in db2.vw_Agency_Payment on y.UserName equals t.UserName
where y.PaymentDate >= yesterday
&& y.PaymentDate < DateTime.Today
&& t.PaymentDate >= DateTime.Today
&& t.PaymentDate < tomorrow
select new AgencyPaymentModel
{
agencyUserCode = y.UserName,
yesterdayPayment = y.PaymentAmount,
todayPayment = t.PaymentAmount,
growth = (t.PaymentAmount - y.PaymentAmount) / y.PaymentAmount * 100
};
return View(agencyPayment.OrderByDescending(c => c.growth).Take(100).ToList());
It return no data.
I don't know what make it wrong!?
Why not the following code (taking the date part of datetime field)?
var yesterday = DateTime.Today.AddDays(-1);
var agencyPayment = from y in db2.vw_Agency_Payment
join t in db2.vw_Agency_Payment on y.UserName equals t.UserName
where y.PaymentDate.Date = yesterday
&& t.PaymentDate.Date = DateTime.Today
select new AgencyPaymentModel
{
agencyUserCode = y.UserName,
yesterdayPayment = y.PaymentAmount,
todayPayment = t.PaymentAmount,
growth = (t.PaymentAmount - y.PaymentAmount) / y.PaymentAmount * 100
};
return View(agencyPayment.OrderByDescending(c => c.growth).Take(100).ToList());
where y.PaymentDate >= yesterday
&& y.PaymentDate < DateTime.Today
&& t.PaymentDate >= DateTime.Today
&& t.PaymentDate < tomorrow
No result will satisfy this condition:
from line 1-2, PaymentDate is limited to yesterday... intersect with line 3 will narrow down to nothing.
Basically you need to draw a reasonable range.
Plus, snippet 2 contains more logic than snippet 1, you should test them under same conditions.

Check whether a "Recurring Date" lies between two DateTime instances

We have a class called ABDate which contains among other things a Month, a nullable Year and a Day. When creating an instance of ABDate if the year is given as null, the date is considered recurring. There is an IsRecurring property as well.
Now we have a IList<ABDate>, and we are given a start and an end DateTime. How do we check to see whether any ABDate in the list lies in the start and end interval.
For the non-recurring case the solution is obvious. Given a collection abDates and start and end instances:
var anyInBetween = abDates
.Select(ab => new DateTime(ab.Year.Value, ab.Month, ab.Day))
.Any(d => d >= start && d <= end);
For the recurring case, we can observe that since no month has more than 31 days, a function such as f(month, day) = month * 100 + day will project each distinct (month, day) tuple into a unique integer. Comparing two such integers allows us to determine which of the source tuples represents a day earlier in the year:
// Note: m * 100 is "random". Multiplying by any factor > 31 will work as well.
Func<int, int, int> customTotalOrder = (m, d) => m * 100 + d;
var startIndex = customTotalOrder(start.Month, start.Day);
var endIndex = customTotalOrder(end.Month, end.Day);
var spanningYears = end.Year - start.Year;
var anyInBetween = false;
switch (spanningYears)
{
default:
case 2:
// start => end contains at least one whole year, so obviously...
anyInBetween = abDates.Any();
case 1:
var projection = abDates.Select(ab => customTotalOrder(ab.Month, ab.Day));
anyInBetween = startIndex < endIndex
? projection.Any(i => i >= startIndex && i <= endIndex)
: projection.Any(i => i >= startIndex || i <= endIndex);
case 0:
anyInBetween = abDates
.Select(ab => customTotalOrder(ab.Month, ab.Day))
.Any(i => i >= startIndex && i <= endIndex);
}
foreach (ABDate a in AbDateList)
{
for ( in checkYear = startDate.Year ;checkYear <= EndDate.Year; checkYear ++)
{
if ((new DateTime(checkYear ,a.Month,a.Day) >= startDate) && (new DateTime(checkYear ,a.Month,a.Day) <= endDate))
{
// I AM IN BETWEEN
}
}
}
}

LINQ Select Records X Minutes apart

I have a simple table that keeps track of the entry date. I would like to select records that are X minutes apart.
IMAGE_LOCATION IMAGE DATE
============== =============
2227.jpg 08/03/2014 22:27:47
2228.jpg 08/03/2014 22:28:48
2229.jpg 08/03/2014 22:59:49
2230.jpg 08/03/2014 23:12:50
2231.jpg 08/03/2014 23:29:49
From the sample above i would like the query to return items that are at least X minutes apart, lets say 30 min. so from the list above 2227.jpg, 2229.jpg and 2231.jpg would be returned only.
This is what i have so far that just returns the latest images, however i need the latest ones but separated by at least 30 minutes between records.
using (var db = new GibFrontierEntities())
{
var result = (from u in db.CCTV_IMAGES.OrderByDescending(u => u.ImageDate)
select u).Take(rows);
return result.ToList();
}
This is a quick attempt to achieve exactly what you asked for, a LINQ solution (tested and working in .NET 4):
var list = db.CCTV_IMAGES.OrderByDescending(u => u.ImageDate);
return list.Where((d, i) =>
{
//Look ahead to compare against the next if it exists.
if (list.ElementAtOrDefault(i + 1) != null)
{
return d.ImageDate.Subtract(list.ElementAtOrDefault(i + 1).ImageDate).TotalMinutes > 30;
}
//Look behind to compare against the previous if this is the last item in the list.
if (list.ElementAtOrDefault(i - 1) != null)
{
return list.ElementAtOrDefault(i - 1).ImageDate.Subtract(d.ImageDate).TotalMinutes > 30;
}
return false;
}).ToList();
Per comments and a clearer definition of the requirement:
Because you stated in the comments below that you will have 1 item a minute and you previously stated that you need them separated by at least 30 minutes, would you consider simplifying the logic to grab every 30th item from the list?
return list.Where((d, i) => i % 30 == 0);
You can use SelectMany to achieve what you want:
using (var db = new GibFrontierEntities())
{
var images = db.CCTV_IMAGES;
var result = images
.SelectMany(i => images,
(first, second) => new { First = first, Second = second })
.Where(i => i.First != i.Second)
.Where(i => Math.Abs(
EntityFunctions
.DiffMinutes(i.First.ImageDate, i.Second.ImageDate)) >= 30)
.Select(i => i.First)
.Distinct()
.OrderByDescending(i => i.ImageDate)
.Take(rows)
.ToList();
return result;
}
As mentioned already, this could be easily achievable through iteration. However, it you really have to deal with LINQ expression, here's a quick and dirty sample that will return dates that are 30 minutes apart:
List<DateTime> dateLlist = new List<DateTime>();
dateLlist.Add(new DateTime(2014, 1, 1, 1, 0, 0, 0));
dateLlist.Add(new DateTime(2014, 1, 1, 1, 10, 0, 0));
dateLlist.Add(new DateTime(2014, 1, 1, 1, 45, 0, 0));
DateTime previousTime = new DateTime();
bool shouldAdd = false;
List<DateTime> newList = dateLlist.Where(x =>
{
shouldAdd = (previousTime == DateTime.MinValue || previousTime.AddMinutes(30) < x);
previousTime = x;
return shouldAdd;
}).ToList();

Categories

Resources