Algorithm uniform distribution of elements in the matrix - c#

I need to generate tasks in the month of the date of the random uniform distribution. For example, for 10 people to 10 tasks so that the dates are spaced not less than two days. Weekends and holidays are not to be used. Essentially uniform random distribution of elements in the multiplicity with the additional condition where the multiplicity - people x dates of month. Suggest, where I can watch the algorithm.

In general decided somehow. However, I must say, a decision I do not like, but nothing better could not think. Parameters are as follows: tasksPerMonth - the number of jobs per month, distanceBetweenTasks - the minimum distance between two adjacent jobs, isTasksInWeekend - take into account whether or not the weekend, minDay - starting from this day (for various reasons, this may not be the first day of the month), listOfDays - initially empty, holidays - a list of days off and holidays, workdaysInMonth - list of working days, random - empty Random (). The rest, I think, in principle, it is clear, the function code is shown below OptimizationTheDistributionOfTasks
public void GenerateRandomTasks(int tasksPerMonth, int distanceBetweenTasks, bool isTasksInWeekend, int minDay, List<int> listOfDays, List<int> holidays, List<int> workdaysInMonth, Random random)
{
if (tasksPerMonth == 0)
tasksPerMonth = 1;
var daysInMonth = workdaysInMonth.Count + holidays.Count;
var tasksDaysInMonth = !isTasksInWeekend ? workdaysInMonth.Count : daysInMonth;
for (int i = 0; i < tasksPerMonth; i++)
{
int maxDayInPeriod;
if (i < tasksPerMonth - 1)
{
maxDayInPeriod = minDay + tasksDaysInMonth / tasksPerMonth;
if (!isTasksInWeekend && holidays.Contains(maxDayInPeriod))
maxDayInPeriod = workdaysInMonth.First(v => v > maxDayInPeriod);
}
else
{
maxDayInPeriod = daysInMonth;
if (!isTasksInWeekend && holidays.Contains(maxDayInPeriod))
maxDayInPeriod = workdaysInMonth.Last();
}
if (minDay > maxDayInPeriod)
minDay = maxDayInPeriod;
var day = random.Next(minDay, maxDayInPeriod);
if ((isTasksInWeekend != true && holidays.Contains(day)))
day = OptimizationTheDistributionOfTasks(minDay, maxDayInPeriod, listOfDays, day, holidays);
if (day > daysInMonth)
day = daysInMonth;
listOfDays.Add(day);
minDay = maxDayInPeriod;
if (minDay <= day + distanceBetweenTasks)
minDay = day + distanceBetweenTasks + 1;
}
}
Method OptimizationTheDistributionOfTasks:
private int OptimizationTheDistributionOfTasks(int minDay, int maxDay, List<int> listDays, int day, List<int> holidays)
{
var listOfDays = new List<DaysForTaskPlan>();
for (int k = minDay; k <= maxDay; k++)
{
var tempCountDays = listDays.Count(d => k == d);
if (!holidays.Contains(k))
listOfDays.Add(new DaysForTaskPlan(k, tempCountDays));
}
if (listOfDays.Any())
{
day = listOfDays.First(p => p.AmountDays == listOfDays.Min(z => z.AmountDays)).CurrDay;
}
listOfDays.Clear();
return day;
}
Good luck to all

Related

How to check if my date range is totally covered by CONSECUTIVE rows?

