Find periods relations in list using LINQ - c#

I have a class which contains date information about period, Start and End:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
then I have a List of objects A where I declared a few periods:
List<A> listOfA = new List<A>()
{
new A {Id=1,Name="1", Start = new DateTime (2020,1,1), End = new DateTime(2020,1,20) },
new A {Id=2,Name="2", Start = new DateTime (2020,1,21), End = new DateTime(2020,2,20) },
new A {Id=3,Name="3", Start = new DateTime (2020,5,11), End = new DateTime(2020,5,14) },
new A {Id=4,Name="4", Start = new DateTime (2020,5,15), End = new DateTime(2020,5,20) }
};
I want to find relation (overlapping, containing etc.) betwen given periods and periods in list:
var wrong = new A { Id = 5, Name = "5", Start = new DateTime(2020, 1, 3), End = new DateTime(2020, 4, 20) };
var ok = new A { Id = 6, Name = "6", Start = new DateTime(2020, 4, 3), End = new DateTime(2020, 4, 14) };
In above example wrong object have Start date inside one of the object in list and ok object have no relation. How to find that relation using LINQ?

It's quadratic time complexity and totally untested, however it looks good and that's what counts
var results = list.Where(x =>
list.Any(y =>
x != y &&
(x.Start >= y.Start && x.Start <= y.End ||
x.End <= y.End && x.End >= y.Start)))
.ToList();
Or
Given
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public bool Intersect(A a)
=> this != a && (Start >= a.Start && a.Start <= a.End || End <= a.End && End >= a.Start);
}
Usage
var wrong = list.Where(x => list.Any(x.Intersect)).ToList();
var good = list.Except(wrong).ToList();

You could try :
var wrong2 = listOfA.Any(i => wrong.End > i.Start && wrong.Start < i.End);
var ok2 = listOfA.Any(i => ok.End > i.Start && ok.Start < i.End);
"wrong2" will be true (overlap).
"ok2" will be false (no overlap).
Edit : You should maybe consider to create a "Period" object that would have "StartDate" and "EndDate" and a "IsOverlapping" method. Result would be the same but more readable :-) (See : ValueObject).

Related

Check if Sequence of Year and Month Is Complete (Without Gaps) Given Start and End

The task is pretty easy and I have an iterative solution but I am thinking that there is probably a more efficient of cleaner solution. I have a list of objects which contain a year and a month property. I want to make sure every month from a given start year + month to a given end year + month is covered.
This is my current solution:
for (int year = startYear; year <= endYear; year++)
{
int startM = year == startYear ? startMonth : 1;
int endM = year == endYear ? endMonth : 12;
for (int month = startM; month <= endM; month++)
{
if (!someList.Any(x => x.Year == year && x.Month == month))
throw new Exception("List contains gaps.");
}
}
The extension methods for DateTime below create a time series that can be used to solve your problem by joining with Linq - works for daily gaps as well.
This is a more extensible solution, not necessarily more efficient given it uses Linq vs interation.
Usage
void Main()
{
var startDate = new DateTime(2014, 1, 1);
var months = 36;
//sample w/ 3 month gaps
var monthsWithGaps = Enumerable.Range(0, months).Where(i=> i % 3 == 0)
.Select(h=> startDate.AddMonths(h))
.Dump();
//usage
startDate.GetTimeSlices(monthsWithGaps.Last(), true)
.Where(d=> d.DayOfMonth == 1)
.GroupJoin(monthsWithGaps, slice => slice.Date, set => set, (slice, set) =>
new {
slice.Date,
set
})
.Where(result => !result.set.Any()) //identify gaps
.Count(result => !result.set.Any()) //count gaps
.Dump();
}
Extension Method Implementation
public static class DateTimeExtensions
{
public static List<TimeSlice> GetTimeSlices(this DateTime date, int numberOfDays)
{
int count = 1;
return Enumerable.Range(0, numberOfDays).Select(x => date.AddDays(x)).Select(x => new TimeSlice
{
Date = x.Date,
Year = x.Year,
MonthOfYear = x.Month,
MonthOfSet = ((count - 1) / 30) + 1,
WeekOfSet = ((count - 1) / 7) + 1,
DayOfMonth = x.Day,
DayOfSet = count++
}).ToList();
}
public static List<TimeSlice> GetTimeSlices(this DateTime date, DateTime endDate, bool includeEndDate = true)
{
return GetTimeSlices(date, (endDate - date).Days + (includeEndDate ? 1 : 0));
}
public class TimeSlice
{
public DateTime Date { get; set; }
public int Year { get; set; }
public int MonthOfYear { get; set; }
public int MonthOfSet { get; set; }
public int WeekOfSet { get; set; }
public int DayOfMonth { get; set; }
public int DayOfSet { get; set; }
}
}

