I'm trying to filter objects with SkipWhile but it does not evaluate multiple conditions.
Below is a sample code demonstrating the issue,
int[] numbers = { 1, 2, 3, 4, 5 };
var result = numbers.SkipWhile(n => n < 2 && n != 2).ToList();
This query selects 2,3,4,5, which omits the second condition (n != 2), when the first condition is true.
Is it possible to make the query evaluate both conditions?
Edit:
My actual condition is something like
... dateRanges
.OrderBy(d=>d.Sequence)
.SkipWhile(d => d.FromDate <= currentDate && d.ToDate >= currentDate)
.Skip(1).First();
which is operating on DateTime filed, to select next object in the list
Edit 2:
I have created a sample program, which is something similar to my actual code
Class to hold data,
public class DateRange
{
public int Sequence { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
}
Program
static void Main(string[] args)
{
var dateRanges = new List<DateRange>(){
new DateRange{Sequence = 1 , FromDate = new DateTime(2014,1,1), ToDate = new DateTime(2014,1,31)},
new DateRange{Sequence = 2 , FromDate = new DateTime(2014,2,1), ToDate = new DateTime(2014,2,28)},
new DateRange{Sequence = 3 , FromDate = new DateTime(2014,3,1), ToDate = new DateTime(2014,3,31)},
new DateRange{Sequence = 4 , FromDate = new DateTime(2014,4,1), ToDate = new DateTime(2014,4,30)},
new DateRange{Sequence = 5 , FromDate = new DateTime(2014,5,1), ToDate = new DateTime(2014,5,31)},
};
var myDate = new DateTime(2014, 2, 10); // A Date in Fabruary
//This query selects {2, 2014/2/1, 2014/2/28}
var selectedItem = dateRanges.OrderBy(d => d.Sequence)
.Where(d => d.FromDate <= myDate && d.ToDate >= myDate)
.First();
//What I actually need to select is {3, 2014/3/1, 2014/3/31}
//Which is next item of the list
//This is what i have tried
//But this query also selects {2, 2014/2/1, 2014/2/28}
var nextItem = dateRanges.OrderBy(d => d.Sequence)
.SkipWhile(d => d.FromDate <= myDate && d.ToDate >= myDate)
.Skip(1).First();
//Because, results of this part of query returns objects from {1, 2014/1/1, 2014/1/31} ...
var unexpectdItems = dateRanges.OrderBy(d => d.Sequence)
.SkipWhile(d => d.FromDate <= myDate && d.ToDate >= myDate);
}
It is evaluating both conditions - but as soon as the condition is false, the rest of the sequence is returned. As soon as n==2, n < 2 && n != 2 is false. In fact, your condition makes no sense anyway - if n is less than 2 it can't be equal to 2.
Basically it's not clear what you're trying to achieve, but the condition you're using isn't appropriate - and if you want to check your condition on every value rather than just "values until the condition isn't met" then you should use Where instead of SkipWhile.
EDIT: Now that you've posted a complete example, we can see what's wrong. Look at your condition:
SkipWhile(d => d.FromDate <= myDate && d.ToDate >= myDate)
Now look at the first item of your data:
new DateRange{Sequence = 1 , FromDate = new DateTime(2014,1,1),
ToDate = new DateTime(2014,1,31)},
And myDate:
var myDate = new DateTime(2014, 2, 10);
Is your condition satisfied by the first item of your data? No, because ToDate (January 31st) is not greater than or equal to myDate (February 10th). So no items are skipped by SkipWhile. Perhaps you wanted || instead of &&? (It's still not clear what this query is meant to achieve.)
Related
I'm trying to create a function that returns the number of Dates in a Date Range that are sequential, starting on a specific date.
Example:
StartDate: 9/1/2022
Date Range: 9/1/2022, 9/2/2022, 9/3/2022, 9/4/2022, 9/7/2022
In this scenario the function I'm looking for would return 4.
Assume dates could be unordered and they can roll over into the next month, so with StartDate 9/29/2022:
9/29/2022, 9/30/2022, 10/1/2022, 10/4/2022 would return 3.
I know I can loop through the dates starting at the specific date and check the number of consecutive days, but I'm wondering if there's a clean way to do it with Linq.
This is the cleanest solution I can come up with...
var startDate = new DateTime(2022, 9, 1);
var days = new List<DateTime>()
{
new(2022, 8, 28),
new(2022, 9, 1),
new(2022, 9, 2),
new(2022, 9, 3),
new(2022, 9, 4),
new(2022, 9, 7)
};
var consecutiveDays = GetConsecutiveDays(startDate, days);
foreach (var day in consecutiveDays)
{
Console.WriteLine(day);
}
Console.ReadKey();
static IEnumerable<DateTime> GetConsecutiveDays(DateTime startDate, IEnumerable<DateTime> days)
{
var wantedDate = startDate;
foreach (var day in days.Where(d => d >= startDate).OrderBy(d => d))
{
if (day == wantedDate)
{
yield return day;
wantedDate = wantedDate.AddDays(1);
}
else
{
yield break;
}
}
}
Output is:
01.09.2022 0:00:00
02.09.2022 0:00:00
03.09.2022 0:00:00
04.09.2022 0:00:00
If you wanted the count, you can call .Count() on the result or just modify the method... Should be easy.
To count the number of consecutive dates in a given date range.
first parse the dates from a string and order them in ascending order.
Then, use the TakeWhile method to take a sequence of consecutive dates from the start of the list.
Finally, count the number of elements in the returned sequence and display the result.
public class Program
{
private static void Main(string[] args)
{
var dateRange = "9/29/2022, 9/30/2022, 10/1/2022, 10/4/2022";
var dates = dateRange
.Split(", ")
.Select(dateStr =>
{
var dateData = dateStr.Split("/");
var month = int.Parse(dateData[0]);
var day = int.Parse(dateData[1]);
var year = int.Parse(dateData[2]);
return new DateTime(year, month, day);
})
.OrderBy(x => x)
.ToList();
var consecutiveDatesCounter = dates
.TakeWhile((date, i) => i == 0 || dates[i - 1].AddDays(1) == date)
.Count();
Console.WriteLine(consecutiveDatesCounter);
}
}
Output: 3
Demo: https://dotnetfiddle.net/tYdWvz
Using a loop would probably be the cleanest way to go. I would use something like the following:
List<DateTime> GetConsecutiveDates(IEnumerable<DateTime> range, DateTime startDate)
{
var orderedRange = range.OrderBy(d => d).ToList();
int startDateIndex = orderedRange.IndexOf(startDate);
if (startDateIndex == -1) return null;
var consecutiveDates = new List<DateTime> { orderedRange[startDateIndex] };
for (int i = startDateIndex + 1; i < orderedRange.Count; i++)
{
if (orderedRange[i] != orderedRange[i - 1].AddDays(1)) break;
consecutiveDates.Add(orderedRange[i]);
}
return consecutiveDates;
}
Yet another approach using a loop. (I agree with the others that said a loop would be cleaner than using Linq for this task.)
public static int NumConsecutiveDays(IEnumerable<DateTime> dates)
{
var previous = DateTime.MinValue;
var oneDay = TimeSpan.FromDays(1);
int result = 0;
foreach (var current in dates.OrderBy(d => d))
{
if (current.Date - previous.Date == oneDay)
++result;
previous = current;
}
return result > 0 ? result + 1 : 0; // Need to add 1 to result if it is not zero.
}
var dates = new List<DateTime>() {new DateTime(2014,1,1), new DateTime(2014, 1, 2), new DateTime(2014, 1, 3) , new DateTime(2014, 1, 5), new DateTime(2014, 1, 6), new DateTime(2014, 1, 8) };
var startDate = new DateTime(2014,1,2);
var EarliestContiguousDates = dates.Where(x => x>=startDate).OrderBy(x => x)
.Select((x, i) => new { date = x, RangeStartDate = x.AddDays(-i) })
.TakeWhile(x => x.RangeStartDate == dates.Where(y => y >= startDate).Min()).Count();
You're either going to sort the dates and find sequential ones, or leave it unsorted and repeatedly iterate over the set looking for a match.
Here's the latter dumb approach, leaving it unsorted and using repeated calls to 'IndexOf`:
public static int CountConsecutiveDays(DateTime startingFrom, List<DateTime> data)
{
int count = 0;
int index = data.IndexOf(startingFrom);
while(index != -1) {
count++;
startingFrom = startingFrom.AddDays(1);
index = data.IndexOf(startingFrom);
}
return count;
}
I have a collection of dates stored in my object. This is sample data. In real time, the dates will come from a service call and I will have no idea what dates and how many will be returned:
var ListHeader = new List<ListHeaderData>
{
new ListHeaderData
{
EntryDate = new DateTime(2013, 8, 26)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 11)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 1, 1)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 15)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 17)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 5)
},
};
I now need to group by date range like so:
Today (1) <- contains the date 9/17/2013 and count of 1
within 2 weeks (3) <- contains dates 9/15,9/11,9/5 and count of 3
More than 2 weeks (2) <- contains dates 8/26, 1/1 and count of 2
this is my LINQ statement which doesn't achieve what I need but i think i'm in the ballpark (be kind if I'm not):
var defaultGroups = from l in ListHeader
group l by l.EntryDate into g
orderby g.Min(x => x.EntryDate)
select new { GroupBy = g };
This groups by individual dates, so I have 6 groups with 1 date in each. How do I group by date range , count and sort within each group?
Introduce array, which contains ranges you want to group by. Here is two ranges - today (zero days) and 14 days (two weeks):
var today = DateTime.Today;
var ranges = new List<int?> { 0, 14 };
Now group your items by range it falls into. If there is no appropriate range (all dates more than two weeks) then default null range value will be used:
var defaultGroups =
from h in ListHeader
let daysFromToday = (int)(today - h.EntryDate).TotalDays
group h by ranges.FirstOrDefault(range => daysFromToday <= range) into g
orderby g.Min(x => x.EntryDate)
select g;
UPDATE: Adding custom ranges for grouping:
var ranges = new List<int?>();
ranges.Add(0); // today
ranges.Add(7*2); // two weeks
ranges.Add(DateTime.Today.Day); // within current month
ranges.Add(DateTime.Today.DayOfYear); // within current year
ranges.Sort();
How about doing this?
Introduce a new property for grouping and group by that.
class ListHeaderData
{
public DateTime EntryDate;
public int DateDifferenceFromToday
{
get
{
TimeSpan difference = DateTime.Today - EntryDate.Date;
if (difference.TotalDays == 0)//today
{
return 1;
}
else if (difference.TotalDays <= 14)//less than 2 weeks
{
return 2;
}
else
{
return 3;//something else
}
}
}
}
Edit: as #servy pointed in comments other developers may confuse of int using a enum will be more readable.
So, modified version of your class would look something like this
class ListHeaderData
{
public DateTime EntryDate;
public DateRange DateDifferenceFromToday
{
get
{
//I think for this version no comments needed names are self explanatory
TimeSpan difference = DateTime.Today - EntryDate.Date;
if (difference.TotalDays == 0)
{
return DateRange.Today;
}
else if (difference.TotalDays <= 14)
{
return DateRange.LessThanTwoWeeks;
}
else
{
return DateRange.MoreThanTwoWeeks;
}
}
}
}
enum DateRange
{
None = 0,
Today = 1,
LessThanTwoWeeks = 2,
MoreThanTwoWeeks = 3
}
and use it like this
var defaultGroups = from l in ListHeader
group l by l.DateDifferenceFromToday into g // <--Note group by DateDifferenceFromToday
orderby g.Min(x => x.EntryDate)
select new { GroupBy = g };
Do you specifically want to achieve the solution in this way? Also do you really want to introduce spurious properties into your class to meet these requirements?
These three lines would achieve your requirements and for large collections willbe more performant.
var todays = listHeader.Where(item => item.EntryDate == DateTime.Today);
var twoWeeks = listHeader.Where(item => item.EntryDate < DateTime.Today.AddDays(-1)
&& item.EntryDate >= DateTime.Today.AddDays(-14));
var later = listHeader.Where(item => item.EntryDate < DateTime.Today.AddDays(-14));
also you then get the flexibility of different groupings without impacting your class.
[Edit: in response to ordering query]
Making use of the Enum supplied above you can apply the Union clause and OrderBy clause Linq extension methods as follows:
var ord = todays.Select(item => new {Group = DateRange.Today, item.EntryDate})
.Union(
twoWeeks.Select(item => new {Group = DateRange.LessThanTwoWeeks, item.EntryDate}))
.Union(
later.Select(item => new {Group = DateRange.MoreThanTwoWeeks, item.EntryDate}))
.OrderBy(item => item.Group);
Note that I'm adding the Grouping via a Linq Select and anonymous class to dynamically push a Group property again not effecting the original class. This produces the following output based on the original post:
Group EntryDate
Today 17/09/2013 00:00:00
LessThanTwoWeeks 11/09/2013 00:00:00
LessThanTwoWeeks 15/09/2013 00:00:00
LessThanTwoWeeks 05/09/2013 00:00:00
MoreThanTwoWeeks 26/08/2013 00:00:00
MoreThanTwoWeeks 01/01/2013 00:00:00
and to get grouped date ranges with count:
var ord = todays.Select(item => new {Group = DateRange.Today, Count=todays.Count()})
.Union(
twoWeeks.Select(item => new {Group = DateRange.LessThanTwoWeeks, Count=twoWeeks.Count()}))
.Union(
later.Select(item => new {Group = DateRange.MoreThanTwoWeeks, Count=later.Count()}))
.OrderBy(item => item.Group);
Output is:
Group Count
Today 1
LessThanTwoWeeks 3
MoreThanTwoWeeks 2
I suppose this depends on how heavily you plan on using this. I had/have a lot of reports to generate so I created a model IncrementDateRange with StartTime, EndTime and TimeIncrement as an enum.
The time increment handler has a lot of switch based functions spits out a list of times between the Start and End range based on hour/day/week/month/quarter/year etc.
Then you get your list of IncrementDateRange and in linq something like either:
TotalsList = times.Select(t => new RetailSalesTotalsListItem()
{
IncrementDateRange = t,
Total = storeSales.Where(s => s.DatePlaced >= t.StartTime && s.DatePlaced <= t.EndTime).Sum(s => s.Subtotal),
})
or
TotalsList = storeSales.GroupBy(g => g.IncrementDateRange.StartTime).Select(gg => new RetailSalesTotalsListItem()
{
IncrementDateRange = times.First(t => t.StartTime == gg.Key),
Total = gg.Sum(rs => rs.Subtotal),
}).ToList(),
I have a datatable with two columns FromDate and ToDate , which are in string format.
I want to check if there are any duplicate records in my table.i.e
From Date To Date
----------------------
9/01/2012 9/16/2012
8/23/2012 8/24/2012
8/25/2012 8/25/2012
8/5/2012 8/6/2012
8/26/2012 8/27/2012
9/15/2012 9/23/2012
The table contains duplicate records as their date range is mapping for
From Date To Date
----------------------
9/01/2012 9/16/2012
9/15/2012 9/23/2012
It should return false.
var query = from row in dt.AsEnumerable()
from row1 in dt.AsEnumerable()
where
(
(
DateTime.Parse(row1.Field<string>("fromDate")) >= DateTime.Parse(row.Field<string>("fromDate")) &&
DateTime.Parse(row1.Field<string>("fromDate")) <= DateTime.Parse(row.Field<string>("toDate"))
)
||
(
DateTime.Parse(row1.Field<string>("toDate")) >= DateTime.Parse(row.Field<string>("fromDate")) &&
DateTime.Parse(row1.Field<string>("toDate")) <= DateTime.Parse(row.Field<string>("toDate"))
)
)
select new
{
fromDate = DateTime.Parse(row1.Field<string>("fromDate")),
toDate = DateTime.Parse(row1.Field<string>("toDate"))
};
//This lst contains the dates which are overlapping
var lst = query.Distinct().ToList();
Okay then, a selfjoin will help here:
I have a small class TimePeriod, just to meet your needs
public class TimePeriod
{
public int Id;
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public static DateTime Parse(string date)
{
var dt = DateTime.Parse(date,
CultureInfo.CreateSpecificCulture("en-US"), DateTimeStyles.RoundtripKind);
return dt;
}
}
then I have some TestData
var list = new List();
list.Add(new TimePeriod() { Id = 1, FromDate = TimePeriod.Parse("9/01/2012"), ToDate = TimePeriod.Parse("9/16/2012") });
list.Add(new TimePeriod() { Id = 2, FromDate = TimePeriod.Parse("8/23/2012"), ToDate = TimePeriod.Parse("8/24/2012") });
list.Add(new TimePeriod() { Id = 3, FromDate = TimePeriod.Parse("8/25/2012"), ToDate = TimePeriod.Parse("8/25/2012") });
list.Add(new TimePeriod() { Id = 4, FromDate = TimePeriod.Parse("8/5/2012"), ToDate = TimePeriod.Parse("8/6/2012") });
list.Add(new TimePeriod() { Id = 5, FromDate = TimePeriod.Parse("8/26/2012"), ToDate = TimePeriod.Parse("8/27/2012") });
list.Add(new TimePeriod() { Id = 6, FromDate = TimePeriod.Parse("9/15/2012"), ToDate = TimePeriod.Parse("9/23/2012") });
And here is the solution: (with some inspiration of OraNob, thanks for that)
var overlaps = from current in list
from compare in list
where
(
(compare.FromDate > current.FromDate &&
compare.FromDate < current.ToDate) ||
(compare.ToDate > current.FromDate &&
compare.ToDate < current.ToDate)
)
select new
{
Id1 = current.Id,
Id2 = compare.Id,
};
Perhaps you want to leave out the second Id (as you will have duplicates
here ( 1 / 6) and (6 / 1)
Sort by ToDate, FromDate (or build a sorted array of indexes into your DataTable). Loop from row or array position #2 to the end and see if the FromDate <= to the previous item's ToDate. Place overlapping items into a list. Job done.
You can also sort by FromDate, ToDate and do similar logic.
Try parsing the "To Date" column and for each, search the "From Date" column for any earlier date that has a later corresponding "To Date".
Use DataTable.Search() mehod to find out existence of any record in your DataTable this way you could enforce uniqueness in your records.
Something like this
string expression;
expression = "FromDate = #9/01/2012# AND ToDate = #9/16/2012#";
DataRow[] foundRows;
// Use the Select method to find all rows matching the filter.
foundRows = table.Select(expression);
if(foundRows.Length > 0)
// Show duplicate message
else
// Insert your new dates
For more Go here
If you handle large datatables you should use #ErikE response.
which needs more lines of code, but is definitely the most efficient by far.
If its small table and you prefer to compare each two rows.
you can use what other advised.
just make sure to prevent row from being compared to itself, and duplicates in the result enumeration.
var query = from x in list
where list.Exists((y) => x != y &&
x.FromDate <= y.ToDate &&
y.FromDate <= x.ToDate)
select x;
strong textDeclare #Table Table
(
RowId Int Identity(1, 1) Not Null,
Id NChar(3) Not Null,
StartDate DATETIME Not Null,
EndDate DATETIME Not Null
);
Insert Into #Table (Id, StartDate, EndDate)
Select 'id1', '20131210 10:10', '20131220 10:10' Union All
Select 'id1', '20131211', '20131215' Union All
Select 'id1', '20131201', '20131205' Union All
Select 'id1', '20131206', '20131208' Union All
Select 'id1', '20131225 10:10', '20131225 10:11'
Select *
From #Table;
With Overlaps (OverlapRowId, BaseRowId, OStart, OEnd, BStart, BEnd)
As
(
Select Overlap.RowId, Base.RowId, Overlap.StartDate, Overlap.EndDate, Base.StartDate, Base.EndDate
From #Table As Base
Inner Join #Table As Overlap On Overlap.Id = Base.Id
Where (((Overlap.StartDate > Base.StartDate) And (Overlap.StartDate < Base.EndDate))
Or ((Overlap.StartDate = Base.StartDate) And (Overlap.EndDate > Base.EndDate)))
And (Base.RowId != Overlap.RowId)
)
-- Remove records that were found to cause overlap issues.
Delete T
From #Table As T
Inner Join
(
Select O.OverlapRowId
From Overlaps As O
Left Join Overlaps As Fp On Fp.OverlapRowId = O.BaseRowId
Where (Fp.OverlapRowId Is Null)
) As SubQuery On SubQuery.OverlapRowId = T.RowId;
-- Select the valid options.
Select RowId, Id, StartDate, EndDate
From #Table where StartDate<EndDate;
Go
I have a query that looks like this:
var TheQuery = (from....
where x.TheDate >= StartDate && x.TheDate <= EndDate
select new MyModel()
{
Total = (int?)x.Count() ?? 0,
....
}).Single();
Basically, I'm querying a number of records based between 2 dates. If for the date there are 0 values, it returns 0 as the Total. However, if there are no values at all, it returns null and crashes. I could add .SingleOrDefault() but it would return null instead of MyModel populated with a 0. The property Total is defined as an int.
How can I solve this?
Thanks
Count has an overload with a predicate, and returns 0 when no item matches the predicate
var result = new MyModel {
Total = <yourDataSource>
.Count(x.TheDate >= StartDate && x.TheDate <= EndDate)
};
if(TheQuery !=null || TheQuery .Count()>0){
//do something you wanna do
}
or
var v = TheQuery.ToList();
now check
if (v.Count > 0)
You should opt for:
int count = (from x in ...
where x.TheDate >= StartDate && x.TheDate <= EndDate
select c).Count();
That's what you want.
var TheQuery = (from....
where x.TheDate >= StartDate && x.TheDate <= EndDate
select new MyModel()
{
Total = (int?)x.Count() ?? 0,
....
}).DefaultIfEmpty(0).Single()'
I need help to write a function or logic to find if all values in my List of type class (named Stack) are equal or not. So it will return either true or false.
public class Stack
{
public string Key { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
I have created a class with 3 properties as above.
List<Stack> Stack = new List<Stack>();
Stack WOKey = new Stack() { Key = Resource, StartDate = WorkOrderST, EndDate = WorkOrderED };
Stack.Add(WOKey);
I have created 2 objects with StartDate and EndDate assigned to them through variables.
So I need a logic or function that will return true, if all StartDate have all same values (eg DateTime(2018, 1, 1)) and as case for EndDate (eg DateTime (2018, 1, 30)).
Should I use foreach or is it possible with LINQ? I am new to C# so I am not sure how to implement it.
This is pretty simple with LINQ:
bool allSame = Unavailability.All(s => s.StartDate == new DateTime(2018, 1, 1) &&
s.EndDate == new DateTime(2018, 1, 30));
The .All returns true if every item in the sequence satisfies the condition. See .NET Enumerable.All.
If you want to see if they are all equal, just use the first value...
bool allSame = Unavailability.All(s => s.StartDate == Unavailability[0].StartDate &&
s.EndDate == Unavailability[0].EndDate);
I would use Linq
You can can do the following:
Don't forget to import using System.Linq;
List<Stack> Unavailability = new List<Stack>
{
new Stack{ Key = "A", StartDate = new DateTime(2018,1,1), EndDate = new DateTime(2018,1,30) },
new Stack{ Key = "B", StartDate = new DateTime(2018,1,1), EndDate = new DateTime(2018,1,30)},
new Stack{ Key = "C", StartDate = new DateTime(2018,1,1), EndDate = new DateTime(2018,1,30)}
};
bool allUnique = Unavailability.Select(_ => new { _.StartDate, _.EndDate }).Distinct().Count() <= 0;
What I did here was project the Stack list using the Select to a anonymous type with the objects in it that you want to compare.
Now we can use the Distinct operator to determin all the distinct values.
If the result is less than or equal to 0 that means all the values are unique and if it is something else that means multiple unique values were found.