I need to check if a date range is totally covered by this date range table sorted in ascending order of dFrom, both are Date type:
dFrom dTo
----- -----
10/01 10/03
10/05 10/08
10/08 10/09
10/09 10/12
10/13 10/18
10/15 10/17
10/19 10/24
range A: 10/01-10/14 is NOT totally covered because 10/04 is missing from table.
range B: 10/10-10/20 is totally covered.
What I can think of is for a given date range like A and B, to check if each day is covered in the table:
var dRangeFrom = rangeFrom.Date; // use "var" as C# has no date type
var dRangeTo = rangeTo.Date;
int DaysCovered = 0;
int HowManyDays = (dRangeTo - dRangeFrom).TotalDays()+1;
int StartFromRow = 0;
while (dRangeFrom <= dRangeTo)
{
for (int i=StartFromRow; i<table.rows.count; i++)
{
if (table.rows[i]["dFrom"] > dRangeFrom) // optimization 1: no need to continue.
break;
if (dRangeFrom >= table.rows[i]["dFrom"] && dRangeFrom <= table.rows[i]["dTo"])
{
DaysCovered++;
StartFromRow = i; // optimization 2: next day comparison simply starts from here
break;
}
}
dRangeFrom.AddDays(1);
}
if (DaysCovered == HowManyDays)
Console.Write("Totally covered");
else
Console.Write("NOT");
One way to solve it would be to write a helper method that gets all the days in a range:
public static List<DateTime> GetDaysCovered(DateTime from, DateTime to)
{
var result = new List<DateTime>();
for (var i = 0; i < (to.Date - from.Date).TotalDays; i++)
{
result.Add(from.Date.AddDays(i));
}
return result;
}
And then we can join all the ranges from the table together and see if they match the days in the range we're trying to cover:
foreach (DataRow row in table.Rows)
{
tableDates.AddRange(GetDaysCovered(
row.Field<DateTime>("dFrom").Date,
row.Field<DateTime>("dTo").Date));
}
var rangeDates = GetDaysCovered(dRangeFrom, dRangeTo);
var missingDates = rangeDates
.Where(rangeDate => !tableDates.Contains(rangeDate))
.ToList();
if (missingDates.Any())
{
Console.Write("These dates are not covered: ");
Console.Write(string.Join(",",
missingDates.Select(date => date.ToShortDateString())));
}
else
{
Console.Write("Totally covered");
}
A naive solution is to check for each date in the range whether it is covered by any row.
var totallyCovered = true;
for (var date = rangeFrom.Date; date <= rangeTo.Date; date = date.AddDays(1))
{
var covered = dates.Any(x => date >= x.dFrom && date <= x.dTo);
if (!covered)
{
totallyCovered = false;
break;
}
}
if (totallyCovered)
{
Console.WriteLine("Totally covered.");
}
else
{
Console.WriteLine("No.");
}
That's kinda long and ugly, but thankfully you can fit that into a single LINQ query:
var dateRange = Enumerable.Range(0, 1 + rangeTo.Subtract(rangeFrom).Days)
.Select(offset => rangeFrom.Date.AddDays(offset));
var totallyCovered = dateRange.All(d => dates.Any(x => d >= x.dFrom && d <= x.dTo));
Note: This has time complexity of O(|range| * |rows|), which might be too much. To fix that you'd have to employ a more sophisticated data structure that would allow you to query ranges in logarithmic time, but since your original sample also contained nested loops, I'll assume it's unnecessary.

Scheduler algorithm that fill remaning hours to work

