My result in the MessageBox is lTest concatenated by strings. If I have dupilicate Keys, how can I group them by Key(Values)? For instance if Monday appears four times and Tuesday once, instead of Monday, Monday, Monday, Monday, Tuesday. I want it to appear Monday(4), Tuesday(1).
List<int> lNetworkIds = new List<int>();
Dictionary<DisplayDay, int> numDayOccurances = new Dictionary<DisplayDay, int>();
StringBuilder sb = new StringBuilder();
// get a list of distinct network id's for this proposal
foreach (Proposal lDetail in this._Proposal.Details)
{
if (!lNetworkIds.Contains(lDetail.NetworkId))
lNetworkIds.Add(lDetail.NetworkId);
if (!numDayOccurances.ContainsKey(lDetail.Daypart))
numDayOccurances[lDetail.Daypart] = 0;
numDayOccurances[lDetail.Daypart]++;
}
if (numDayOccurances.Count > 0)
{
string lTest = String.Join(", ", numDayOccurances.Keys);
MessageBox.Show(lTest);
}
It's not really clear from your question what you want to do, but if you have a list of days like so:
var days = new List<DayOfWeek> { DayOfWeek.Monday, DayOfWeek.Monday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday };
var result = from d in days
group d by d into g
select new
{
g.Key,
Count = g.Count()
};
Results in:
Key Count
Monday 3
Tuesday 1
Wednesday 1
You can then write this to a string as you see fit, for example:
String.Join(Environment.NewLine, result.Select(a => String.Format("{0} ({1})", a.Key, a.Count)))
Gives:
Monday (3)
Tuesday (1)
Wednesday (1)
I think you are looking for something like:
MessageBox.Show(
string.Join(",",
numDayOccurances.Keys.GroupBy(r=> r.DayOfWeek)
.Select(grp => string.Format("{0}({1})", grp.Key, grp.Count())));
Considering your class DisplayDay looks like:
class DisplayDay
{
public string DayOfWeek { get; set; }
}
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 list of invoices which all have an entryDate as datetime and amount as double. I need to return a list of doubles with the last 12 months of turnover, so I get a list like 19486.23, 52742.19, 23653.79 and so on for all 12 months. Is that possible?
public async Task<IEnumerable<Double>> GetTurnoverYear()
{
var invoices = await GetInvoices();
var turnover = invoices.???
}
public async Task<IEnumerable<Bill>> GetInvoices()
{
var client = new HttpBase();
using (var httpClient = client.GetBaseClient())
{
var response = await httpClient.GetAsync("invoices");
var result = await response.Content.ReadAsStringAsync();
dynamic data = JsonConvert.DeserializeObject<dynamic>(result);
var invoices = JsonConvert.DeserializeObject<IEnumerable<Invoice>>(data.invoices.ToString());
return invoices;
}
}
create a variable for a date that is 12 months ago and do a Where
var list = new List<Invoice> {
new Invoice{entryDate=new DateTime(2019,1,1),amount=2},
new Invoice{entryDate=new DateTime(2019,1,1),amount=2},
new Invoice{entryDate=new DateTime(2020,6,1),amount=1},
new Invoice{entryDate=new DateTime(2020,7,1),amount=1}
};
var aYearAgo = DateTime.Today.AddMonths(-12);
var last12Months = list.Where(l => l.entryDate > aYearAgo).Select(l => l.amount);
//or
var last12Months = list.Where(l => l.entryDate > DateTime.Today.AddMonths(-12)).Select(l => l.amount);
//this will work the same but its less readable i think
Assuming you don't want to start the last 12 months somewhere in the middle of a month, so we'll first have to calculate the first day of the month 12 months ago:
DateTime now = DateTime.Now;
DateTime firstDayThisMonth = new DateTime(now.Year, now.Month, 1);
var firstDayOfMonthOneYearAgo = firstDayThisMonth.AddMonths(-12);
We want to get rid of all with an EntryDate older than the first day of the month one year ago:
var newerInvoices = dbContext.Invoices
.Where(invoice => invoice.EntryDate >= firstDayOfMonthOneYearAgo)
Make groups of invoices with the same month. We don't want to group the invoices of this month with the invoices of the month one year ago (june 2020 not together with june 2019). So the GroupBy must be on year and month:
// parameter keySelector: make groups with same Year / Month as EntryDate:
.GroupBy(invoice => new
{
Year = invoice.EntryDate.Year,
Month = invoice.EntryDate.Month}
// Parameter resultSelector: take each year/month combination and all invoices with this
// year/month combination to calculate the sum of all Amounts in the group:
(yearMonth, invoicesInThisYearMonth) => new
{
Year = yearMonth.Year,
Month = yearMonth.Month,
Total = invoicesInThisYearMont.Select(invoice => invoice.Amount).Sum(),
});
Note: one year ago you will have the complete month (july 2019), but for july 2020, you'll only have the sum of the Amounts until today (2020-07-20)
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 an array of data (double[] data) and a list of datetimes (List datetimes). Each position of data array is related to the position of the datetimes. I mean: data[i] was collected in datetimes[i].
Now I want to filter the data collected with a week pattern (7 day, 24 hours).
So, I have the week pattern:
class WeekPattern
{
List<DayPattern> week;
public WeekPattern(List<DayPattern> _week)
{
week = _week;
}
public bool isInRange(DateTime time)
{
return week.Any(i => i.isInRange(time));
}
}
class DayPattern
{
DayOfWeek day;
List<bool> hours;
public DayPattern(List<bool> _hours, DayOfWeek _day)
{
hours = _hours;
day = _day;
}
public bool isInRange(DateTime time)
{
if (time.DayOfWeek != day)
return false;
return hours[time.Hour];
}
}
Filter the datetimes in range is easy (I have alread Weekpattern pattern object)
double[] data = { 1, 2, 3, 4}
string[] times = { "23/01/2013 12:00", "23/01/2013 13:00", "23/01/2013 14:00", "23/01/2013 15:00" }
List<DateTime> datetimes = Array.ConvertAll(_times, time => DateTime.ParseExact(time, "dd/MM/yyyy HH:mm:ss", null)).ToList();
Weekpattern pattern... // Weekpattern object
List<DateTime> filter = datetimes.Where(i => pattern.isInRange(i)).ToList();
But, how I get the data filteres (double[] data filtered) instead of datetimes the list of datetimes filtered?
1 was collected on 23/01/2013 12:00
2 was collected on 23/01/2013 13:00
3 was collected on 23/01/2013 14:00
4 was collected on 23/01/2013 15:00
Suppose I have a range "Wednesday, 13:00 - 14:00". So I want to get an array of doubles with 2 and 3:
data = { 2, 3 }
Once you have the list of matching dates, simply call the IndexOf() function on the datetimes list for each match then use the return to pull the value out of the double[].
Sample:
var date = new DateTime(2013, 1, 12);
List<DateTime> dates = new List<DateTime>() { new DateTime(2013, 1, 11), date, new DateTime(2013, 1, 13) };
double[] values = new double[] { 0, 1, 2 };
var filtered = dates.Where(x => x == date);
foreach (var found in filtered)
{
Console.Write(values[dates.IndexOf(found)]);
}
Console.ReadLine();
You can try something like this (this overload of the Select method accepts the element index):
var filteredData = datetimes.Select((date, i) =>
{
if (pattern.isInRange(date))
{
return data[i];
}
else
{
return -1;
}
});
The only problem is that I'll need to verify if the value is equals to -1. But this works for me.
Editing: a better solution would be using the Where method overload that uses the element index on the lambda expression:
var filteredData = data.Where((d, i) =>
{
return pattern.isInRange(datetimes[i]);
});
I have a form which user select start and end date to get data.
Although user select start and end date i have to show data from table week by week in date range.
My model is simple
public class DateBetween
public Datetime StartDate{ get;set;}
public Datetime EndDate{ get;set;}
I get list of my datas between these dates from database
IList<Revenue> datas = DB.CreateCrateria(typeof(Revenue))
.Add(Restrictions.Bt("Date", model.startDate, model.endDate))
.List<Revenue>();
public class Revenue
public int Id{ get;set;}
public double Revenue { get;set;}
public Datetime RevenueDate{ get;set;}
Example:
id Date Revenue
1 10/11/2011 554
2 11/10/2011 500
etc
If user select date like 6/30/2011 and 10/15/2011
I want to show to user
Week Date Avg.Revenue
Week 1 6/30/2011-7/2/2011 587
Week 2 7/3/2011-7/9/2011 650
...
etc
Is there any recommendation doing with aggregate funct. in linq
You could use a handwritten LINQ or use this answer:
LINQ query to split an ordered list into sublists of contiguous points by some criteria
Example: using nothing else than plain Linq and DateTime/Calendar:
var rnd = new Random();
var data = Enumerable.Range(1,100).Select(i => DateTime.Now.AddDays(rnd.Next(-91000,91000)/24));
var calendar = CultureInfo.CurrentCulture.Calendar;
Func<DateTime, int> twoWeeks = dt => (dt.Year * 100) + 2 * (calendar.GetWeekOfYear(dt, CalendarWeekRule.FirstFullWeek, DayOfWeek.Sunday) / 2);
var by2weeks = data.GroupBy(twoWeeks);
foreach (var period in by2weeks.OrderBy(g => g.Key))
{
Console.WriteLine("{0}: {1}", period.Key, string.Join(", ", period));
}
For C# 3.5 and earlier string.Join(", ", period)
string.Join(", ", period.Select(o => o.ToString()).ToArray())
The trick was getting the exact calendar start date for each week in any given year, and for that I looped on DateTime's Office Automation method. Doing that produced a Dictionary with 53 entries. After that, it was all standard LINQ grouping and referencing into the dictionary for the start date.
Calendar cal = Calendar.ReadOnly(CultureInfo.CurrentCulture.Calendar);
StringBuilder sb = new StringBuilder();
DirectoryInfo rdi = new DirectoryInfo(Root); // get all files in the root directory
List<FileInfo> allfis = rdi.GetFiles("*", SearchOption.AllDirectories).ToList();
var a = allfis.GroupBy(q=>cal.GetYear(q.LastWriteTime));
foreach (var b in a.Where(q=>q.Key==2011)) // this year only
{
double yearStartOaDate = new DateTime(b.Key, 1, 1).ToOADate();
double yearEndOaDate = yearStartOaDate + 365;
// get exact start dates for each week
Dictionary<int, DateTime> weekStartingDates = new Dictionary<int, DateTime>();
while (yearStartOaDate <= yearEndOaDate)
{
DateTime dt = DateTime.FromOADate(yearStartOaDate);
int ww = cal.GetWeekOfYear(dt, CalendarWeekRule.FirstDay, DayOfWeek.Sunday);
if(!weekStartingDates.ContainsKey(ww))
{
weekStartingDates.Add(ww, dt);
}
yearStartOaDate += ww == 1 ? 1 : 7;
}
var c = b.GroupBy(q => cal.GetWeekOfYear(q.LastWriteTime, CalendarWeekRule.FirstDay, DayOfWeek.Sunday)).OrderBy(q=>q.Key);
foreach(var d in c)
{
sb.AppendLine("Between " + weekStartingDates[d.Key].ToShortDateString() + " and " + weekStartingDates[d.Key].AddDays(6).ToShortDateString() + " there were " + d.Count() + " files modified");
}
}
File.WriteAllText("results.txt", sb.ToString());
And the results were...
Between 09/01/2011 and 15/01/2011 there were 22 files modified
Between 12/06/2011 and 18/06/2011 there were 11 files modified
etc etc.
First Select date object with WeekOfYear then group on it and ...
var result = datas.Select(p => new
{
week = EntityFunctions.DiffDays(EntityFunctions.CreateDateTime(p.Date.Year, 1, 1, 0, 0, 0), p.Date.Value).Value / 7,
Date= p.Date,
Revenue= p.Revenue
}).GroupBy(p => p.week)
.Select(p => new
{
week=p.Key,
Date=string.Format("{0:M/d/yyyy}",p.Min(q=>q.Date))+"-"+string.Format("{0:M/d/yyyy}",p.Max(q=>q.Date))
Revenue=p.Average(q=>q.Revenue)
}).ToList();