I have these 3 strings (using # as a delimiter):
Name#startTime#endTime#room
Meeting#19:00:00#20:30:00#Conference
Hist 2368#19:00:00#20:30:00#Large Conference Room
Hist 2368#09:00:00#10:30:00#Large Conference Room
I want to know how would I generate this
Conference 9:00:00 19:00:00
Large Conference Room 10:30:00 20:30:00
Large Conference Room 20:30:00 22:00:00
So what this is generating are the times of a room that are free. In the top 3 strings we can see that Conference is occupied from 19:00:00 to 20:30:00 so the free time is 9:00:00 to 19:00:00 (A day starts at 9:00:00 and ends at 22:00:00).
So to make this task relatively easy you need to define a class that understands how to split a period of time given a potentially overlapping period of time.
Here's that class:
private sealed class Period : IEquatable<Period>
{
public DateTime StartTime { get; private set; }
public DateTime EndTime { get; private set; }
public Period(DateTime startTime, DateTime endTime)
{
this.StartTime = startTime;
this.EndTime = endTime;
}
public override bool Equals(object obj)
{
if (obj is Period)
return Equals((Period)obj);
return false;
}
public bool Equals(Period obj)
{
if (obj == null)
return false;
if (!EqualityComparer<DateTime>.Default.Equals(
this.StartTime, obj.StartTime))
return false;
if (!EqualityComparer<DateTime>.Default.Equals(
this.EndTime, obj.EndTime))
return false;
return true;
}
public override int GetHashCode()
{
int hash = 0;
hash ^= EqualityComparer<DateTime>.Default
.GetHashCode(this.StartTime);
hash ^= EqualityComparer<DateTime>.Default
.GetHashCode(this.EndTime);
return hash;
}
public override string ToString()
{
return String.Format("{{ StartTime = {0}, EndTime = {1} }}",
this.StartTime, this.EndTime);
}
public IEnumerable<Period> Split(Period period)
{
if (period.StartTime <= this.StartTime)
{
if (period.EndTime <= this.StartTime)
yield return this;
else if (period.EndTime >= this.EndTime)
yield break;
else
yield return new Period(period.EndTime, this.EndTime);
}
else if (period.StartTime < this.EndTime)
{
yield return new Period(this.StartTime, period.StartTime);
if (period.EndTime < this.EndTime)
{
yield return new Period(period.EndTime, this.EndTime);
}
}
else
yield return this;
}
}
The important code here is the IEnumerable<Period> Split(Period period) method. It goes through each possible case when comparing two time periods and returns zero, one or two periods that can be left after the split.
So, given your input data is like this:
var lines = new []
{
"Meeting#19:00:00#20:30:00#Conference",
"Hist 2368#19:00:00#20:30:00#Large Conference Room",
"Hist 2368#09:00:00#10:30:00#Large Conference Room",
};
var full_day =
new Period(
DateTime.Parse("09:00"),
DateTime.Parse("22:00"));
I can then run this code to determine the free times:
var free_times =
from line in lines
let parts = line.Split('#')
let Start = DateTime.Parse(parts[1])
let End = DateTime.Parse(parts[2])
orderby Start, End
group new Period(Start, End) by parts[3] into groups
select new
{
Room = groups.Key,
FreePeriods =
groups.Aggregate(new [] { full_day },
(ys, x) => ys.SelectMany(y => y.Split(x)).ToArray()),
};
The result I get is:
NB: Your example results in the question do not match your data. I have assumed that your data is correct and ignored your example results.
Related
So basically I got the following assignment:
Returns the number of open count time in the given list of appointments.
Given appointments [9:30-10:00, 12:00-13:00, 15:15-16:30]
the result should be 4
[8:00-9:30, 10:00-12:00, 13:00-15:15, 16:30-17:00].
name="appointments">The list of current appointments
The number of open count time in the given list of appointments
What is the best way to check for open slots, without using a lot of IF statements?
This is what I got so far:
public int count (Appointment[] appointments)
{
int openslots = 0;
foreach (var t in appointments)
{
if (t.End > t.Start )
{
openslots++;
}
}
return openslots;
}
NOTE: "Appointmens[]" contains a list with all the appointments.
UPDATE 2: This is what I got so far:
Sometimes it helps to re-frame the problem in a way that makes it easier to tackle, and make sure you haven't missed anything. It can also help to draw a representation of the problem, depending on how visual you are - I think pictures make time range problems easier to grasp.
Given a time range [Tstart = 08:00, Tend = 17:00] and a list of non-overlapping sub-ranges (appointments), find the number of sub-ranges that are not covered by any range provided.
From the provided times we can draw a rough visualization of the plan like this:
8 9 10 11 12 13 14 15 16 17
+---+---+---+---+---+---+---+---+---+
| ■■ ■■■■ ■■■■■ |
Visually the breaks in the used time are clear, giving 4 breaks. If all of the possible appointment lists mapped out like the above then it'd be simple to calculate the number of open blocks as appointments.Length + 1. Here are some other options that don't fit that calculation:
8 9 10 11 12 13 14 15 16 17 Appointments to Gaps
+---+---+---+---+---+---+---+---+---+
■■■■■■■■ ■■■■ ■■■■■ | 3 : 3
| ■■■ ■■■■ ■■■■■■■■ 3 : 3
■■■■■■■■ ■■■■■■■■ 2 : 1
■■■■■■■■|■■■■■■■■■|■■■■■■■■|■■■■■■■■■ 4 : 0
We could go on and test edge cases for days, including how we deal with overlapping appointments, but a better option would be to scan the list and see what we get.
From your code it looks like your Appointment class defines a time range between Start and End. If we step through the list, tracking the end of the previous range, we can enumerate through the open times for the day like this:
static TimeSpan DayStart = DateTime.Parse("08:00");
static TimeSpan DayEnd = DateTime.Parse("17:00");
int CountOpenTimeSlots(Appointment[] appointments)
{
int count = 0;
// Tracking variable for the end of the last closed slot.
var previousEnd = DayStart;
foreach (var appointment in Appointments)
{
// check the length of time between previous end and current start
var openTime = appointment.Start - previousEnd;
if (openTime.TotalMinutes > 0)
count++;
// update tracking
previousEnd = appointment.End;
}
// Finally, check for open slot at the end of the day
if ((DayEnd - previousEnd).TotalMinutes > 0)
count++;
return count;
}
This assumes that the appointment list is already sorted, and that none of the appointments end before the start of the day or start after the end of the day, and will fail if you have any total overlaps (one appointment completely inside another).
The above can be fairly easily converted to actually generate a list of open timeslots instead of just counting them. And we can make it a little more general, allowing for specification of the work range, filter out some bad inputs, etc.
class TimeRange { public TimeSpan Start, End; }
IEnumerable<TimeRange> EnumerateOpenSlots(TimeSpan DayStart, TimeSpan DayEnd, Appointment[] appointments)
{
// clean up source list
var filtered = appointments
.Where(o => o.End >= DayStart && o.Start < DayEnd)
.OrderBy(o => o.Start);
var previousEnd = DayStart;
foreach (var range in filtered)
{
// skip total overlaps
if (range.End < previousEnd)
continue;
var openTime = range.Start - previousEnd;
if (openTime.TotalMinutes > 0)
yield return new TimeRange { Start = previousEnd, End = range.Start };
previousEnd = range.End;
}
if ((DayEnd - previousEnd).TotalMinutes > 0)
yield return new TimeRange { Start = previousEnd, End = DayEnd };
}
Now we can get the count using LINQ extensions:
int CountOpenTimeSlots(TimeRange[] appointments)
=> EnumerateOpenSlots(DayStart, DayEnd, appointsments).Count();
Depends on what you mean by "best". If, for instance, you mean concise, perhaps:
appointments.Count(t => t.End > t.Start)
Sometimes I find it's useful to have a class or struct that does the hard lifting, that you write once, but use over and over again to make these kinds of calculations easy.
Here's my Period struct that does that:
private struct Period : IEquatable<Period>
{
public DateTime StartTime { get; private set; }
public DateTime EndTime { get; private set; }
public Period(DateTime startTime, DateTime endTime)
{
this.StartTime = startTime;
this.EndTime = endTime;
}
public override bool Equals(object obj)
{
if (obj is Period)
return Equals((Period)obj);
return false;
}
public bool Equals(Period obj)
{
if (!EqualityComparer<DateTime>.Default.Equals(this.StartTime, obj.StartTime))
return false;
if (!EqualityComparer<DateTime>.Default.Equals(this.EndTime, obj.EndTime))
return false;
return true;
}
public override int GetHashCode()
{
int hash = 0;
hash ^= EqualityComparer<DateTime>.Default.GetHashCode(this.StartTime);
hash ^= EqualityComparer<DateTime>.Default.GetHashCode(this.EndTime);
return hash;
}
public override string ToString()
{
return $"{{ StartTime = {this.StartTime}, EndTime = {this.EndTime} }}";
}
public IEnumerable<Period> Fragment(Period that)
{
if (this.StartTime < that.StartTime)
{
if (this.EndTime <= that.StartTime)
{
yield return this;
yield return that;
}
else if (this.EndTime < that.EndTime)
{
yield return new Period(this.StartTime, that.StartTime);
yield return new Period(that.StartTime, this.EndTime);
yield return new Period(this.EndTime, that.EndTime);
}
else if (this.EndTime == that.EndTime)
{
yield return new Period(this.StartTime, that.StartTime);
yield return that;
}
else if (this.EndTime > that.EndTime)
{
yield return new Period(this.StartTime, that.StartTime);
yield return that;
yield return new Period(that.EndTime, this.EndTime);
}
}
else if (this.StartTime == that.StartTime)
{
if (this.EndTime < that.EndTime)
{
yield return this;
yield return new Period(this.EndTime, that.EndTime);
}
else if (this.EndTime == that.EndTime)
{
yield return this;
}
else if (this.EndTime > that.EndTime)
{
yield return that;
yield return new Period(that.EndTime, this.EndTime);
}
}
else if (this.StartTime < that.EndTime)
{
if (this.EndTime < that.EndTime)
{
yield return new Period(that.StartTime, this.StartTime);
yield return this;
yield return new Period(this.EndTime, that.EndTime);
}
else if (this.EndTime == that.EndTime)
{
yield return new Period(that.StartTime, this.StartTime);
yield return this;
}
else if (this.EndTime > that.EndTime)
{
yield return new Period(that.StartTime, this.StartTime);
yield return new Period(this.StartTime, that.EndTime);
yield return new Period(that.EndTime, this.EndTime);
}
}
else // (this.StartTime >= that.EndTime)
{
yield return that;
yield return this;
}
}
}
It's job is to represent a period in time and to be able to fragment two periods together.
For example, if I write this code:
DateTime today = DateTime.Now.Date;
var p1 = new Period(today.AddHours(9.0), today.AddHours(10.0));
var p2 = new Period(today.AddHours(9.5), today.AddHours(10.5));
var fragments = p1.Fragment(p2);
Then I get out three periods 09:00-09:30, 09:30-10:00, 10:00-10:30.
Now it's easy to represent your appointments and your full day as periods:
Period[] appointments = new[]
{
new Period(today.AddHours(9.5), today.AddHours(10.0)),
new Period(today.AddHours(12.0), today.AddHours(13.0)),
new Period(today.AddHours(15.25), today.AddHours(16.5)),
};
Period[] full_day = new[]
{
new Period(today.AddHours(9.0), today.AddHours(17.0)),
};
Computing the free slots is an easy bit of LINQ:
Period[] free_slots =
appointments
.Aggregate(full_day, (a, x) => a.SelectMany(y => y.Fragment(x)).ToArray())
.Except(appointments)
.ToArray();
That gives me: 09:00-09:30, 10:00-12:00, 13:00-15:15, 16:30-17:00.
And finally it's super easy to get the count by calling free_slots.Length.
It's a handy struct to have for these kinds of problems.
I have this challange that I finished which asked to print out a string according to the provided schedule. Here is an example:
var restaurant = new Restaurant(
new OpeningHour(8,16), // Sunday
new OpeningHour(8,17), // Monday
new OpeningHour(8,17), // Tuesday
new OpeningHour(8,17), // Wednesday
new OpeningHour(8,16), // Thursday
new OpeningHour(8,16), // Friday
new OpeningHour(8,16) // Saturday
);
expected output result = "Sun, Thu - Sat: 8-16, Mon - Wed: 8-17"
What I did was essentially:
Create a List of Days, OpenHours, and CloseHours
Create a HashSet of the days so that I can compare the days
Create a for loop according to HashSet and Days
Seperate the Start, Middle, and Ending
Concatenate the result according to the open and close hours as well as the gap between days
I have tried my best but I know for a fact that my code is not efficient at all, instead messy. I am trying to improve my C# skills please help. Here is my messy code:
namespace Livit
{
using System;
using System.Collections.Generic;
using System.Linq;
public class Restaurant
{
public WeekCollection<OpeningHour> OpeningHours { get; private set; }
public Restaurant() {
// No opening hours available for restaurant
}
public Restaurant(OpeningHour monday, OpeningHour tuesday, OpeningHour wednesday, OpeningHour thursday, OpeningHour friday, OpeningHour saturday, OpeningHour sunday)
{
OpeningHours = new WeekCollection<OpeningHour>(monday, tuesday, wednesday, thursday, friday, saturday, sunday);
}
// THE EMPHASIS OF THE CHALLANGE IS THIS FUNCTION RIGHT HERE!!!
// Parse the date into desired format
public string DateParser(List<DayOfWeek> days, List<TimeSpan> openHours, List<TimeSpan> closeHours)
{
HashSet<string> availableRanges = new HashSet<string>();
List<string> timeRanges = new List<string>();
DayOfWeek current = DayOfWeek.Sunday;
string result = "";
for (int i = 0 ; i < days.Count; i++){
string timeRange = openHours[i].ToString().Substring(1,1)+'-'+closeHours[i].ToString().Substring(0,2);
availableRanges.Add(timeRange);
timeRanges.Add(timeRange);
}
List<string> arToList= availableRanges.ToList();
for (int i = 0 ; i < arToList.Count; i++)
{
for (int j = 0 ; j < timeRanges.Count; j++){
if(timeRanges[j] == arToList[i]){
// First Item
if(j==0 ){
result += days[j].ToString().Substring(0,3);
}
// Last Item
else if(j==timeRanges.Count-1){
char last = result.Last();
if(last != ' '){
result += " - ";
}
result += days[j].ToString().Substring(0,3);
}
// Everything in the middle
else{
if(days[j]-current > 1){
result += ", ";
}
if(timeRanges[j] != timeRanges[j-1] ){
result += days[j].ToString().Substring(0,3);
} else if (timeRanges[j] == timeRanges[j-1]){
char last = result.Last();
if(last != ' '){
result += " - ";
}
if(timeRanges[j] != timeRanges[j+1]){
result += days[j].ToString().Substring(0,3);
}
}
}
current = days[j];
}
}
result += ": " + arToList[i];
if(i!=arToList.Count-1){
result += ", ";
}
}
Console.WriteLine(result);
return result;
}
public string GetOpeningHours()
{
// Declare List for each attribute
List<DayOfWeek> days = new List<DayOfWeek>();
List<TimeSpan> openHours = new List<TimeSpan>();
List<TimeSpan> closeHours = new List<TimeSpan>();
// Call the opening and closing hours from each day and feed into new array
foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek)).OfType<DayOfWeek>().ToList()) {
TimeSpan openHour = OpeningHours.Get(day).OpeningTime;
TimeSpan closeHour = OpeningHours.Get(day).ClosingTime;
days.Add(day);
openHours.Add(openHour);
closeHours.Add(closeHour);
}
return DateParser(days,openHours,closeHours);
throw new NotImplementedException();
}
}
public class OpeningHour
{
public TimeSpan OpeningTime { get; private set; }
public TimeSpan ClosingTime { get; private set; }
public OpeningHour(TimeSpan openingTime, TimeSpan closingTime)
{
OpeningTime = openingTime;
ClosingTime = closingTime;
}
public OpeningHour(int openingHour, int closingHour)
{
OpeningTime = TimeSpan.FromHours(openingHour);
ClosingTime = TimeSpan.FromHours(closingHour);
}
}
public class WeekCollection<T>
{
private Dictionary<DayOfWeek, T> _collection;
public WeekCollection(T sunday, T monday, T tuesday, T wednesday, T thursday, T friday, T saturday)
{
_collection = new Dictionary<DayOfWeek, T>();
_collection.Add(DayOfWeek.Sunday, sunday);
_collection.Add(DayOfWeek.Monday, monday);
_collection.Add(DayOfWeek.Tuesday, tuesday);
_collection.Add(DayOfWeek.Wednesday, wednesday);
_collection.Add(DayOfWeek.Thursday, thursday);
_collection.Add(DayOfWeek.Friday, friday);
_collection.Add(DayOfWeek.Saturday, saturday);
}
public T Get(DayOfWeek dayOfWeek)
{
return _collection[dayOfWeek];
}
}
}
Currently, I am still trying to find a better way in doing this challange. Any help would be appriciated.
P.S. I highlighted the part where my concatenation is occuring, this part is basically the emphasis of the whole challange
Let's say I need to find out when the next scheduled date is when I know that the schedule was based off a start date of 8/1/2014, it's supposed to run every 7 days and the current date is 8/10/2014. I should get back a date of 8/14/2014. I eventually want to make this code work for every X hours, days, and weeks, bur right now I'm just testing with days. I have the following code I'm using to calculate the next run time, but I get it to work for one date and then it fails for another. FYI, I'm using the option to specify the current date for testing purposes. What am I doing wrong?
public class ScheduleComputer
{
public DateTime GetNextRunTime(ScheduleRequest request)
{
var daysSinceBase = ((int)((request.CurrentDate - request.BaseDate).TotalDays)) + 1;
var partialIntervalsSinceBaseDate = daysSinceBase % request.Interval;
var fullIntervalsSinceBaseDate = daysSinceBase / request.Interval;
var daysToNextRun = 0;
if (partialIntervalsSinceBaseDate > 0)
{
daysToNextRun = (request.Interval - partialIntervalsSinceBaseDate) + 1;
}
var nextRunDate = request.BaseDate.AddDays((fullIntervalsSinceBaseDate * request.Interval) + daysToNextRun - 1);
return nextRunDate;
}
}
public class ScheduleRequest
{
private readonly DateTime _currentDate;
public ScheduleRequest()
{
_currentDate = DateTime.Now;
}
public ScheduleRequest(DateTime currentDate)
{
_currentDate = currentDate;
}
public DateTime CurrentDate
{
get { return _currentDate; }
}
public DateTime BaseDate { get; set; }
public Schedule Schedule { get; set; }
public int Interval { get; set; }
}
public enum Schedule
{
Hourly,
Daily,
Weekly
}
And here are my unit tests
[TestFixture]
public class ScheduleComputerTests
{
private ScheduleComputer _scheduleComputer;
[SetUp]
public void SetUp()
{
_scheduleComputer = new ScheduleComputer();
}
[Test]
public void ThisTestPassesAndItShould()
{
var scheduleRequest = new ScheduleRequest(currentDate: DateTime.Parse("8/14/2014"))
{
BaseDate = DateTime.Parse("8/1/2014"),
Schedule = Schedule.Daily,
Interval = 7
};
var result = _scheduleComputer.GetNextRunTime(scheduleRequest);
Assert.AreEqual(DateTime.Parse("8/14/2014"), result);
}
[Test]
public void ThisTestFailsAndItShouldNot()
{
var scheduleRequest = new ScheduleRequest(currentDate: DateTime.Parse("8/2/2014"))
{
BaseDate = DateTime.Parse("8/1/2014"),
Schedule = Schedule.Daily,
Interval = 7
};
var result = _scheduleComputer.GetNextRunTime(scheduleRequest);
Assert.AreEqual(DateTime.Parse("8/7/2014"), result);
}
FYI, I saw the post here, but I can't seem to tailor it to my needs.
--- UPDATE 1 ---
Here is my updated code. I know I've made it verbose with variables so I can understand the logic better (hopefully that doesn't impact performance much). I also added logic to deal with different periods (hours, days, weeks) and added extension methods to make the code somewhat cleaner. However, this code seems to be working perfectly for hours and days, but is failing on weeks. Somewhere I'm not multiplying or dividing by 7 properly.
public class ScheduleComputer
{
public DateTime GetNextRunTime(ScheduleRequest request)
{
var timeBetwenCurrentAndBase = request.CurrentDate - request.BaseDate;
var totalPeriodsBetwenCurrentAndBase = timeBetwenCurrentAndBase.TotalPeriods(request.Schedule);
var fractionalIntervals = totalPeriodsBetwenCurrentAndBase % request.Interval;
var partialIntervalsLeft = request.Interval - fractionalIntervals;
if (request.Schedule != Schedule.Hourly) partialIntervalsLeft = partialIntervalsLeft - 1;
var nextRunTime = request.CurrentDate.AddPeriods(partialIntervalsLeft, request.Schedule);
return nextRunTime;
}
}
public static class ScheduleComputerExtensions
{
public static double TotalPeriods(this TimeSpan timeBetwenCurrentAndBase, Schedule schedule)
{
switch (schedule)
{
case Schedule.Hourly: return timeBetwenCurrentAndBase.TotalHours;
case Schedule.Daily: return timeBetwenCurrentAndBase.TotalDays;
case Schedule.Weekly: return timeBetwenCurrentAndBase.TotalDays * 7;
default: throw new ApplicationException("Invalid Schedule Provided");
}
}
public static DateTime AddPeriods(this DateTime dateTime, double partialIntervalsLeft, Schedule schedule)
{
switch (schedule)
{
case Schedule.Hourly: return dateTime.AddHours(partialIntervalsLeft);
case Schedule.Daily: return dateTime.AddDays(partialIntervalsLeft);
case Schedule.Weekly: return dateTime.AddDays(partialIntervalsLeft * 7);
default: throw new ApplicationException("Invalid Schedule Provided");
}
}
}
Try replacing your GetNextRunTime with this
public DateTime GetNextRunTime(ScheduleRequest request)
{
double days = (request.Interval - ((request.CurrentDate - request.BaseDate).TotalDays % request.Interval));
return request.CurrentDate.AddDays(days-1);
}
That should give you the correct dates.
EDIT: Let's break it down in hopes of helping you figure out the logic.
diff = (request.CurrentDate - request.BaseDate).TotalDays this gives you the number of days between the BaseDate and CurrentDate. Note that the number of days DOES NOT INCLUDE the day for BaseDate. So the different between 8/7/14 and 8/1/14 is 6 days.
daysSinceLast = diff % request.Interval this gives you the number of days that have past since the last interval hit, so if the last interval hit on 8/1/14 and it is now 8/7/14 then the result would be 6 % 7 = 6; 6 days have past since the last scheduled interval (not including the last interval date). This is the most important part of the calculation; It keeps the number of days, no matter how many have passed within the interval, so for example, if 100 days have passed since the BaseDate and the interval is 7: 100 % 7 = 2 which means that 2 days have passed since the last interval triggered, there is no need to actually know the last date it was triggered. All you need is the BaseDate and CurrentDate. You could use this logic to find the date of the last triggered interval, just subtract the number of days from the CurrentDate.
daysUntil = request.Interval - daysSinceLast This gives you the number of days until the next scheduled interval. 7 - 6 = 1 day until the next scheduled interval
1 day in this scenario is not correct and the result will never be correct because the calculation of TimeSpan differences does not include the day for BaseDate, so you need to subtract 1 from the daysUntil nextDate = request.CurrentDate.AddDays(daysUntil - 1)
Adding the number of remaining days (minus 1 for the base date) to the current date gives you the required value. Does this help at all?
UPDATE
In light of your testing, I see that the problem is on both of our ends. My calculation was incorrect and you were multiplying by 7 when you needed to divide by 7. Either way, the result was still wrong. Try this instead.
Remove your extension class completely
Modify your GetNextRunTime with the below code
Modify your ScheduleRequest and Schedule class/enum with the below code
GetNextRunTime
public DateTime GetNextRunTime(ScheduleRequest request)
{
double diffMillis = (request.CurrentDate - request.BaseDate).TotalMilliseconds;
double modMillis = (diffMillis % request.IntervalMillis);
double timeLeft = (request.IntervalMillis - modMillis);
ulong adjust = (request.Schedule == Schedule.Daily) ? (ulong)Schedule.Daily : 0;
return request.CurrentDate.AddMilliseconds(timeLeft - adjust);
}
ScheduleRequest
public class ScheduleRequest
{
private readonly DateTime _currentDate;
public ScheduleRequest()
{
_currentDate = DateTime.Now;
}
public ScheduleRequest(DateTime currentDate)
{
_currentDate = currentDate;
}
public DateTime CurrentDate
{
get { return _currentDate; }
}
public DateTime BaseDate { get; set; }
public Schedule Schedule { get; set; }
public double IntervalMillis { get { return (double)this.Schedule * this.Interval; } }
public int Interval { get; set; }
}
Schedule
public enum Schedule : ulong
{
Hourly = 3600000,
Daily = 86400000,
Weekly = 604800000
}
This should work correctly for all dates, intervals and schedules. EDIT: corrected adjust value
I have a number of objects each with 3 numerical properties: "high", "low" and "tiebreaker". They are to be sorted as such: if an object's low is higher than another object's high, it appears before it in the list. Likewise if an object's high is lower than another's low, it appears later in the list. But in the case that two objects have conflicting ranges (eg one's high is between the other object's low and high), the tiebreaker property is considered wherein the object with the higher tiebreaker value gets placed earlier on the list.
I am specifically working with c#, but I think the ideas here are language agnostic enough such that code of any sort (no puns) would be welcome.
Also, I have worked on this myself. I have a nested for-loop that is just not working out for me so far. I'd give up some code but I'm on my phone and that makes it a chore. Besides, this is probably a fun one for you and you don't need my ugly code in your way anyhow.
Are you assuming that Min <= Tie <= Max? You do not say so in your question, and if you do not, the sort order is not well defined because it is not transitive. For instance, writing your ranges as [Min, Tie, Max], consider:
A: [5,-10, 6]
B: [0, 1, 10]
C: [2, 3, 4]
A < B (because they overlap and -10 < 1)
B < C (because they overlap and 1 < 3)
but A > C (because they don't overlap and 5 > 4)
If they are you can define a custom IComparer<Range> for your Range class, and pass it to any c# sort method.
Update and here's one such implementation.
public struct RangeWithTie<T> where T : IEquatable<T>, IComparable<T>
{
readonly T min;
readonly T max;
readonly T tie;
readonly bool isNonEmpty;
public static Range<T> Empty = new Range<T>();
public static IComparer<RangeWithTie<T>> CreateSortingComparer()
{
return new RangeWithTieComparer();
}
public RangeWithTie(T start, T tie, T end)
{
// Enfore start <= tie <= end
var comparer = Comparer<T>.Default;
if (comparer.Compare(start, end) > 0) // if start > end
{
throw new ArgumentOutOfRangeException("start and end are reversed");
}
else if (comparer.Compare(start, tie) > 0)
{
throw new ArgumentOutOfRangeException("tie is less than start");
}
else if (comparer.Compare(tie, end) > 0)
{
throw new ArgumentOutOfRangeException("tie is bigger than end");
}
else
{
this.min = start;
this.max = end;
this.tie = tie;
}
this.isNonEmpty = true;
}
public T Min { get { return min; } }
public T Max { get { return max; } }
public T Tie { get { return tie; } }
public bool IsEmpty { get { return !isNonEmpty; } }
public class RangeWithTieComparer : IComparer<RangeWithTie<T>>
{
#region IComparer<RangeWithTie<T>> Members
public int Compare(RangeWithTie<T> x, RangeWithTie<T> y)
{
// return x - y.
if (x.IsEmpty)
{
if (y.IsEmpty)
return 0;
else
return -1;
}
else if (y.IsEmpty)
{
return 1;
}
var comparer = Comparer<T>.Default;
if (comparer.Compare(y.Min, x.Max) > 0)
return -1;
else if (comparer.Compare(x.Min, y.Max) > 0)
return 1;
return comparer.Compare(x.Tie, y.Tie);
}
#endregion
}
public override string ToString()
{
if (IsEmpty)
return "Empty";
StringBuilder s = new StringBuilder();
s.Append('[');
if (Min != null)
{
s.Append(Min.ToString());
}
s.Append(", ");
if (Tie != null)
{
s.Append(Tie.ToString());
}
s.Append(", ");
if (Max != null)
{
s.Append(Max.ToString());
}
s.Append(']');
return s.ToString();
}
}
This could be used like so:
var sortedRanges = ranges.OrderBy(x => x, RangeWithTie<double>.CreateSortingComparer()).ToArray();
I didn't make the struct implement IComparer<RangeWithTie<T>> directly because ranges with identical comparisons aren't necessarily equal. For instance, [-1,0,1] and [-2,0,1] have identical comparisons but are not equal.
A quick solution, and a console application to test it. This method will return the larger of two objects. Just replace dynamic with the appropriate object type you need.
class Program
{
private static object Sort(dynamic first, dynamic second)
{
if (OverlapExists(first, second))
{
// Note: If tiebreakers are equal, the first will be returned:
return first.tiebreaker >= second.tiebreaker ? first : second;
}
else
{
// Note: Only need to test one value (just high); Since we know
// there is no overlap, the whole object (both high and low) must
// be either over or under that which it is compared to:
return first.high > second.high ? first : second;
}
}
private static bool OverlapExists(dynamic first, dynamic second)
{
return (first.low < second.high) && (second.low < first.high);
}
static void Main(string[] args)
{
dynamic first = new {name="first", high = 10,
tiebreaker = 5, low = 1 };
dynamic second = new {name="second", high = 15,
tiebreaker = 12, low = 11 };
dynamic third = new {name="third", high = 20,
tiebreaker = 9, low = 6 };
var firstResult = Sort(first, second);
var secondResult = Sort(first, third);
var thirdResult = Sort(second, third);
Console.WriteLine("1) " + first.ToString()
+ "\nVS: " + second.ToString());
Console.WriteLine("Winner: " + firstResult.name);
Console.WriteLine("\n2) " + first.ToString()
+ "\nVS: " + third.ToString());
Console.WriteLine("Winner: " + secondResult.name);
Console.WriteLine("\n3) " + second.ToString()
+ "\nVS: " + third.ToString());
Console.WriteLine("Winner: " + thirdResult.name);
Console.ReadKey();
}
}
Let’s say you have a List<T> (T being your objects with High-, Low- and Tie- Property), then you can use
list.Sort(…);
with a Comparison<T> as a Parameter. That’s a delegate that takes 2 of you objects and should return < 0, when the first instance of your object should be a head of the other instance or 0 if they are of equal order (or > 0 if the second second object should be ahead of first).
Or you could pass an custom comparer (implementing IComparer<T>) which does basically the same as the Comparison<T> but inform of an interface.
No matter what your logic is, you may implement IComparable to enable an Array or List's sorting capability. So, as the follow code shows,
public class MyStuff : IComparable<MyStuff>
{
public int High { get; set; }
public int Low { get; set; }
public int TieBreaker { get; set; }
public int CompareTo(MyStuff other)
{
// if an object's low is higher than another object's high,
// it appears before it in the list
if ((this.Low > other.High) ||
// if its high is between the other object's low and
// high then compare their tiebreaker
(this.High > other.Low && this.High < other.High &&
this.TieBreaker > other.TieBreaker))
return 1;
else if (this.Low == other.High)
return 0;
else
return -1;
}
}
The basic idea is CompareTo returns either 1 (move this before other), 0 (retain both positions) or -1 (move this after other), depending on your ordering logic.
See IComparable<T>
class DataObject : IComparable<DataObject>
{
public double High, Low, Tiebreaker;
public int CompareTo(DataObject obj)
{
// this doesn't seem to make sense as a range sort, but seems to match your question...
// low > another high
if (this.Low != obj.High)
return this.Low.CompareTo(obj.High);
// otherwise sort tiebreaker ascending
else this.TieBreaker.CompareTo(obj.TieBreaker);
}
}
used as
var items = new[] { new DataObject(1,2,3), new DataObject(4,5,6) };
Array.Sort<DataObject>(items);
// items is now sorted
I'm very much a vb person, but have had to use this id number class in c#. I got it from http://www.codingsanity.com/idnumber.htm :
namespace Utilities
{
[Serializable]
public class IdentityNumber
{
public enum PersonGender
{
Female = 0,
Male = 5
}
public enum PersonCitizenship
{
SouthAfrican = 0,
Foreign = 1
}
static Regex _expression;
Match _match;
const string _IDExpression = #"(?<Year>[0-9][0-9])(?<Month>([0][1-9])|([1][0-2]))(?<Day>([0-2][0-9])|([3][0-1]))(?<Gender>[0-9])(?<Series>[0-9]{3})(?<Citizenship>[0-9])(?<Uniform>[0-9])(?<Control>[0-9])";
static IdentityNumber()
{
_expression = new Regex(_IDExpression, RegexOptions.Compiled | RegexOptions.Singleline);
}
public IdentityNumber(string IDNumber)
{
_match = _expression.Match(IDNumber.Trim());
}
public DateTime DateOfBirth
{
get
{
if(IsUsable == false)
{
throw new ArgumentException("ID Number is unusable!", "IDNumber");
}
int year = int.Parse(_match.Groups["Year"].Value);
// NOTE: Do not optimize by moving these to static, otherwise the calculation may be incorrect
// over year changes, especially century changes.
int currentCentury = int.Parse(DateTime.Now.Year.ToString().Substring(0, 2) + "00");
int lastCentury = currentCentury - 100;
int currentYear = int.Parse(DateTime.Now.Year.ToString().Substring(2, 2));
// If the year is after or at the current YY, then add last century to it, otherwise add
// this century.
// TODO: YY -> YYYY logic needs thinking about
if(year > currentYear)
{
year += lastCentury;
}
else
{
year += currentCentury;
}
return new DateTime(year, int.Parse(_match.Groups["Month"].Value), int.Parse(_match.Groups["Day"].Value));
}
}
public PersonGender Gender
{
get
{
if(IsUsable == false)
{
throw new ArgumentException("ID Number is unusable!", "IDNumber");
}
int gender = int.Parse(_match.Groups["Gender"].Value);
if(gender < (int) PersonGender.Male)
{
return PersonGender.Female;
}
else
{
return PersonGender.Male;
}
}
}
public PersonCitizenship Citizenship
{
get
{
if(IsUsable == false)
{
throw new ArgumentException("ID Number is unusable!", "IDNumber");
}
return (PersonCitizenship) Enum.Parse(typeof(PersonCitizenship), _match.Groups["Citizenship"].Value);
}
}
/// <summary>
/// Indicates if the IDNumber is usable or not.
/// </summary>
public bool IsUsable
{
get
{
return _match.Success;
}
}
/// <summary>
/// Indicates if the IDNumber is valid or not.
/// </summary>
public bool IsValid
{
get
{
if(IsUsable == true)
{
// Calculate total A by adding the figures in the odd positions i.e. the first, third, fifth,
// seventh, ninth and eleventh digits.
int a = int.Parse(_match.Value.Substring(0, 1)) + int.Parse(_match.Value.Substring(2, 1)) + int.Parse(_match.Value.Substring(4, 1)) + int.Parse(_match.Value.Substring(6, 1)) + int.Parse(_match.Value.Substring(8, 1)) + int.Parse(_match.Value.Substring(10, 1));
// Calculate total B by taking the even figures of the number as a whole number, and then
// multiplying that number by 2, and then add the individual figures together.
int b = int.Parse(_match.Value.Substring(1, 1) + _match.Value.Substring(3, 1) + _match.Value.Substring(5, 1) + _match.Value.Substring(7, 1) + _match.Value.Substring(9, 1) + _match.Value.Substring(11, 1));
b *= 2;
string bString = b.ToString();
b = 0;
for(int index = 0; index < bString.Length; index++)
{
b += int.Parse(bString.Substring(index, 1));
}
// Calculate total C by adding total A to total B.
int c = a + b;
// The control-figure can now be determined by subtracting the ones in figure C from 10.
string cString = c.ToString() ;
cString = cString.Substring(cString.Length - 1, 1) ;
int control = 0;
// Where the total C is a multiple of 10, the control figure will be 0.
if(cString != "0")
{
control = 10 - int.Parse(cString.Substring(cString.Length - 1, 1));
}
if(_match.Groups["Control"].Value == control.ToString())
{
return true;
}
}
return false;
}
}
}
}
Can someone please tell the syntax for how I pass an id number to the class?
You'll have to use the constructor.
var someNumber = new IdentityNumber("123456");
Then, you can use the properties of that class to find out the specifics of that Id number.
Console.WriteLine (someNumber.DateOfBirth);
Console.WriteLine (someNumber.Gender);
Console.WriteLine (someNumber.Citizenship);
Console.WriteLine (someNumber.IsValid);
Console.WriteLine (someNumber.IsUsable);
IdentityNumber number = new IdentityNumber("123456");
All you need is to use provided Constructor like
IdentityNumber someNumber = new IdentityNumber("006834");