I need to generate all possible values to a scheduler who works like this:
Some hours of the week can be already chosen.
The week of work is defined by the following pattern "???????" question marks can be replaced.
Given a maximum of hours, I need to replace the question marks with digits so that the sum of the scheduled hours match the hours need to work in a week returning a string array with all possible schedules, ordered lexicographically.
Example:
pattern = "08??840",
required_week_hours= 24
In this example, there are only 4 hours left to work.
calling this:
function List<String> GenerateScheduler(int workHours, int dayHours, string pattern){}
public static void Main(){
GenerateScheduler(24, 4, "08??840");
}
This would return the following list of strings:
0804840
0813840
.......
.......
0840840
I'm not very familiar with algorithms, which one I could use to solve this problem?
This sounds like a problem where you have to generate all permutations of a list of a certain amount of numbers that sum up to a certain number. First, you need to sum up the hours you already know. Then you need to count up the number of ? aka the number of shifts/days you do not know about. Using these parameters, this is what the solution will look like,
public List<string> GenerateScheduler(int workHours, int dayHours, string pattern){
int remainingSum = workHours;
int unknownCount = 0;
// first iterate through the pattern to know how many ? characters there are
// as well as the number of hours remaining
for (int i = 0; i < pattern.Length; i++) {
if (pattern[i] == '?') {
unknownCount++;
}
else {
remainingSum -= pattern[i] - '0';
}
}
List<List<int>> permutations = new List<List<int>>();
// get all the lists of work shifts that sum to the remaining number of hours
// the number of work shifts in each list is the number of ? characters in pattern
GeneratePermutations(permutations, workHours, unknownCount);
// after getting all the permutations, we need to iterate through the pattern
// for each permutation to construct a list of schedules to return
List<string> schedules = new List<string>();
foreach (List<int> permutation in permutation) {
StringBuilder newSchedule = new StringBuilder();
int permCount = 0;
for (int i = 0; i < pattern.Length(); i++) {
if (pattern[i] == '?') {
newSchedule.Append(permutation[permCount]);
permCount++;
}
else {
newSchedule.Append(pattern[i]);
}
}
schedules.Add(newSchedule.ToString());
}
return schedules;
}
public void GeneratePermutations(List<List<int>> permutations, int workHours, int unknownCount) {
for (int i = 0; i <= workHours; i++) {
List<int> permutation = new List<int>();
permutation.Add(i);
GeneratePermuationsHelper(permutations, permutation, workHours - i, unknownCount - 1);
}
}
public void GeneratePermutationsHelper(List<List<int>> permutations, List<int> permutation, int remainingHours, int remainingShifts){
if (remainingShifts == 0 && remainingHours == 0) {
permutations.Add(permutation);
return;
}
if (remainingHours <= 0 || remainingShifts <= 0) {
return;
}
for (int i = 0; i <= remainingHours; i++) {
List<int> newPermutation = new List<int>(permutation);
newPermutation.Add(i);
GeneratePermutationsHelper(permutations, newPermutation, remainingHours - i, remainingShifts - 1);
}
}
This can be a lot to digest so I will briefly go over how the permutation recursive helper function works. The parameters go as follows:
a list containing all the permutations
the current permutation being examined
the remaining number of hours needed to reach the total work hour count
the number of remaining shifts (basically number of '?' - permutation.Count)
First, we check to see if the current permutation meets the criteria that the total of its work hours equals the amount of hours remaining needed to complete the pattern and the number of shifts in the permutation equals the number of question marks in the pattern. If it does, then we add this permutation to the list of permutations. If it doesn't, we check to see if the total amount of work hours surpasses the amount of hours remaining or if the number of shifts has reached the number of question marks in the pattern. If so, then the permutation is not added. However, if we can still add more shifts, we will run a loop from i = 0 to remainingHours and make a copy of the permutation while adding i to this copied list in each iteration of the loop. Then, we will adjust the remaining hours and remaining shifts accordingly before calling the helper function recursively with the copied permutation.
Lastly, we can use these permutations to create a list of new schedules, replacing the ? characters in the pattern with the numbers from each permutation.
As per OP, you already know the remaining hours, which I assume is given by the parameter dayHours. So, if you were to break down the problem further, you would need to replace '?' characters with numbers so that, sum of new character(number) is equal to remaining hours(dayHours).
You can do the following.
public IEnumerable<string> GenerateScheduler(int totalHours,int remainingHours,string replacementString)
{
var numberOfPlaces = replacementString.Count(x => x == '?');
var minValue = remainingHours;
var maxValue = remainingHours * Math.Pow(10,numberOfPlaces-1);
var combinations = Enumerable.Range(remainingHours,(int)maxValue)
.Where(x=> SumOfDigit(x) == remainingHours).Select(x=>x.ToString().PadLeft(numberOfPlaces,'0').ToCharArray());
foreach(var item in combinations)
{
var i = 0;
yield return Regex.Replace(replacementString, "[?]", (m) => {return item[i++].ToString(); });
}
}
double SumOfDigit(int value)
{
int sum = 0;
while (value != 0)
{
int remainder;
value = Math.DivRem(value, 10, out remainder);
sum += remainder;
}
return sum;
}

Cleaner way of filtering datetime by week in C#