ListView sorting by two columns

I have a model class Reminder:
public class Reminder
{
public string ReminderName { get; set; }
public ReminderTypes ReminderType { get; set; }
public DateTime ReminderDate { get; set; }
}
and I also have an enum called ReminderTypes:
public enum ReminderTypes
{
REMINDER_NORMAL,
REMINDER_FLAGGED,
REMINDER_IMPORTANT,
REMINDER_WARNING
}
I created my list as follows.
List<Reminder> reminders = new List<Reminder> {
new Reminder{ ReminderName = "Reminder1", ReminderType = ReminderTypes.REMINDER_FLAGGED , ReminderDate = DateTime.Now.AddDays(5)
},
new Reminder{ ReminderName = "Reminder2", ReminderType = ReminderTypes.REMINDER_IMPORTANT, ReminderDate = DateTime.Now.AddDays(10).AddHours(5).AddMinutes(50)
},
new Reminder{ ReminderName = "Reminder3", ReminderType = ReminderTypes.REMINDER_NORMAL, ReminderDate = DateTime.Now.AddHours(5)
},
new Reminder{ ReminderName = "Reminder4", ReminderType = ReminderTypes.REMINDER_WARNING, ReminderDate = DateTime.Now.AddDays(10).AddHours(5).AddMinutes(49)
},
new Reminder{ ReminderName = "Reminder5", ReminderType = ReminderTypes.REMINDER_FLAGGED, ReminderDate = DateTime.Now.AddDays(5).AddHours(5)
},
};
ListViewReminder.ItemsSource = reminders;
The sorting rule that should be:
Always flagged ones must be on top (flagged ones should be sorted by time)
Those that are not flagged should be sorted by current time (those close to current time should be on top)
The view that should be in listview:
Reminder1 ( Becuse flagged and Closer date than the other flagged.)
Reminder5 ( Because flagged )
Reminder3 ( Non-flagged, Closer date than the other non-flagged )
Reminder4
Reminder2
How can I do that?
you can Achieve this using System.Linq,
var Result = reminders
.Where(e => e.ReminderType == ReminderTypes.REMINDER_FLAGGED)
.OrderBy(e => e.ReminderDate).ToList();
Result.AddRange(reminders
.Where(e => e.ReminderType != ReminderTypes.REMINDER_FLAGGED)
.OrderBy(e => e.ReminderDate));
I would recommend two changes.
Assign numbers to your enums so they sort how you want them sorted.
Use OrderBy and ThenBy to sort your list based on ReminderType, then ReminderDate.
public enum ReminderTypes
{
REMINDER_FLAGGED = 0,
REMINDER_NORMAL = 1,
REMINDER_WARNING = 2,
REMINDER_IMPORTANT = 3
}
List<Reminder> ordered = reminders.OrderBy(x => x.ReminderType).ThenBy(x => x.ReminderDate).ToList();
ordered.ForEach(x => Console.WriteLine(x.ReminderName));
Output
Reminder1
Reminder5
Reminder3
Reminder4
Reminder2

Algorithm to find and fix Date Intersects

I have a an object that includes a date range
public class MyObject
{
public int Id {get; set;}
public string Name {get; set;}
public DateTime StartDate {get; set;}
public DateTime EndDate {get; set;}
}
I want to be able to pass in an instance of MyObject and check to make sure all other objects that have intersecting dates, such as
var myObj = new MyObject
{
Id = 1,
Name = "Test",
StartDate = new Date(2017, 3,1),
EndDate = new Date(2017, 3,10)
};
foreach(var objInList in objList)
{
bool done = false;
//if both dates are inside rnage
if (objInList.StartDate <= myObj.StartDate && myObj.StartDate <= objInList.EndDate
&& objInList.StartDate<= myObj.EndDate && myObj.EndDate<= objInList.EndDate)
{// update start and end
done = true;
}
// if start of new item is in range, but end isnt
else if (objInList.StartDate<= myObj.StartDate && myObj.StartDate <= objInList.EndDate)
{
if (!done)
{
done = true;
}
}
// if both dates are inside range
else if (objInList.StartDate<= myObj.EndDate && myObj.EndDate <= objInList.EndDate)
{
if (!done)
{ }
}
}
Basically I need to update objInList to ensure that there are no date intersects after the loop. I am having a hard time putting the logic in place without it getting overly complicated.
The code for checking date range intersects is as follows:
MyObject a = ...
MyObject b = ...
DateTime maxStart = a.StartDate > b.StartDate ? a.StartDate : b.StartDate;
DateTime minEnd = a.EndDate < b.EndDate ? a.EndDate : b.EndDate;
bool intersect = (maxStart < minEnd);
I hope i helps you:
private bool IsTimeBetween(DateTime theTime, DateTime startTime, DateTime endTime)
{
if (theTime >= startTime && theTime <= endTime)
return true;
else
return false;
}
private void check()
{
MyObject myObj = new MyObject
{
Id = 1,
Name = "Test",
StartDate = new DateTime(2017, 3, 1),
EndDate = new DateTime(2017, 3, 10)
};
List<MyObject> objList = new List<MyObject>();
foreach (var objInList in objList)
{
if (objInList.StartDate >= objInList.EndDate)
continue;
if (IsTimeBetween(objInList.StartDate, myObj.StartDate, myObj.EndDate)
|| IsTimeBetween(objInList.EndDate, myObj.StartDate, myObj.EndDate))
{
//intersects
}
if (objInList.StartDate < myObj.StartDate && objInList.EndDate > myObj.EndDate)
{
//intersects
}
}
}

