Reputation: 313
I have a dictionary that stores each day of the week and the open/close times for that day.
Dictionary<string, string> hours = new Dictionary<string, string>();
hours.Add("M", String.Format("{0}-{1}", this.MondayOpen, this.MondayClosed));
hours.Add("T", String.Format("{0}-{1}", this.TuesdayOpen, this.TuesdayClosed));
hours.Add("W", String.Format("{0}-{1}", this.WednesdayOpen, this.WednesdayClosed));
hours.Add("Th", String.Format("{0}-{1}", this.ThursdayOpen, this.ThursdayClosed));
hours.Add("F", String.Format("{0}-{1}", this.FridayOpen, this.FridayClosed));
hours.Add("S", String.Format("{0}-{1}", this.SaturdayOpen, this.SaturdayClosed));
hours.Add("Su", String.Format("{0}-{1}", this.SundayOpen, this.SundayClosed));
I'd like to be able to iterate over the dictionary and group the days based on like times. For example, I'd like to display the data like this
M-Th 8:00 AM - 4:00 PM
F 8:00 AM - 6:00 PM
(leave Saturday and Sunday off if those days don't have open/close times)
I've tried a few different ways but haven't been able to get it completely right. Here's my most recent attempt.
private string FormatHours2()
{
StringBuilder sb = new StringBuilder();
// add all of the hours to a dictionary
Dictionary<string, string> hours = new Dictionary<string, string>();
hours.Add("M", String.Format("{0}-{1}", this.MondayOpen, this.MondayClosed));
hours.Add("T", String.Format("{0}-{1}", this.TuesdayOpen, this.TuesdayClosed));
hours.Add("W", String.Format("{0}-{1}", this.WednesdayOpen, this.WednesdayClosed));
hours.Add("Th", String.Format("{0}-{1}", this.ThursdayOpen, this.ThursdayClosed));
hours.Add("F", String.Format("{0}-{1}", this.FridayOpen, this.FridayClosed));
hours.Add("S", String.Format("{0}-{1}", this.SaturdayOpen, this.SaturdayClosed));
hours.Add("Su", String.Format("{0}-{1}", this.SundayOpen, this.SundayClosed));
// placeholder for the previous time range
string prevValue = String.Empty;
// inrun - indicates whether we are in a run of the same times.
// firstTime - indicates whether this is the first time through the loop.
bool inrun = false, firstTime = true;
for (int i = 0; i < hours.Count; i++)
{
KeyValuePair<string, string> entry = hours.ElementAt(i);
if (entry.Value != prevValue)
{
if (firstTime)
{
if (HasValue(entry.Value)) { sb.Append(entry.Key); }
}
else
{
if (!inrun)
{
if (HasValue(prevValue)) { sb.Append(String.Format(" {0},", hours.ElementAt(i - 1).Value)); }
if (HasValue(entry.Value)) { sb.Append(entry.Key); }
}
else
{
if (HasValue(prevValue))
{
sb.Append(String.Format("-{0} {1}", hours.ElementAt(i - 1).Key, hours.ElementAt(i - 1).Value));
}
if (HasValue(entry.Value)) { sb.Append(String.Format(",{0}", entry.Key)); }
}
}
inrun = false;
}
else
{
inrun = true;
}
firstTime = false;
prevValue = entry.Value;
// if we're on the last iteration, write the value
if (i == hours.Count() - 1)
{
if (inrun)
{
if (HasValue(entry.Value))
{
sb.Append(String.Format("-{0} {1}", entry.Key, entry.Value));
}
}
else
{
if (HasValue(prevValue))
{
sb.Append(String.Format(" {0}", hours.ElementAt(i - 1).Value));
}
}
}
}
return sb.ToString().TrimEnd(',');
}
This works pretty well with all scenarios except when days in the middle of the week are closed it doesn't put the comma.
Any help is greatly appreciated. If someone knows of a better way to do this, I'm open to suggestions. I don't even have to use a dictionary.
Thanks
Upvotes: 0
Views: 571
Reputation: 16878
Your code is hard to read and maintain because you are trying to do two things at once: compact data (merge days into groups with the same hours) and build text with the description.
I would split this into two steps, add some LINQ and reorganize all a little. Let's start with the compacting issue. What we want is to group list into smaller clusters with the same working hours. Unfortunately there is no built-in LINQ method for this. But of course we can write one:
public static class EnumerableExtensions
{
public static IEnumerable<TResult> Compact<T, TKey, TResult>(
this IEnumerable<T> source,
Func<T,TKey> keySelector,
Func<TKey, IEnumerable<T>, TResult> resultSelector)
{
if (!source.Any())
yield break;
var comparer = EqualityComparer<TKey>.Default;
TKey previousKey = keySelector(source.First());
List<T> group = new List<T>() { source.First() };
foreach (var item in source.Skip(1))
{
TKey currentKey = keySelector(item);
if (!comparer.Equals(previousKey, currentKey))
{
yield return resultSelector(previousKey, group);
group = new List<T>();
}
group.Add(item);
previousKey = currentKey;
}
if (group.Any())
{
yield return resultSelector(previousKey, group);
}
}
}
This code should be placed in some helper library, so won't be seen by the programmers looking at your code. Having this helpfull method, we can use it:
var compact = hours.Compact(p => p.Value,
(key, values) => new {
Hours = key,
Start = values.First().Key,
End = values.Last().Key
});
Dictionary has been now compacted (with respect to KeyValuePair<>.Value
, meaning working hours) and collection of anonymous objects has now all necessary information about days, you can simply string.Join
them with proper formatting:
Func<string, string, string> dayFormatter =
(first, second) => first == second ? first : string.Format("{0} - {1}", first, second);
var result = string.Join(", ", compact.Select(x => string.Format("{0} {1}",
dayFormatter(x.Start, x.End),
x.Hours)));
Note: For sample data like:
string MondayOpen = "8:00 AM"; string MondayClosed = "4:00 PM";
string TuesdayOpen = "8:00 AM"; string TuesdayClosed = "4:00 PM";
string WednesdayOpen = "7:00 AM"; string WednesdayClosed = "3:00 PM";
string ThursdayOpen = "8:00 AM"; string ThursdayClosed = "4:00 PM";
string FridayOpen = "8:00 AM"; string FridayClosed = "4:00 PM";
string SaturdayOpen = "8:00 AM"; string SaturdayClosed = "2:00 PM";
string SundayOpen = "8:00 AM"; string SundayClosed = "2:00 PM";
Dictionary<string, string> hours = new Dictionary<string, string>();
hours.Add("M", String.Format("{0}-{1}", MondayOpen, MondayClosed));
hours.Add("T", String.Format("{0}-{1}", TuesdayOpen, TuesdayClosed));
hours.Add("W", String.Format("{0}-{1}", WednesdayOpen, WednesdayClosed));
hours.Add("Th", String.Format("{0}-{1}", ThursdayOpen, ThursdayClosed));
hours.Add("F", String.Format("{0}-{1}", FridayOpen, FridayClosed));
hours.Add("S", String.Format("{0}-{1}", SaturdayOpen, SaturdayClosed));
hours.Add("Su", String.Format("{0}-{1}", SundayOpen, SundayClosed));
above code produces:
`"M - T 8:00 AM-4:00 PM, W 7:00 AM-3:00 PM, Th - F 8:00 AM-4:00 PM, S - Su 8:00 AM-2:00 PM"`
Upvotes: 1