Trying to get array of all possible flags from enum value, say 3 to array of {1, 2}.
I have an extension
internal static MyEnum[] GetFlags(this MyEnum modKey)
{
string[] splitStr = modKey.ToString().Split(new string[1] { ", " }, StringSplitOptions.RemoveEmptyEntries);
MyEnum[] flags = new MyEnum[splitStr.Length];
for (int i = 0; i < splitStr.Length; i++)
{
flags[i] = (MyEnum)Enum.Parse(typeof(MyEnum), splitStr[i]);
}
return flags;
}
...but it seems a bit wasteful for the purpose. Could this be done more effectively?
You can simply filter all possible values of the MyEnum to the ones in modKey:
internal static MyEnum[] GetFlags(this MyEnum modKey)
{
return Enum.GetValues(typeof(MyEnum))
.Cast<MyEnum>()
.Where(v => modKey.HasFlag(v))
.ToArray();
}
Edit
Based on the comment below, in case of combinations specified, the method should only return the combinations, not all flags set.
The solution is to loop through all flags set in the enum starting from the highest one. In each iteration, we have to add a flag to the result, and remove it from the iterated enum until it's empty:
internal static MyEnum[] GetFlags(this MyEnum modKey)
{
List<MyEnum> result = new List<MyEnum>();
while (modKey != 0)
{
var highestFlag = Enum.GetValues(typeof(MyEnum))
.Cast<MyEnum>()
.OrderByDescending(v => v)
.FirstOrDefault(v => modKey.HasFlag(v));
result.Add(highestFlag);
modKey ^= highestFlag;
}
return result.ToArray();
}
assuming your MyEnum has a Flags Attribute, to test if a flag is set the (standard?) way is to perform a binary & between your value and the flag you want to test:
so something like this should work:
internal static MyEnum[] GetFlags(this MyEnum modKey)
{
List<MyEnum> flags = new List<MyEnum>();
foreach (var flag in Enum.GetValues(typeof(MyEnum)))
{
if (modKey & flag == flag)
flags.Add((MyEnum)flag);
}
return flags.ToArray();
}
if you use .Net 4 or later, you can use HasFlag
if (modKey.HasFlag((MyEnum)flag))
...
Both answers don't do what (I think) is asked: get the elementary values from an enum value, not any composed values. One example where this may be useful is when one enum value must be used in a Contains statement in LINQ to a SQL backend that doesn't support HasFlag.
For this purpose I first created a method that returns elementary flags from an enum type:
public static class EnumUtil
{
public static IEnumerable<TEnum> GetFlags<TEnum>()
where TEnum : Enum
{
return Enum.GetValues(typeof(TEnum))
.Cast<TEnum>()
.Where(v =>
{
var x = Convert.ToInt64(v); // because enums can be Int64
return x != 0 && (x & (x - 1)) == 0;
// Checks whether x is a power of 2
// Example: when x = 16, the binary values are:
// x: 10000
// x-1: 01111
// x & (x-1): 00000
});
}
}
And then a method that returns elementary flags from an enum value:
public static IEnumerable<TEnum> GetFlags<TEnum>(this TEnum enumValue)
where TEnum : Enum
{
return GetFlags<TEnum>()
.Where(ev => enumValue.HasFlag(ev));
}
Usage
Taking this enum type:
[Flags]
public enum WeekDay
{
Monday = 1 << 0,
Tuesday = 1 << 1,
Wednesday = 1 << 2,
Thursday = 1 << 3,
Friday = 1 << 4,
Saturday = 1 << 5,
Sunday = 1 << 6,
BusinessDay = Monday | Tuesday | Wednesday | Thursday | Friday,
WeekendDay = Saturday | Sunday,
All = BusinessDay | WeekendDay
}
The statements (in Linqpad)...
string.Join(",", EnumUtil.GetFlags<WeekDay>()).Dump();
var t = WeekDay.Thursday | WeekDay.WeekendDay;
string.Join(",", t.GetFlags()).Dump();
t = WeekDay.All;
string.Join(",", t.GetFlags()).Dump();
...return this:
Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
Thursday,Saturday,Sunday
Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
Basic idea taken from this answer to my question on Code Review.
Related
I have my own DaysOfWeek Flag enum (kind of https://learn.microsoft.com/en-us/previous-versions/ms886541(v=msdn.10))
[Flags]
public enum DayOfWeek
{
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 4,
Thursday = 8,
Friday = 16,
Saturday = 32
}
And I need to compare standard DayOfWeek with mine. How can I do that?
Since your enum uses the same order of days as the built-in DayOfWeek, all you need to do is to use the variable of DayOfWeek type as the exponent of 2 and then bitwise-AND it with your enum variable.
Something like this (this will check if Monday flag of your enum is 1 or not):
MyDayOfWeek Days = MyDayOfWeek.Monday | MyDayOfWeek.Friday;
DayOfWeek D = DayOfWeek.Monday;
var Mask = 1 << (int)D;
if ((Mask & (int)Days) == Mask)
//Do whatever;
I have renamed your enum to MyDaysOfWeek, whereas DayOfWeek is the built-in .NET type. You can do the same for any day of week.
Edit
As #HenkHolterman pointed out (thanks for that), you'll have problems with your Sunday set to 0. A Flags enum should normally start with a member named None that is equal to 0 and which indicates that none of the flags of the variable are set.
Thanks everyone for help.
Finally, I have solution
Own DaysOfWeek with flag:
[Flags]
public enum DaysOfWeek
{
None = 0,
Sunday = 1 << 0,
Monday = 1 << 1,
Tuesday = 1 << 2,
Wednesday = 1 << 3,
Thursday = 1 << 4,
Friday = 1 << 5,
Saturday = 1 << 6,
}
Since own enum has same order of days, we can write extension method to convert standard DayOfWeek to own
public static class EnumExtensions
{
public static DaysOfWeek ToFlag(this DayOfWeek dayOfWeek)
{
var mask = 1 << (int)dayOfWeek;
return (DaysOfWeek)Enum.ToObject(typeof(DaysOfWeek), mask);
}
}
And usage:
var days = DaysOfWeek.Sunday | DaysOfWeek.Friday;
var day = DayOfWeek.Sunday;
var ownDay = day.ToFlag();
if (days.HasFlag(ownDay))
// Do whatever;
playground: https://dotnetfiddle.net/sV3yge
public enum BitwiseDayOfWeek
{
Sunday = 1,
Monday = 2,
Tuesday = 4,
Wednesday = 8,
Thursday = 16,
Friday = 32,
Saturday = 64
}
public class Program
{
public static void Main(string[] args)
{
BitwiseDayOfWeek scheduledDayOfWeek
= BitwiseDayOfWeek.Saturday | BitwiseDayOfWeek.Sunday;
// turn System.DayOfWeek (DateTime.Now.DayOfWeek) into BitwiseDayOfWeek
BitwiseDayOfWeek currentDayOfWeek
= (BitwiseDayOfWeek)Math.Pow(2, (double)DateTime.Now.DayOfWeek);
// test if today is the scheduled day
if ( (currentDayOfWeek & scheduledDayOfWeek) == currentDayOfWeek )
Console.WriteLine(currentDayOfWeek);
Console.WriteLine("---------------------");
Console.ReadLine();
}
}
If you change your Enum like this:
[Flags]
public enum DayOfWeek
{
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6
}
You can try this:
class Program
{
public static bool Equal(DayOfWeek mine, System.DayOfWeek cSharp)
{
int mineInt = (int)mine;
int cSharpInt = (int)cSharp;
return mineInt == cSharpInt;
}
static void Main(string[] args)
{
DateTime dateTime = DateTime.Now;
DayOfWeek dayOfWeek = DayOfWeek.Sunday;
bool areEqual = Equal(dayOfWeek, dateTime.DayOfWeek);
Console.WriteLine(areEqual);
Console.ReadKey();
}
}
If you can't change your Enum, you can try this:
class Program
{
public static bool Equal(DayOfWeek mine, System.DayOfWeek cSharp)
{
if (mine == DayOfWeek.Friday && cSharp == System.DayOfWeek.Friday ||
mine == DayOfWeek.Monday && cSharp == System.DayOfWeek.Monday ||
mine == DayOfWeek.Saturday && cSharp == System.DayOfWeek.Saturday ||
mine == DayOfWeek.Sunday && cSharp == System.DayOfWeek.Sunday ||
mine == DayOfWeek.Thursday && cSharp == System.DayOfWeek.Thursday ||
mine == DayOfWeek.Tuesday && cSharp == System.DayOfWeek.Tuesday ||
mine == DayOfWeek.Wednesday && cSharp == System.DayOfWeek.Wednesday)
return true;
return false;
}
static void Main(string[] args)
{
DateTime dateTime = DateTime.Now;
DayOfWeek dayOfWeek = DayOfWeek.Tuesday;
bool areEqual = Equal(dayOfWeek, dateTime.DayOfWeek);
Console.WriteLine(areEqual);
Console.ReadKey();
}
}
I have the need to create scheduled windows tasks using a C# app. I have a comma separated string that stores the months I'd like to run the task on. The string contains the short values for the type MonthsOfYear - eg. "1,2,4,16,128,1024".
The example I have shows that you can assign multiple months seperated by a pipe as follows:
MonthlyTrigger mt = new MonthlyTrigger();
mt.StartBoundary = Convert.ToDateTime(task.getStartDateTime());
mt.DaysOfMonth = new int[] { 10, 20 };
mt.MonthsOfYear = MonthsOfTheYear.July | MonthsOfTheYear.November;
My question is, how do I assign multiple months to the trigger dynamically, using the values from the comma seperated string.
I'm not quite sure, what your problem is. And you didn't post code of your Trigger or your enum. Because of this i'll provide a complete example with a List for comparesion:
public class MonthlyTrigger
{
[Flags] // Important because we want to set multiple values to this type
public enum MonthOfYear
{
Jan = 1, // 1st bit
Feb = 2, // 2nd bit..
Mar = 4,
Apr = 8,
May = 16,
Jun = 32,
Jul = 64,
Aug = 128,
Sep = 256,
Oct = 512,
Nov = 1024,
Dec = 2048
}
public HashSet<int> Months { get; set; } = new HashSet<int>(); // classical list to check months
public MonthOfYear MonthFlag { get; set; } // out new type
}
public static void Main(string[] args)
{
MonthlyTrigger mt = new MonthlyTrigger();
string monthsFromFileOrSomething = "1,3,5,7,9,11"; // fake some string..
IEnumerable<int> splittedMonths = monthsFromFileOrSomething.Split(',').Select(s => Convert.ToInt32(s)); // split to values and convert to integers
foreach (int month in splittedMonths)
{
mt.Months.Add(month); // adding to list (hashset)
// Here we "add" another month to our Month-Flag => "Flag = Flag | Month"
MonthlyTrigger.MonthOfYear m = (MonthlyTrigger.MonthOfYear)Convert.ToInt32(Math.Pow(2, month - 1));
mt.MonthFlag |= m;
}
Console.WriteLine(String.Join(", ", mt.Months)); // let's see our list
Console.WriteLine(mt.MonthFlag); // what is contained in our flag?
Console.WriteLine(Convert.ToString((int)mt.MonthFlag, 2)); // how is it binarily-stored?
// Or if you like it in one row:
mt.MonthFlag = 0;
foreach (MonthlyTrigger.MonthOfYear m in monthsFromFileOrSomething.Split(',').Select(s => (MonthlyTrigger.MonthOfYear)Convert.ToInt32(s)))
mt.MonthFlag = m;
return;
}
Adding or removing single Flags in an enum:
MyEnumType myEnum = 1; // enum with first flag set
myEnum |= 2; // Adding the second bit. Ofcouse you can use the enum-name here "MyEnumType.MyValueForBitTwo"
// Becuase:
// 0000 0001
// | 0000 0010 "or"
// = 0000 0011
myEnum &= (int.MaxValue - 2) // Deletes the second enum-bit.
// Because:
// 0000 0011
// & 1111 1101 "and"
// = 0000 0001
I currently have some crude google code.. that works but I want to swap to an enum.
Currently I need a byte to represent some bit flags that are set,
I currently have this:
BitArray bitArray =new BitArray(new bool[] { true, true, false, false, false, false, false, false });
used in line..
new byte[] {ConvertToByte(bitArray)})
with ConvertToByte from this site...
private static byte ConvertToByte(BitArray bits) // http://stackoverflow.com/questions/560123/convert-from-bitarray-to-byte
{
if (bits.Count != 8)
{
throw new ArgumentException("incorrect number of bits");
}
byte[] bytes = new byte[1];
bits.CopyTo(bytes, 0);
return bytes[0];
}
However I wanted to use an enum as I touched on, so I created it as so:
[Flags]
public enum EventMessageTypes
{
None = 0,
aaa = 1,
bbb = 2,
ccc = 4,
ddd = 8,
eee = 16,
fff = 32,
All = aaa | bbb | ccc | ddd | eee | fff // All Events
}
and then
// Do bitwise OR to combine the values we want
EventMessageTypes eventMessages = EventMessageTypes.aaa | EventMessageTypes.bbb | EventMessageTypes.ccc;
But how do I then get eventMessages to a byte (0x07) I think! so I can append that to my byte array?
just simply cast it to byte!.
example:
byte eventMessages =(byte)( EventMessageTypes.aaa | EventMessageTypes.bbb | EventMessageTypes.ccc);
You have a way of getting a byte, so now just cast:
byte b = ConvertToByte(bitArray);
EventMessageTypes a = (EventMessageTypes) b;
Also, a tip, restrict the enum range to byte to prevent someone later adding out of range values to the enum:
[Flags]
public enum EventMessageTypes : byte
{
...
}
Here's one way:
var bitArray = new BitArray(
new [] { true, true, false, false, false, false, false, false });
var eventMessages = (EventMessageTypes)bitArray
.Cast<Boolean>()
.Reverse()
.Aggregate(0, (agg, b) => (agg << 1) + (b ? 1 : 0));
Download for LinqPad
If I understand your problem right, then in essence you can cast to enum like this EventMessageTypes result = (EventMessageTypes)ConvertToByte(bitArray);
BitArray bitArray = new BitArray(new bool[]
{ true, true, false, false,
false, false, false, false });
EventMessageTypes b = (EventMessageTypes)ConvertToByte(bitArray);
you could for readability and future code reuse make a extension class
static class Extension
{
public static byte ToByte(this BitArray bits)
{
if (bits.Count != 8)
{
throw new ArgumentException("incorrect number of bits");
}
byte[] bytes = new byte[1];
bits.CopyTo(bytes, 0);
return bytes[0];
}
static class EnumConverter<TEnum> where TEnum : struct, IConvertible
{
public static readonly Func<long, TEnum> Convert = GenerateConverter();
static Func<long, TEnum> GenerateConverter()
{
var parameter = Expression.Parameter(typeof(long));
var dynamicMethod = Expression.Lambda<Func<long, TEnum>>(
Expression.Convert(parameter, typeof(TEnum)),
parameter);
return dynamicMethod.Compile();
}
}
public static T ToEnum<T>(this byte b) where T : struct, IConvertible
{
if (!typeof(T).IsEnum)
{
throw new ArgumentException("T must be an enumerated type");
}
return EnumConverter<T>.Convert(b);
}
}
Then you can write the convert like this. bitArray.ToByte() or even better like this EventMessageTypes b = bitArray.ToByte().ToEnum<EventMessageTypes>();
enum Test
{
X = 1,
Y = 1 << 1,
Z = 1 << 2,
All = ~0
}
Will Test.All be generated as a flag of bitwise one's, no matter how many values will be added to Test at a later points in time? For example, if new values are added:
enum Test
{
X = 1,
Y = 1 << 1,
Z = 1 << 2,
W = 1 << 3,
WAndZ = W | Z,
All = ~0
}
Is it guaranteed that Test.All will always include all the other values?
Yes, bitwise, this enum value will include all other values. This means tests like this:
(Test.All & Test.X) == Test.X
will evaluate to true to all members of this enum (even if integer value of X is 0).
Likewise, you can (and should) have a default value for 0, which will evaluate to false when matched against other flags:
enum Test
{
None = 0, // no flags
X = 1,
Y = 2,
Z = 4,
...
All = ~0 // all flags
}
This means All will include all flags, and None will include no flags:
Debug.Assert((Test.All & Test.None) == Test.None);
Debug.Assert((Test.All & Test.X) == Test.X);
Debug.Assert((Test.All & Test.All) == Test.All);
Debug.Assert((Test.None & Test.None) == Test.None);
Debug.Assert((Test.None & Test.X) == Test.None);
Debug.Assert((Test.None & Test.All) == Test.None);
I have a bit shift mask that represents days in a week:
Sunday = 1
Monday = 2
Tuesday = 4
...
Saturday = 64
I'm using a bitmask because several (at least one) days may be set to 1.
The problem
Then I get a date. Any date. And based on the date.DayOfWeek I need to return the first nearest date after it that is set in the bitmask. So my method can return the same day or any other day between date and date + 6.
Example 1
My bitmask defines all days being set to 1. In this case my method should return the same date, because date.DayOfWeek is set in the bitmask.
Example 2
My bitmask defines that only Wednesday is set to 1. If my incoming date is Tuesday, I should return date+1 (which is Wednesday). But if incoming date is Thursday I should return date+6 (which is again Wednesday).
Question
What is the fastest and most elegant way of solving this? Why also fastest? Because I need to run this several times so if I can use some sort of a cached structure to get dates faster it would be preferred.
Can you suggest some guidance to solve this in an elegant way? I don't want to end up with a long spaghetti code full of ifs and switch-case statements...
Important: It's important to note that bitmask may be changed or replaced by something else if it aids better performance and simplicity of code. So bitmask is not set in stone...
A possible approach
It would be smart to generate an array of offsets per day and save it in a private class variable. Generate it once and reuse it afterwards like:
return date.AddDays(cachedDayOffsets[date.DayOfWeek]);
This way we don't use bitmask at all and the only problem is how to generate the array the fastest and with as short code as possible.
I'd go about this with a bitmask, some shifting, and a bitscan. It's not a very obvious routine, but it should be fast, as it never branches:
original_date = Whatever //user input
bitmask = Whatever //user input
bitmask |= (bitmask << 7) //copy some bits so they don't get
//lost in the bitshift
bitmask >>= original_date.dayOfWeek() //assuming Sunday.dayOfWeek() == 0
return original_date + bitscan(bitmask) - 1 //the position of the least
//significant bit will be one greater
//than the number of days to add
Bitscan - especially yours, 'cause it only cares about seven bits - is easy to implement in a lookup table. In fact, if you did a custom table, you could call the LSB bit 0, and skip the subtraction in the return statement. I'd guess the slowest part of all of this would be the dayOfWeek() function, but that would depend on it's implementation.
Hope this helps!
Edit: An example bitscan table (that treats the lsb as index 1 - you'll probably want to treat it as zero, but this makes a better example):
int[128] lsb = {
0, //0 = 0b00000000 - Special case!
1, //1 = 0b00000001
2, //2 = 0b00000010
1, //3 = 0b00000011
3, //4 = 0b00000100
1, //5 = 0b00000101
2, //6 = 0b00000110
....
1 //127 = 0b01111111
};
Then, to use your table on mask, you'd just use:
first_bit_index = lsb[mask & 127];
The & lets you write a smallish table, 'cause you really only care about the seven lowest bits.
PS: At least some processors implement a bitscan instruction that you could use instead, but it seems unlikely that you could get at them with C#, unless there's a wrapper function somewhere.
You might hate this answer, but perhaps you'll be able to run with it in a new direction. You said performance is extremely important, so maybe it's best to just index all the answers up front in some data structure. That data structure might be somewhat convoluted, but it could be encapsulated in its own little world and not interfere with your main code. The data structure I have in mind would be an array of ints. If you allow Monday, Friday, and Saturday, those ints would be:
[1][0][3][2][1][0][0]
Ok weird right? This is basically the "days away" list for the week. On Sunday, there's "1 day until next allowed day of week". On Monday, there's 0. On Tuesday, it's 3 days away. Now, once you build this list, you can very easily and very quickly figure out how many days you need to add to your date to get the next occurance. Hopefully this helps??
Generating these offsets
This is the code that generates these offsets:
this.dayOffsets = new int[] {
this.Sundays ? 0 : this.Mondays ? 1 : this.Tuesdays ? 2 : this.Wednesdays ? 3 : this.Thursdays ? 4 : this.Fridays ? 5 : 6,
this.Mondays ? 0 : this.Tuesdays ? 1 : this.Wednesdays ? 2 : this.Thursdays ? 3 : this.Fridays ? 4 : this.Saturdays ? 5 : 6,
this.Tuesdays ? 0 : this.Wednesdays ? 1 : this.Thursdays ? 2 : this.Fridays ? 3 : this.Saturdays ? 4 : this.Sundays ? 5 : 6,
this.Wednesdays ? 0 : this.Thursdays ? 1 : this.Fridays ? 2 : this.Saturdays ? 3 : this.Sundays ? 4 : this.Mondays ? 5 : 6,
this.Thursdays ? 0 : this.Fridays ? 1 : this.Saturdays ? 2 : this.Sundays ? 3 : this.Mondays ? 4 : this.Tuesdays ? 5 : 6,
this.Fridays ? 0 : this.Saturdays ? 1 : this.Sundays ? 2 : this.Mondays ? 3 : this.Tuesdays ? 4 : this.Wednesdays ? 5 : 6,
this.Saturdays ? 0 : this.Sundays ? 1 : this.Mondays ? 2 : this.Tuesdays ? 3 : this.Wednesdays ? 4 : this.Thursdays ? 5 : 6
};
This one generates forward offsets. So for any given date you can get the actual applicable date after it by simply:
SomeDate.AddDays(this.dayOffsets[(int)SomeDate.DayOfWeek]);
If you need to get nearest past date you can reuse the same array and calculate it out by using this formula:
SomeDate.AddDays((this.dayOffsets[(int)SomeDate.DayOfWeek] - 7) % 7);
Here is what I'd do, the variable dateDiff will be what you are looking for.
DoW mask = DoW.Wednesday | DoW.Friday;
DoW? todayDoW = null;
int dateDiff = 0;
do
{
DateTime date = DateTime.Today.AddDays(dateDiff);
todayDoW = (DoW)Enum.Parse(typeof(DoW), date.DayOfWeek.ToString());
if ((mask & todayDoW.Value) != 0)
{
todayDoW = null;
}
else
{
dateDiff++;
}
}
while(todayDoW.HasValue);
enum DoW
{
Sunday = 1,
Monday = 2,
Tuesday = 4,
Wednesday = 8,
Thursday = 16,
Friday = 32,
Saturday = 64
}
Here's an algorithm to populate the lookup table. You only have to do it once, so I'm not sure that it matters how efficient it is...
int[] DaysOfWeek = (int[])Enum.GetValues(typeof(DayOfWeek));
int NumberOfDaysInWeek = DaysOfWeek.Length;
int NumberOfMasks = 1 << NumberOfDaysInWeek;
int[,] OffsetLookup = new int[NumberOfDaysInWeek, NumberOfMasks];
//populate offset lookup
for(int mask = 1; mask < NumberOfMasks; mask++)
{
for(int d = 0; d < NumberOfDaysInWeek; d++)
{
OffsetLookup[d, mask] = (from x in DaysOfWeek
where ((1 << x) & mask) != 0
orderby Math.Abs(x - d)
select (x - d) % NumberOfDaysInWeek).First();
}
}
Then just use:
DateTime SomeDate = ...; //get a date
DateTime FirstDate = SomeDate.AddDays(OffsetLookup[SomeDate.DayOfWeek, DayOfWeekMask]);
I understand that you said performance is to be taken in consideration but I would start with a simple, easy to understand approach and verify if its performance is sufficient before jumping to a more complex method that can leave future maintainers of the code a bit lost.
Having said that, a possible solution using pre-initialized lookups:
[Flags]
enum DaysOfWeek
{
None = 0,
Sunday = 1,
Monday = 2,
Tuesday = 4,
Wednesday = 8,
Thursday = 16,
Friday = 32,
Saturday = 64
}
Assuming the previous enumeration:
private static Dictionary<DayOfWeek, List<DaysOfWeek>> Maps { get; set; }
static void Main(string[] args)
{
Maps = CreateMaps();
var date = new DateTime(2011, 9, 29);
var mask = DaysOfWeek.Wednesday | DaysOfWeek.Friday;
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10000; i++)
{
GetNextDay(date, mask);
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
private static DaysOfWeek GetNextDay(DateTime date, DaysOfWeek mask)
{
return Maps[date.DayOfWeek].First(dow => mask.HasFlag(dow));
}
And finally the CreateMaps implementation:
private static Dictionary<DayOfWeek, List<DaysOfWeek>> CreateMaps()
{
var maps = new Dictionary<DayOfWeek, List<DaysOfWeek>>();
var daysOfWeek = new List<DaysOfWeek>(7)
{
DaysOfWeek.Sunday,
DaysOfWeek.Monday,
DaysOfWeek.Tuesday,
DaysOfWeek.Wednesday,
DaysOfWeek.Thursday,
DaysOfWeek.Friday,
DaysOfWeek.Saturday
};
foreach (DayOfWeek dayOfWeek in Enum.GetValues(typeof(DayOfWeek)))
{
var map = new List<DaysOfWeek>(7);
for (int i = (int)dayOfWeek; i < 7; i++)
{
map.Add(daysOfWeek[i]);
}
for (int i = 0; i < (int)dayOfWeek; i++)
{
map.Add(daysOfWeek[i]);
}
maps.Add(dayOfWeek, map);
}
return maps;
}
This should be pretty easy to do. Consider that DayOfWeek.Sunday == 0, Monday == 1, etc.
Your mask corresponds to to this. That is, in your mask:
Sunday = 1 << 0
Monday = 1 << 1
Tuesday = 1 << 2
Now, given a day of week, we can easily determine the day that will match your criteria:
[Flags]
enum DayMask
{
Sunday = 1,
Monday = 2,
Tuesday = 4,
Wednesday = 8,
Thursday = 16,
Friday = 32,
Saturday = 64
}
static DayOfWeek FindNextDay(DayMask mask, DayOfWeek currentDay)
{
DayOfWeek bestDay = currentDay;
int bmask = 1;
for (int checkDay = 0; checkDay < 7; ++checkDay)
{
if (((int)mask & bmask) != 0)
{
if (checkDay >= (int)currentDay)
{
bestDay = (DayOfWeek)checkDay;
break;
}
else if (bestDay == currentDay)
{
bestDay = (DayOfWeek)checkDay;
}
}
bmask <<= 1;
}
return bestDay;
}
For example, the day you want to match is Wednesday, but the mask contains only Monday. You can see that the algorithm will select Monday as the best day and then go through the rest of the days, not selecting anything.
If the mask contains Monday, Tuesday, and Thursday, the algorithm will select Monday as the best candidate, ignore Tuesday, and then select Thursday as the best candidate and exit.
This won't be as fast as a lookup table, but it should be pretty darned fast. And it'll use a lot less memory than a lookup table.
A lookup table would be much faster, and it wouldn't take but a kilobyte of memory. And given the FindNextDay method above, it's easy enough to construct:
static byte[,] LookupTable = new byte[128, 7];
static void BuildLookupTable()
{
for (int i = 0; i < 128; ++i)
{
DayMask mask = (DayMask)i;
for (int day = 0; day < 7; ++day)
{
LookupTable[i, day] = (byte)FindNextDay(mask, (DayOfWeek)day);
}
}
}
Now, to get the next day for any combination of mask and current day:
DayOfWeek nextDay = (DayOfWeek)LookupTable[(int)mask, (int)currentDay];
There's undoubtedly a faster way to generate the table. But it's fast enough and since it would be executed once at program startup, there doesn't seem much point in optimizing it. If you want startup to be faster, write a little program that will output the table as C# code. Something like:
Console.WriteLine("static byte[,] LookupTable = new byte[128,7] {");
for (int i = 0; i < 128; ++i)
{
Console.Write(" {");
for (int j = 0; j < 7; ++j)
{
if (j > 0)
{
Console.Write(",");
}
Console.Write(" {0}", LookupTable[i, j]);
}
Console.WriteLine(" },");
}
Console.WriteLine("};");
Then you can copy and paste that into your program.