Group By ranges in Linq

Thanks all, I am sorry but I am very beginner. Let me explain it in details: I have a table "theTable" and has two columns "Date" in DateTime and "Value" in double.
What I want as a result: 1.check which date is the minimum 2. Take this as firstdat
I have tried but seems not working
from cf in theTable.
group t by t.Month.Month / 8 + t.Month.Year into g
select new { sum = g.Sum(x=>x.Amount) ,year = g.Key}
You can try to do something like that:
private static void Main(string[] args)
{
var columns = new List<Column>();
var dateRanges = new List<DateRange>()
{
new DateRange(new DateTime(2013, 09, 03), new DateTime(2014, 09, 03))
};
var result = columns.GroupBy(
column => dateRanges.FirstOrDefault(dr => dr.IsInRange(column.Date)),
(dr, drc) => new KeyValuePair<DateRange, int>(dr, drc.Sum(v => v.Value)));
}
public struct DateRange
{
public DateRange(DateTime from, DateTime to)
: this()
{
From = from;
To = to;
}
public bool IsInRange(DateTime date)
{
return date >= From && date <= To;
}
public DateTime From { get; private set; }
public DateTime To { get; private set; }
}
public class Column
{
public int Value { get; set; }
public DateTime Date { get; set; }
}
theTable.GroupBy(t => t.Date.Month / 8 + t.Date.Year)
.Select(g => g.Sum(x = > x.Value));
If you take the range 8/1991-7/1992 for instance they will both end up in the same group:
8 / 8 + 1991 = 7 / 8 + 1992 = 1992
myList.Where(x=> x.Date < lastDate && x.Date > firstDate).Sum(x=>x.Value)
Not sure of the answer because the question is not really clear but this is the sum of all the property "Value" of your list which property "Date" is between a date firstDate and a date LastDate.

c# Array copy, resize