I have a large database of records with dates. When the user selects MTD the programs counts the number of records in each 7 day span starting today (not using the weeks of the current month so I can't use .Day or .GetWeekOfMonth). Right now it looks like this:
if (options.timeRange == "MTD")
{
if (x.close_dt.Value.Date < DateTime.Today.AddMonths(-1))
{
complete = true;
}
if (DateTime.Today.AddMonths(-1).Date <= x.close_dt.Value.Date && x.close_dt.Value.Date <= DateTime.Today)
{
if (DateTime.Today.AddDays(-6).Date <= x.close_dt.Value.Date && x.close_dt.Value.Date <= DateTime.Today.Date)
{
chartObj.weeks[0]++;
}
else if (DateTime.Today.AddDays(-13).Date <= x.close_dt.Value.Date && x.close_dt.Value.Date <= DateTime.Today.AddDays(-7).Date)
{
chartObj.weeks[1]++;
}
else if (DateTime.Today.AddDays(-20).Date <= x.close_dt.Value.Date && x.close_dt.Value.Date <= DateTime.Today.AddDays(-14).Date)
{
chartObj.weeks[2]++;
}
else if (DateTime.Today.AddDays(-27).Date <= x.close_dt.Value.Date && x.close_dt.Value.Date <= DateTime.Today.AddDays(-21).Date)
{
chartObj.weeks[3]++;
}
}
}
Thank you for your help
I'm still not sure I follow you correctly, but if you just want to check for the previous 4 weeks of todays date and increment each "week" count you can simplify your code a lot.
First, keep your code DRY by not repeating common code:
private static bool DateInRange(DateTime date, DateTime minDate, DateTime maxDate)
{
return date.Date > minDate.Date && date.Date <= maxDate.Date;
}
Then change your foreach loop to this:
//Store todays date in a variable so you don't grab it every time
//(it changes every "tick" you know)
var todaysDate = DateTime.Now;
foreach(var date in sampleDates)
{
//Date is out of our 4 "week" range, skip to next loop
if(date.Date < todaysDate.AddDays(-28).Date || date.Date > todaysDate.Date)
{
continue;
}
//Using simple math we skip a lot of unnecessary code
//This loop runs 4 times
//-7 - 0
//-14 - -7
//-21 - -14
//-28 - -21
for(int i = 0; i < 4; i++)
{
if(DateInRange(date, todaysDate.AddDays((i + 1) * -7), todaysDate.AddDays(-7 * i)))
{
//Increment our "week's" counter
weekCount[i]++;
}
}
}
Fiddle here
You can do this pretty easily via Enumerable.Range and ToDictionary:
var recordsByWeek = Enumerable.Range(0, 4).ToDictionary(i => i + 1, i =>
{
var start = DateTime.Today.AddDays(7 * i);
var end = start.AddDays(7);
return records.Where(r => start <= r.Date && r.Date < end).ToList();
});
Then, you can simply do something like:
Week 1 Record Count: #recordsByWeek[1].Count;
UPDATE
I initially missed the part about it being previous weeks. The code is mostly the same, but with slight modifications. I'm leaving the original for comparison and in case someone on the interwebs stumbles upon this and actually needs that instead.
var recordsByWeek = Enumerable.Range(0, 4).Reverse().ToDictionary(i => 4 - i, i =>
{
var end = DateTime.Today.AddDays(-7 * i);
var start = end.AddDays(-7);
return records.Where(r => start <= r.Date && r.Date < end).ToList();
});
Essentially, you just reverse the enumerable so it's 3, 2, 1, 0 instead of 0, 1, 2, 3. Then, to get the week number, your subtract i from 4, which then gives you 1, 2, 3, 4. Finally, the calculation for start and end is basically flipped, so that we determine end first, using the enumerable value and then figure out start by subtracting 7 days from that.
Oh, and one more thing, this is actually exclusive of today. If you need to include today as well, such that the 4th week would count records from today, then you just need to add an extra day in when calculating end:
var end = DateTime.Today.AddDays(-7 * i + 1);

Data Aggregations over time, when time is variable

I am in the process of developing an application which calculates the shared acquired in a product over a specified time period (Term).
After the calculations have been performed, it is necessary for me to aggregate the data into groups based on a predefined review period (for example if the time required to gain 100% ownership of the product is 25 years, and the review period value is 5 years, I would have 5 sets of data aggregations for the agreement).
I perform the aggregations as shown by looping through my calculation result set:
if (Year% ReviewPeriod == 0)
{
// Perform Aggregations
}
This works fine in most scenarios.
However I do have a number of scenarios where the product reaches 100% ownership before the end of term.
What I need to be able to do is aggregate the calculations performed based on the ReviewPeriod variable, but if the final number of values in the calculations is not equal to the review period, aggregate the items based on the number of items remaining.
For example, given a 22 year term, data would be aggregated based on the Review Period variable, however if there is a remainder, then the remainder should be aggregated based on the value of the remainder.
Worked Example
Year 0 - 5 = 5 Aggregations
Year 6 - 10 = 5 Aggregations
Year 11 - 15 = 5 Aggregations
Year 16 - 20 = 5 Aggregations
Year 21 - 22 = 2 Aggregations
Could anyone help me with the logic to aggregate the data as I have described.
Probably the simplest way would be something like:
for ( int year = 0; year <= max_year; year++ ) {
if ( year % reviewPeriod == 0 ) {
// start a new aggregation
}
// add year to current aggregation
}
You could keep a list of aggregations and add a new one at the start of each period.
Here is a working example that just groups years in lists:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Aggregations
{
class Program
{
static void Main(string[] args)
{
int maxYear = 22;
int period = 5;
int year = 1985;
List<List<int>> aggregations = new List<List<int>>();
int i = -1;
for (int y = 0; y <= maxYear; y++)
{
if (y % period == 0)
{
aggregations.Add(new List<int>());
i++;
}
aggregations.ElementAt(i).Add(year);
year++;
}
foreach ( List<int> l in aggregations )
{
foreach (int yy in l)
{
Console.Write(yy + " ");
}
Console.WriteLine();
}
}
}
}
You've not really given enough of your code to go on. Hopefully you should be able to use this however your loop is currently set up. It "leaks" the mod value to the outside of the loop; after the loop is over, you can check the final mod value to see how many aggregations are left.
int modValue = 0;
for //foreach/while/... - your loop here
{
...
modValue = Year % ReviewPeriod;
if (modValue == 0)
{
// Perform Aggregations
}
...
} // end of your loop
if (modValue != 0)
{
// Perform final aggregation. There are modValue items to aggregate.
}
I think my suggestion is not worth 300rep bounty, and either I misunderstood your problem, or you've overshot the bounty..
Do your existing code that calculates the final aggregations works well? If so, then to determine the ranges yo umay just use modulo (%) and simple math:
int minYear = ...the first year // inclusive, i.e. 1970
int maxYear = ...the last year // inclusive, i.e. 2012
int span = maxYear - minYear + 1; // 1970..2012->43, 2001..2006->6
int fullFives = span / 5; // 1970..2012->8, 2001..2006->1
int remainder = span % 5; // 2001..2006->3, 2001..2006->1
for(int i=0; i<fullFives; ++i)
{
int yearFrom = minYear + 5*i
int yearTo = minYear + 5*(i+1) - 1
// 1970..2012 -> 1970-1974, 1975-1979,1980-1984,1985-1989,1990-1994,1995-1999,2000-2004,2005-2009
// 2001..2006 -> 2001-2005
aggregate(yearFrom, yearTo);
}
if(remainder > 0)
{
int yearFrom = minYear + 5*fullFives
int yearTo = minYear + maxYear
// 1970..2012 -> 2010-2012
// 2001..2006 -> 2006-2006
aggregate(yearFrom, yearTo);
}
This is written "out of thin air", I've not checked/compiled it - it is just to sketch the idea.
Note: you've said that everything works but sometimes "a number of scenarios where the product reaches 100% ownership before the end of term." - that would suggest that you rather have an error in the calculations, not in the looping. If the error were in the loop or year boundary detection, then probably almost all would be off. It's hard to say without more of the calculating code is revealed.
The code sample will fire on years 0, 5, 10 etc rather than for every year.
If you just need the number of years to aggregate when that code fires, and the term can be set in advance when a product reaches 100% ownership early, I think this would work:
int term = 22;
int reviewperiod = 5;
for (int year = 0; year < term; year++)
{
if (year % reviewperiod == 0)
{
var endyear = Math.Min(year + reviewperiod, term);
Console.WriteLine("Aggregate years {0} to {1}, {2} Aggregations ", year, endyear, endyear - year);
}
}
Do you think of something like
private int reviewPeriod = 5;
public void Aggregate(int term)
{
Enumerable.Range(0, term)
.ToList()
.Foreach(this.AggregateYear);
}
when this.AggregateYear is defined as follows
public void AggregateYear(int year)
{
var currentRemainder = year % this.reviewPeriod;
var aggregatePeriod = (currentRemainder == 0)
? this.reviewPeriod
: currentRemainder;
this.PerformAggregation(aggregatePeriod);
}
and this.PerformAggregation is defined as follows
private void PerformAggregation(int aggregatePeriod)
{
//...
}
Assuming this data is in memory (since you have not specified otherwise), then you can just use the GroupBy function from Linq:
struct YearValue
{
public int Year, Value;
}
static void Main()
{
// Create some data, hopefully representative of what you are dealing with...
Random r = new Random();
YearValue[] dataValues = new YearValue[22];
for (int i = 0; i < dataValues.Length; i++)
dataValues[i] = new YearValue {Year = i, Value = r.Next(200)};
// Average of values across 'ReviewPeriod' of five:
foreach (var item in dataValues.AsEnumerable().GroupBy(i => i.Year / 5))
{
YearValue[] items = item.ToArray();
Console.WriteLine("Group {0} had {1} item(s) averaging {2}",
item.Key,
items.Length,
items.Average(i => i.Value)
);
}
}
This program then outputs the following text:
Group 0 had 5 item(s) averaging 143.6
Group 1 had 5 item(s) averaging 120.4
Group 2 had 5 item(s) averaging 83
Group 3 had 5 item(s) averaging 145.2
Group 4 had 2 item(s) averaging 98.5

DateTime.AddDays or new DateTime

I'm creating a list of a month's worth of dates. I'm wondering what will be more efficient
List<DateTime> GetDates(DateTime StartDay) {
List<DateTime> dates = new List<DateTime>();
int TotalDays=StartDay.AddMonths(1).AddDays(-1).Day;
for (int i=1; i<TotalDays; i++) {
dates.Add(new DateTime(StartDay.Year, StartDay.Month, i));
}
return dates;
}
or
List<DateTime> GetDates(DateTime StartDay) {
List<DateTime> dates = new List<DateTime>();
DateTime NextMonth = StartDay.AddMonths(1);
for (DateTime curr=StartDay; !curr.Equals(NextMonth); curr=curr.AddDays(1)) {
dates.Add(curr);
}
return dates;
}
basically, is new DateTime() or DateTime.addDays more efficient.
UPDATE:
static void Main(string[] args) {
System.Diagnostics.Stopwatch sw=new System.Diagnostics.Stopwatch();
long t1, t2, total;
List<DateTime> l;
DateTime begin = DateTime.Now;
total = 0L;
for (int i=0; i<10; i++) {
sw.Start();
l = GetDates(begin);
sw.Stop();
sw.Stop();
t1 = sw.ElapsedTicks;
sw.Reset();
sw.Start();
l = GetDates2(begin);
sw.Stop();
t2=sw.ElapsedTicks;
total += t1- t2;
Console.WriteLine("Test {0} : {1} {2} : {3}", i,t1,t2, t1- t2);
}
Console.WriteLine("Total: {0}", total);
Console.WriteLine("\n\nDone");
Console.ReadLine();
}
static List<DateTime> GetDates(DateTime StartDay) {
List<DateTime> dates = new List<DateTime>();
int TotalDays=StartDay.AddMonths(10000).AddDays(-1).Day;
for (int i=1; i<TotalDays; i++) {
dates.Add(new DateTime(StartDay.Year, StartDay.Month, i));
}
return dates;
}
static List<DateTime> GetDates2(DateTime StartDay) {
List<DateTime> dates = new List<DateTime>();
DateTime NextMonth = StartDay.AddMonths(10000);
for (DateTime curr=StartDay; !curr.Equals(NextMonth); curr=curr.AddDays(1)) {
dates.Add(curr);
}
return dates;
}
Test 0 : 2203229 63086205 : -60882976
Test 1 : 63126483 102969090 : -39842607
Test 2 : 102991588 93487982 : 9503606
Test 3 : 93510942 69439034 : 24071908
Test 4 : 69465137 70660555 : -1195418
Test 5 : 70695702 68224849 : 2470853
Test 6 : 68248593 63555492 : 4693101
Test 7 : 63578536 65086357 : -1507821
Test 8 : 65108190 64035573 : 1072617
Test 9 : 64066128 64933449 : -867321
Total: -62484058
Done
results are consistently negative... way negative, so, looks like the constructor and integer test is the more efficient method.
Measure it - write a test program and see which one takes less time.
I believe datetime operations return new datetime structures so you will be creating new instances either way.
http://msdn.microsoft.com/en-us/library/system.datetime.aspx
Unless you are doing some financial processing then I would worry more about readability than performance here. Only start worrying about performance somewhere like here if it's a proven bottleneck.
Since they both do the same thing in the end, there isn't much of a difference.
If you're looking for efficiency, just use ticks. All (that I've seen) calls in DateTime are eventually converted into ticks before any math gets done.
It's really hard to imagine a case in which this would make a significant difference, but Reflector shows that the AddDays technique should be more efficient.
Compare the core logic of AddDays (from Add(Double, Int32))
long num = (long) ((value * scale) + ((value >= 0.0) ? 0.5 : -0.5));
if ((num <= -315537897600000L) || (num >= 0x11efae44cb400L)) {
// Throw omitted
}
return this.AddTicks(num * 0x2710L);
To the core logic of the DateTime(int, int, int) constructor (from DateToTicks):
if (((year >= 1) && (year <= 0x270f)) && ((month >= 1) && (month <= 12)))
{
int[] numArray = IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;
if ((day >= 1) && (day <= (numArray[month] - numArray[month - 1])))
{
int num = year - 1;
int num2 = ((((((num * 0x16d) + (num / 4)) - (num / 100)) + (num / 400)) + numArray[month - 1]) + day) - 1;
return (num2 * 0xc92a69c000L);
}
}
// Throw omitted
AddDays just converts the specified number of days to the equivalent number of ticks (a long) and adds it to the existing ticks.
Creating a new DateTime using the year/month/day constructor requires many more calculations. That constructor has to check whether the specified year is a leap year, allocate an array of days in each month, perform a bunch of extra operations, just to finally get the number of ticks those three numbers represent.
Edit: DateTime.AddDays(int) is faster than new DateTime(int, int, int), but your first algorithm is faster than the second algorithm. This is probably because the iteration costs are much higher in the second algorithm. As you observed in your edit, this might well be because DateTime.Equals is more expensive than comparing integers.
Here is a working test program, with the algorithms implemented so that they can actually be compared (they still need work, though):
class Program
{
static void Main(string[] args)
{
IList<DateTime> l1, l2;
DateTime begin = new DateTime(2000, 1, 1);
Stopwatch timer1 = Stopwatch.StartNew();
for (int i = 0; i < 10000; i++)
l1 = GetDates(begin);
timer1.Stop();
Stopwatch timer2 = Stopwatch.StartNew();
for (int i = 0; i < 10000; i++)
l2 = GetDates2(begin);
timer2.Stop();
Console.WriteLine("new DateTime: {0}\n.AddDays: {1}",
timer1.ElapsedTicks, timer2.ElapsedTicks);
Console.ReadLine();
}
static IList<DateTime> GetDates(DateTime StartDay)
{
IList<DateTime> dates = new List<DateTime>();
int TotalDays = DateTime.DaysInMonth(StartDay.Year, StartDay.Month);
for (int i = 0; i < TotalDays; i++)
dates.Add(new DateTime(StartDay.Year, StartDay.Month, i + 1));
return dates;
}
static IList<DateTime> GetDates2(DateTime StartDay)
{
IList<DateTime> dates = new List<DateTime>();
DateTime NextMonth = StartDay.AddMonths(1);
for (DateTime curr = StartDay; !curr.Equals(NextMonth); curr = curr.AddDays(1))
dates.Add(curr);
return dates;
}
} // class
Output (I added the commas):
new DateTime: 545,307,375
.AddDays: 180,071,512
These results seem pretty clear to me, though honestly I thought they'd be a lot closer.
I agree with Mark. Test both methods yourself and see which one is faster. Use the Stopwatch class to get accurate timings of how long each method takes to run. My first guess is that since both end up creating new structures anyway, that any speed difference will be negligible. Also, with only generating a month's worth of dates (31 days maximum), I don't think either method will be that much slower than the other. Perhaps is you you were generating thousands or millions of dates, it would make a difference, but for 31 dates, it's probably premature optimization.

Categories

Resources