I have a c# class that contains 12 boolean properties;
AvaialableJan, AvailableFeb, AvailableMar, AvailableApr... etc
Each instance of this class can have any number of them as true, and normally they run in a sequence. I.e. AvailableFeb - AvailableApr will be true but then none others. Sometimes there will be only a single bool as true, i.e. only available in one month. Sometimes they all will be, i.e. available year round.
Sometimes, and this is the tricky bit, they will have two ranges, i.e. available Feb-Apr, and Aug-Oct.
I am trying to write a function to return a string to represent the selections made.
Only 1 bool selected I would like to return (e.g.): "Feb only".
Range selected (e.g.): "Jan-Mar".
Multiple ranges (e.g.): "Jan-Mar, Aug-Nov".
Mixed single and range (e.g.): "Jan-Mar, Sep".
All selected: "Year round".
A range is where consecutive months have been assigned true, e.g. Jan, Feb, Mar being true should result in Jan-Mar.
I have tried using a loop with simple condition checks but it's just messy and I can't get it right, I regret storing them as individual properties rather than an int array but I am stuck with this now. I wonder if there is a way to store the bools inside another property using reflection then looping. Haven't had luck so far. Any help much appreciated!
Thanks
If you care to change your class a bit then follow this way!.
You can have 12 properties and map them into single int array. So you have int array of length 12. when intArray[0] = 1
means first month is available. if intArray[0] = 0
means first month is not available and so on.
You convert this int array into string with base-16 representation of 1-based index of each month, but if its not available then put 0 instead. Later we use a dictionary to get the name of months by giving its number instead.
Example 1:
"003456000a00"
This means months 3th,4th,5th,6th
and a = 10th
are available. then use regex to parse this and find the matches. The regex pattern would be [^0]+
. means it matches any character except 0
.
So the regex will give us this matches.
Match 1 : 3456
Match 2 : a
First match length is more than 1
means its a range of months. so we take the first character and last one. and we join them with -
. Here it will be 3
and 6
. So match 1 should become
Mar-Jun
Second match length is only 1
means its a single month. So it should become
Oct
Since we have two matches we join these with ,
and finally the out put is Mar-Jun , Oct
Example 2:
"020000000000"
Matches
Match 1 : 2
Since we have only 1 match and the match length is 1 this should become
Feb Only
Example 3:
"023456780000"
Matches
Match 1 : 234567
This is only one match but the length of this match is more than one. so we just take 2
and 7
and join them with -
.
Feb-Jul
Example 4:
"123456789abc"
Matches
Match 1 : 123456789abc
As you can see here we have all the months. the length of this match is 12 so it should be
Year round
Example 5:
"123456000abc"
Matches
Match 1 : 123456
Match 2 : abc
Here we have two matches. it can be Jan-Jun , Oct-Dec
but the better representation (as you mentioned in comment) is Oct-Jun
. That should be abc123456
. So we check if the last match ends with c
and first match starts with 1
then we join last match with first match.
Oct-Jun
Code:
It will become simple as you can see.
internal class AvailableYear
{
private readonly int[] _available;
private static readonly Regex MatchTrue = new Regex("[^0]+");
private static readonly Dictionary<string, string> GetName = new Dictionary<string, string>
{
{"1","Jan" },
{"2","Feb" },
{"3","Mar" },
{"4","Apr" },
{"5","May" },
{"6","Jun" },
{"7","Jul" },
{"8","Aug" },
{"9","Sep" },
{"a","Oct" },
{"b","Nov" },
{"c","Dec" },
};
public AvailableYear(params int[] available)
{
if (available.Length > 12) throw new IndexOutOfRangeException("given parameters should not exceed 12 months.");
_available = available;
}
public int AvaialableJan
{
get { return _available[0]; }
set { _available[0] = value; }
}
public int AvailableFeb
{
get { return _available[1]; }
set { _available[1] = value; }
}
public int AvailableMar
{
get { return _available[2]; }
set { _available[2] = value; }
}
public int AvailableApr
{
get { return _available[3]; }
set { _available[3] = value; }
}
public int AvaialableMay
{
get { return _available[4]; }
set { _available[4] = value; }
}
public int AvaialableJun
{
get { return _available[5]; }
set { _available[5] = value; }
}
public int AvaialableJul
{
get { return _available[6]; }
set { _available[6] = value; }
}
public int AvaialableAug
{
get { return _available[7]; }
set { _available[7] = value; }
}
public int AvaialableSep
{
get { return _available[8]; }
set { _available[8] = value; }
}
public int AvaialableOct
{
get { return _available[9]; }
set { _available[9] = value; }
}
public int AvaialableNov
{
get { return _available[10]; }
set { _available[10] = value; }
}
public int AvaialableDec
{
get { return _available[11]; }
set { _available[11] = value; }
}
public override string ToString()
{
string values = string.Join("", _available.Select((x, i) => x == 0 ? "0" : Convert.ToString(i + 1, 16)));
var matches = MatchTrue.Matches(values).Cast<Match>().Select(x => x.Value).ToList();
if (matches.Count == 0)
{
return "None";
}
if (matches[0].Length == 12)
{
return "Year round";
}
if (matches.Count == 1 && matches[0].Length == 1)
{
return GetName[matches[0]] + " Only";
}
else
{
if (matches.First().StartsWith("1") && matches.Last().EndsWith("c"))
{
matches[0] = matches.Last() + matches.First();
matches.RemoveAt(matches.Count - 1);
}
List<string> output = new List<string>();
foreach (var match in matches)
{
if (match.Length == 1)
{
output.Add(GetName[match]);
}
else
{
output.Add(GetName[match.First().ToString()] + "-" +
GetName[match.Last().ToString()]);
}
}
return string.Join(", ", output);
}
}
}
Here is the test.
static void Main()
{
AvailableYear ay = new AvailableYear(1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0);
Console.WriteLine(ay.ToString());
// Output : Jan , Apr-Jul , Nov
}
Update :
If you want to assign bool values in constructor you can change constructor to this.
public AvailableYear(params bool[] available)
{
if (available.Length > 12) throw new IndexOutOfRangeException("given parameters should not exceed 12 months.");
_available = available.Select(Convert.ToInt32).ToArray();
}
And the create the instance like this. without writing Convert.ToInt32 each time.
return new AvailableYear(AvailableJan, AvailableFeb, AvailableMar...., AvailableDec).ToString();