I have an array:
static Array results = new[] { new { Id, RoomCount, Cover, Space, Floor,
FloorCount, NameHouse, Price, NameStreet } };
then I have the second one:
var res = (//.......//
select new { f.Id, f.RoomCount, f.Cover, f.Space, f.Floor, f.FloorCount,
h.NameHouse, f.Price, s.NameStreet }).ToArray();
then I wanna copy res to results:
var lres = res.Length;
res.CopyTo(results, lres);
the lenght of the res varies, and the lenght of the results is fixed, cause it is just declared.
I got an exeption: the length of the dest array is too short.
I also tried to resize dest array:
var lres = res.Length;
Array.Resize(results, lres);
Also crashed: The type arguments for method System.Array.Resize<T>(ref T[], int) cannot be inferred from the usage. Try specifying the type arguments explicitly.
Any suggestions?
public partial class Search : System.Web.UI.Page
{
public static int Id { get; set; }
public static int RoomCount { get; set; }
public static string Cover { get; set; }
public static int Space { get; set; }
public static int Floor { get; set; }
public static int FloorCount { get; set; }
public static string NameHouse { get; set; }
public static decimal Price { get; set; }
public static string NameStreet { get; set; }
static Array results = new[] { new { Id, RoomCount, Cover, Space, Floor, FloorCount, NameHouse, Price, NameStreet } };
private Array FormData()
{
//some code
var lng = ht.Length;
while (lng != 0)
{
//
var var = ht[lng - 1];
lng--;
var res = (from f in db.FlatSet
.Where(x => x.RoomCount >= rMin && x.RoomCount <= rMax)
.Where(x => x.Space >= sMin && x.Space <= sMax)
.Where(x => x.Floor >= fMin && x.Floor <= fMax)
.Where(x => x.FloorCount >= fcMin && x.FloorCount <= fcMax)
.Where(x => x.HouseTypeId == var)// || x.HouseTypeId == 2)
.Where(x => x.Price >= pMin && x.Price <= pMax)
.Where(x => x.Rent == r)
join h in db.HouseTypeSet on f.HouseTypeId equals h.Id
join s in db.StreetSet on f.StreetId equals s.Id
select new { f.Id, f.RoomCount, f.Cover, f.Space, f.Floor, f.FloorCount, h.NameHouse, f.Price, s.NameStreet }).ToArray();
var lres = res.Length;
Array.Resize(ref results, lres);
res.CopyTo(results, lres);
}
return results;
}
FIXED:
public class result
{
public int Id { get; set; }
public int RoomCount { get; set; }
public string Cover { get; set; }
public int Space { get; set; }
public int Floor { get; set; }
public int FloorCount { get; set; }
public string NameHouse { get; set; }
public decimal Price { get; set; }
public string NameStreet { get; set; }
}
public partial class Search : System.Web.UI.Page
{
DB_9AB8FB_lisogorEntities db = new DB_9AB8FB_lisogorEntities();
List<result[]> myList = new List<result[]>();
List<result> NewMyList = new List<result>();
}
//////some code
private List<result[]> FormData()//
{
int[] ht = (int[])arr;
var lng = ht.Length;
while (lng != 0)
{
//some code
var var = ht[lng - 1];
lng--;
myList.Add((from f in db.FlatSet
.Where(x => x.RoomCount >= rMin && x.RoomCount <= rMax)
.Where(x => x.Space >= sMin && x.Space <= sMax)
.Where(x => x.Floor >= fMin && x.Floor <= fMax)
.Where(x => x.FloorCount >= fcMin && x.FloorCount <= fcMax)
.Where(x => x.HouseTypeId == var)// || x.HouseTypeId == 2)
.Where(x => x.Price >= pMin && x.Price <= pMax)
.Where(x => x.Rent == r)
join h in db.HouseTypeSet on f.HouseTypeId equals h.Id
join s in db.StreetSet on f.StreetId equals s.Id
select new result{ Id = f.Id, RoomCount = f.RoomCount, Cover = f.Cover, Space = f.Space, Floor = f.Floor,
FloorCount = f.FloorCount, NameHouse = h.NameHouse, Price = f.Price, NameStreet=s.NameStreet }).ToArray());
}
return myList;
}
private void BindData()
{
var i = myList.Count;
while (i != 0)
{
if (myList[i - 1].Length != 0)
{
var j = myList[i - 1].Length;
while (j != 0)
{
NewMyList.Add(myList[i - 1][j-1]);
j--;
}
}
i--;
}
Results1.DataSource = NewMyList;
Results1.DataBind();
}
}
Try putting the explicit type in your Array.Resize<T>:
Array.Resize<String>(ref results, lres);
Then change lres to 0 because lres is the length of the array and the second parameter for CopyTo is the starting index of the destination not the length of the array:
res.CopyTo(results, 0);
The index is the starting index and you start with the last index and that's why destination array was not long enough.
Or use Array.Copy():
Array.Copy(res, results, res.length);
you are using an anonymous type and the cocmpiler doesn't recognize how it will infer the union of the two arrays as the compiler should know the type of the object for which to create the instance
you are getting this exception at
Array.Resize<T>
method which should be strongly typed
Just to understand more
class Program
{
static void Main(string[] args)
{
Array results = new[] { new { RoomCount = "2", Cover = "5", Space = "5", Floor = "5", FloorCount = "5", NameHouse = "5", Price = "5", NameStreet = "5" } };
//with this line not cpmmented does not compile
Array.Resize(ref results, results.Length + 5);
String[] myArr = {"The", "quick", "brown", "fox", "jumps",
"over", "the", "lazy", "dog"};
Array.Resize(ref myArr, myArr.Length + 5);
MyTest[] resultsMyTest = new MyTest[] {new MyTest{RoomCount=3,Cover="Red"}};
//here it will work as the compiler know how to infer it
Array.Resize(ref resultsMyTest, results.Length + 5);
}
public class MyTest
{
public int RoomCount
{
get;
set;
}
public string Cover
{
get;
set;
}
}
}
Hope this help
If you are after a solution for your current code as it is, go with this,
var arguments=new object[] { results, results.Length + res.Length };
var method= typeof(Array).GetMethod("Resize").MakeGenericMethod(res.GetType().GetElementType());
method.Invoke(null,arguments );
var lastIndex = results.Length;
results = arguments[0] as Array;
res.CopyTo(results, lastIndex);
But I strongly suggest you not to do this. But create a class with all those properties and use a List<YourClass> for a clean and maintainable code. A generic Array of anonymous class won't take you long before you are forced to do a complete re-factoring.

Categories