I'm looking for a LINQ query that will select only those objects whose date interval is not higher than 20 seconds. For example:
AuthenticationEssay[] essays = new AuthenticationEssay[] {
new AuthenticationEssay() { Date = DateTime.Now.AddSeconds(20), Success = false },
new AuthenticationEssay() { Date = DateTime.Now.AddSeconds(24), Success = false },
new AuthenticationEssay() { Date = DateTime.Now.AddSeconds(29), Success = false },
new AuthenticationEssay() { Date = DateTime.Now.AddSeconds(38), Success = false },
new AuthenticationEssay() { Date = DateTime.Now.AddSeconds(125), Success = false },
new AuthenticationEssay() { Date = DateTime.Now.AddSeconds(347), Success = false },
new AuthenticationEssay() { Date = DateTime.Now.AddSeconds(400), Success = false },
new AuthenticationEssay() { Date = DateTime.Now.AddSeconds(422), Success = false },
new AuthenticationEssay() { Date = DateTime.Now.AddSeconds(446), Success = false },
new AuthenticationEssay() { Date = DateTime.Now.AddSeconds(467), Success = false }
};
I want to select only the first occurence of those objects whose date interval is not longer than 20 seconds against the next object. In this case, the query should return only the first 4 objects. Any idea? :(
UPDATE
Sorry, I forgot to mention that I'm sorting the array by descending order. So yes, the position in the array shouldn't have any effect on the query.
What about this?
var query from i in Enumerable.Range(1, count - 1)
let current = list[i]
let previous = list[i - 1]
// I see some empty positions in your example, nullability check
where current != null && previous != null
where (current.Date - previous.Date).TotalSeconds < 20
select previous;
EDIT: Obviously you have to call First() in order to get only the first element of the sequence.
query.First();
EDIT 2: I have just read you are ordering your results descending. In this case the query will be slightly different:
var query from i in Enumerable.Range(1, count - 1)
let current = list[i]
let previous = list[i - 1]
// I see some empty positions in your example, nullability check
where current != null && previous != null
where (previous.Date - current.Date).TotalSeconds < 20
select current;
It ain't pretty, but here you go...
var result = Enumerable.Range(0, essays.Count() - 1)
.Select(i => new {Essays1 = essays[i], Essays2 = essays[i + 1]})
.Where(a => a.Essays2 != null)
.Where(a => a.Essays2.Date - a.Essays1.Date < new TimeSpan(0, 0, 0, 20))
.Select(a => a.Essays1);
Does it have to be LINQ? I love LINQ, but I think something like this would be more readable...
var result = new List<AuthenticationEssay>();
for (var i = 0; i < (essays.Count() - 1); i++)
{
if (essays[i + 1] != null)
if (essays[i + 1].Date - essays[i].Date < new TimeSpan(0, 0, 0, 20))
result.Add(essays[i]);
}
It is probably possible to do it using built-in Linq operators, but in this case I think writing a specific method is easier. You could do something like that:
static IEnumerable<AuthenticationEssay> Filter(IEnumerable<AuthenticationEssay> list)
{
AuthenticationEssay last = null;
AuthenticationEssay previous = null;
foreach(var item in list)
{
if (last == null)
{
// Always return the first item
yield return item;
}
else if ((item.Date - last.Date).TotalSeconds >= 20)
{
yield return item;
}
previous = last;
last = item;
}
if (previous != null && last != null && (last.Date - previous.Date).TotalSeconds <= 20)
yield return last;
}
Of course it would be possible to make it more reusable, by making the method generic and passing a predicate as a parameter, but since it's a very specific requirement, I'm not sure it would be very useful...
Related
I am working on an application where I have implemented a functionality. The flow is like we are getting settings that can be 1 HOURS, 2 HOURS, 1 WEEK, 2 WEEKS or 1 MONTH. For Example, If I get Settings from Database like 1 HOUR then it will calculate the difference between two dates and check for 1 HOUR with the difference I am getting from the calculation. I have done it by using multiple IF checks but I want to optimize the code. Here is my implementation.
public enum TaskSchedulingEnum : int
{
[Description("1 HOUR")]
OneHour = 1,
[Description("2 HOURS")]
TwoHours = 2,
[Description("8 HOURS")]
EightHours = 8,
[Description("1 DAY")]
OneDay = 1,
[Description("3 DAYS")]
ThreeDays = 3,
[Description("1 WEEK")]
OneWeek = 1,
[Description("2 WEEKS")]
TwoWeeks = 2,
[Description("1 MONTH")]
OneMonth = 1,
[Description("ALWAYS")]
Always = 14
}
private async Task<bool> GetUserSettings(string companyId, DateTime? TaskStartDateTime)
{
// get company settings ...
var settings = await projulSettingsService.GetCompanyNotificationSettings(companyId, ProjulSettingsTypeEnum.Notifications);
var scheduleSettings = string.Empty;
if (settings.Where(x => x.Name == ProjulPortalSettings.GeneralSettingsNotificationIsDisabled).Select(x => x.Value).FirstOrDefault() == "false")
{
scheduleSettings = JsonConvert.DeserializeObject<string>(settings.Where(x => x.Name == ProjulPortalSettings.NotificationTaskAssignedImmediateWindow).Select(x => x.Value).FirstOrDefault());
if (!string.IsNullOrEmpty(scheduleSettings))
{
var _timespan = (DateTime.UtcNow - TaskStartDateTime).Value;
var difference = _timespan.TotalHours; // in hours..
TimeSpan days = TimeSpan.FromHours(difference); // days..
var weeks = (days.TotalDays % 365) / 7; // weeks
var months = (days.TotalDays % 365) / 30; // months
var list = ApplicationExtensions.GetEnumList<TaskSchedulingEnum>().ToList();
var _val = list.Where(x => x.Text == scheduleSettings).FirstOrDefault();
if (scheduleSettings == TaskSchedulingEnum.OneHour.GetEnumDescrip()
|| scheduleSettings == TaskSchedulingEnum.TwoHours.GetEnumDescrip()
|| scheduleSettings == TaskSchedulingEnum.EightHours.GetEnumDescrip())
{
if (difference == Convert.ToDouble(_val.Value))
return true;
}
else if (scheduleSettings == TaskSchedulingEnum.OneDay.GetEnumDescrip()
|| scheduleSettings == TaskSchedulingEnum.ThreeDays.GetEnumDescrip())
{
if (days.TotalDays == Convert.ToDouble(_val.Value))
return true;
}
else if (scheduleSettings == TaskSchedulingEnum.OneWeek.GetEnumDescrip()
|| scheduleSettings == TaskSchedulingEnum.TwoWeeks.GetEnumDescrip())
{
if (weeks == Convert.ToDouble(_val.Value))
return true;
}
else if (scheduleSettings == TaskSchedulingEnum.OneMonth.GetEnumDescrip())
{
if (months == Convert.ToDouble(_val.Value))
return true;
}
else if (scheduleSettings == TaskSchedulingEnum.Always.GetEnumDescrip())
{
return true;
}
return false;
}
}
return false;
}
Is there any way to write optimized and less code to achieve the functionality? I am getting the same settings from the database as mentioned in the Enum description. That can be a single value every time. Always check will return true always.
I would not recommend using enum values in this way. Let each enum value represent an arbitrary tag, that you separately convert to a duration with a extension method. For example:
public static TimeSpan ToDuration(this TaskSchedulingEnum self)
{
return self switch
{
TaskSchedulingEnum.OneHour => TimeSpan.FromHours(1),
TaskSchedulingEnum.TwoHours => TimeSpan.FromHours(2),
TaskSchedulingEnum.OneDay => TimeSpan.FromDays(1),
TaskSchedulingEnum.Always => Timeout.InfiniteTimeSpan,
};
}
Once you have a timespan to describe the duration it should be trivial to add this to any start time to get the end time or other similar operations.
Note that you might need special handling for the Always-value, since arithmetic operations with infinities will not work correctly. You might want to describe it with TimeSpan.MaxValue or use a TimeSpan? type instead, depending on how it is used.
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.
In my application, I am wanting to fill a list with records from the database that match specific conditions:
public ActionResult SelectYearGraph(int year)
{
var lstAllSummaries = db.Summaries.ToList();
var lstMonths =
lstAllSummaries.Where(x => x.FlightDay.Year == year)
.Select(x => x.TestDay.Month)
.Distinct()
.OrderBy(x => x)
.ToList();
List<string> lstMonthNames = new List<string>();
List<int> lstCountSummaries = new List<int>();
List<int> lstCountDailySummariesDifferentTime = new List<int>();
var tsSix = new TimeSpan(6, 0, 0);
var tsTen = new TimeSpan(22, 0, 0);
foreach (var item in lstMonths)
{
var monthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(item);
lstMonthNames.Add(monthName);
foreach (var item in lstMonths)
{
var monthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(item);
lstMonthNames.Add(monthName);
lstCountDailySummaries.Add(lstAllSummaries.Count(x => x.FlightDay.Month == item && x.FlightDay.Year == year
&& (x.FlightDay.TimeOfDay >= tsSix && x.FlightDay.TimeOfDay <= tsTen)
&& !x.deleted));
lstCountDailySummariesDifferentTime.Add(lstAllSummaries.Count(x => x.FlightDay.Month == item && x.FlightDay.Year == year
&& (x.FlightDay.TimeOfDay > tsTen || x.FlightDay.TimeOfDay < tsSix)
&& !x.deleted));
}
}
... // more down here but not relevant to question
}
When I run this, I get a runtime error:
The specified type member 'TimeOfDay' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
I have tried Date and Time Canonical Functions for LINQ-to-Entities, but I am receiving the same error.
How do I get all records between 6am and 10pm?
You don't need TimeOfDay just reading Hour, Minute, Second of your DateTime will give you the same result in your case.
the TimeOfDay property returns a TimeSpan value that represents a DateTime value's time component.
So just change your code to:
...
&& (DbFunctions.CreateTime(x.TestDay.Hour, x.TestDay.Minute,x.TestDay.Second) > DbFunctions.CreateTime(6,0,0)
&& (DbFunctions.CreateTime(x.TestDay.Hour, x.TestDay.Minute, x.TestDay.Second) < DbFunctions.CreateTime(22,0,0))
...
The problem with looking for times which cross midnight is that whereas a time can be both after 6am AND before 10pm the same is not true for after 10pm AND before 6am.
You'll need to change your AND to an OR
...
&& (
(DbFunctions.CreateTime(x.TestDay.Hour, x.TestDay.Minute,x.TestDay.Second) > DbFunctions.CreateTime(22,0,0)
|| (DbFunctions.CreateTime(x.TestDay.Hour, x.TestDay.Minute, x.TestDay.Second) < DbFunctions.CreateTime(6,0,0))
)
...
var query = from x in db.Summaries
let time = DbFunctions.CreateTime(x.TestDay.Hour, x.TestDay.Minute, x.TestDay.Second)
where x.FlightDay.Year == year
&& !x.deleted
&& (time < DbFunctions.CreateTime(6, 0, 0))
&& (time > DbFunctions.CreateTime(22, 0, 0))
group x by x.FlightDay.Month into summaries
let month = summaries.Key
select new
{
Month = month,
Count = summaries.Count(),
DifferentCount = (from d in db.DailySummaries
let timed = DbFunctions.CreateTime(d.TestDay.Hour, d.TestDay.Minute, d.TestDay.Second)
where d.FlightDay.Year == year && d.FlightDay.Month == month
&& !d.deleted
&& (timed < DbFunctions.CreateTime(6, 0, 0))
&& (timed > DbFunctions.CreateTime(22, 0, 0))
select d).Count(),
};
Im trying to consolidate a list of records of in and out times per day to the minimum number of records possible.
What i have done so far, is grouped up the lines into the groups they need to be in, and put the in and out times in a list for each day.
then i want to process the lists and add the first set of in and out lines onto a single line, then process the next entry and either create a new line or fill in the blanks of the previous line.
The bit im stuck with is removing the first item from the linq result after i have processed it.
happy to look at doing it a different way.
here is what i have:
List<LoginRecordLine> condensedLoginRecordLines = new List<LoginRecordLine>();
List<LoginRecordLine> currentLoginRecordLines = GetLoginRecordsForLoginRecordReport(lowerDate, upperDate, sageDatabaseID, loggedInUserID);
var groupedLines = from LoginRecordLine line in currentLoginRecordLines
group line by new { line.TimesheetID, line.WorkPatternPayRateID } into g
select new
{
Lines = g,
TimesheetID = g.Key.TimesheetID,
PayRateID = g.Key.WorkPatternPayRateID
};
foreach (var g in groupedLines)
{
var monTimes = from line in g.Lines
orderby line.MonTimeIn ascending
where line.MonTimeSpan != TimeSpan.Zero
select new
{
TimeIn = line.MonTimeIn,
TimeOut = line.MonTimeOut,
Timesheet = line.Timesheet,
PayRate = line.WorkPatternPayRate
};
var tueTimes = //Same as monday
var wedTimes = //Same as monday
var thuTimes = //same as monday
var friTimes = //same as monday
var satTimes = //same as monday
var sunTimes = //same as monday
while (monTimes.Count() != 0 || tueTimes.Count() != 0 || wedTimes.Count() != 0 || thuTimes.Count() != 0 || friTimes.Count() != 0 || satTimes.Count() != 0 || sunTimes.Count() != 0)
{
LoginRecordLine condensedLine = new LoginRecordLine();
if (monTimes.Count() >0)
{
condensedLine.MonTimeIn = monTimes.First().TimeIn;
condensedLine.MonTimeOut = monTimes.First().TimeOut;
condensedLine.Timesheet = monTimes.First().Timesheet;
condensedLine.WorkPatternPayRate = monTimes.First().PayRate;
//*************** REVELANT PART *************/
//remove first item from monday list
}
// tue
// wed
// etc
}
}
return condensedLoginRecordLines;
Update - Working code - before performance changes
List<LoginRecordLine> condensedLoginRecordLines = new List<LoginRecordLine>();
List<LoginRecordLine> currentLoginRecordLines = GetLoginRecordsForLoginRecordReport(lowerDate, upperDate, sageDatabaseID, loggedInUserID);
var groupedLines = from LoginRecordLine line in currentLoginRecordLines
group line by new { line.TimesheetID, line.WorkPatternPayRateID } into g
select new
{
Lines = g,
TimesheetID = g.Key.TimesheetID,
PayRateID = g.Key.WorkPatternPayRateID
};
foreach (var g in groupedLines)
{
var monTimes = (from line in g.Lines
orderby line.MonTimeIn ascending
where line.MonTimeSpan != TimeSpan.Zero
select new
{
TimeIn = line.MonTimeIn,
TimeOut = line.MonTimeOut,
Timesheet = line.Timesheet,
PayRate = line.WorkPatternPayRate
}).ToList();
var tueTimes = //Same as monday
var wedTimes = //Same as monday
var thuTimes = //same as monday
var friTimes = //same as monday
var satTimes = //same as monday
var sunTimes = //same as monday
while (monTimes.Count != 0 || tueTimes.Count != 0 || wedTimes.Count != 0 || thuTimes.Count != 0 || friTimes.Count != 0 || satTimes.Count != 0 || sunTimes.Count != 0)
{
LoginRecordLine condensedLine = new LoginRecordLine();
if (monTimes.Count >0)
{
condensedLine.MonTimeIn = monTimes.First().TimeIn;
condensedLine.MonTimeOut = monTimes.First().TimeOut;
condensedLine.Timesheet = monTimes.First().Timesheet;
condensedLine.WorkPatternPayRate = monTimes.First().PayRate;
condensedLoginRecordLines.Add(condensedLine);
monTimes.RemoveAt(0);
}
//etc
}
}
return condensedLoginRecordLines;
use the List.RemoveAt Method something like myList.RemoveAt(0) will remove the first item of your list
You should revise your algorithm and maybe data structures.
For anonymous types in queries I would add a DayOfWeek property, so the queries will look like:
var monTimes = from line in g.Lines
orderby line.MonTimeIn ascending
where line.MonTimeSpan != TimeSpan.Zero
select new
{
TimeIn = line.MonTimeIn,
TimeOut = line.MonTimeOut,
Timesheet = line.Timesheet,
PayRate = line.WorkPatternPayRate,
WeekDay = DayOfWeek.Monday
};
Then, the final loop will be replaced by something like:
var condensedLoginRecordLines = monTimes
.Concat(tueTimes)
.Concat(wedTimes)
..//etc
.Select(data => new CondensedLine { WeekDay = data.WeekDay, /* here all the properties are initialized */ })
.ToList();
And that's all.
If you still prefer to use the MonInTime, TueInTime, etc. properties, move the creation of CondensedLine into a separate function which applies a switch on the WeekDay and initializes the relevant properties only. In this case you should declare a private class instead the anonymous types you currently use in order to pass the information from method to another.
I came across a similar problem. In my case, the source data was a CSV file parsed into a string and I wanted to remove the header information before processing. I could have used the approach of casting to a list and removing the first entry, but I discovered that it is possible to add a second parameter to the where query that would give you the row number index. This works with both IQueryable and IEnumerable overloads as well.
public static List<string> GetDataLinesFromCSV(string csv)
{
var csvLines = csv.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
var dataLines = csvLines.AsQueryable().Where((x, idx) => idx > 0).ToList();
return dataLines;
}
I was just binging/googling about inbuilt functions in .Net to get Top K numbers/elements from un-sorted array without using linq order by or sort methods.
Another way I can write my own TopK method by implementing selection algorithm.
But my main intension is "Is there any inbuilt function which uses selection algorithm like quick select or heap."
If there is no inbuilt TopK method I would like to know how to do with Linq or .Net without using Sort and OrderBy methods.
Reason for avoiding Sort and OrderBy is "they follow sorting algorithms internally not selection algorithm". Please correct me if I'm wrong on this.
How about this:
void Main()
{
var kth = 4;
var arr = new int [] {5, 1, 3, 4, 2, 1};
var val = getItem(arr, kth);
}
int getItem(int[] items, int kth)
{
if (kth == 0 || kth > items.Length)
{
throw new IndexOutOfRangeException();
}
else if (items.Length == 1 && kth == 1)
{
// only 1 item so return it
return items[0];
}
else if (items.Length == 2)
{
if (kth ==1)
{
// two items, 1st required so return smallest
return (items[0] <= items[1] ? items[0] : items[1]);
}
else
{
// two items, 2nd required so return smallest
return (items[0] <= items[1] ? items[1] : items[0]);
}
}
var middleItem = (int)((items.Length)/2);
var pivot = items[middleItem];
var leftarr = items.Where(s => s < pivot).ToArray();
if (leftarr.Length + 1 == kth)
{
//our pivot is the item we want
return pivot;
}
else if (leftarr.Length >= kth)
{
// our item is in this array
return getItem(leftarr, kth);
}
else
{
// need to look in the right array
var rightarr = items.Where(s => s >= pivot).ToArray();
return getItem(rightarr, kth - leftarr.Length);
}
}