Помогите улучшить код на C#
Подскажите как оптимизировать код на C#:
есть строка такого вида:
График работы
Пн
08:30 – 20:00
Вт
08:30 – 20:00
Ср
08:30 – 20:00
Чт
08:30 – 20:00
Пт
08:30 – 20:00
Сб
08:30 – 17:00
Вс
Выходной
нужно сделать строку в краткой записи:
Пн-Пт 08:30 – 20:00
Сб 08:30 – 17:00
Вс Выходной
вот мой код:
static void Main(string[] args)
{
#region График работы
string graf = @"График работы
Пн
10:00 – 21:00
Вт
10:00 – 21:00
Ср
10:00 – 21:00
Чт
10:00 – 21:00
Пт
10:00 – 18:00
Сб
Выходной
Вс
Выходной";
#endregion
//Создаем массив из строковой переменной
string[] arrGraf = graf.Split('\n').Where(x => x.Trim() != "").ToArray();
//Создаем спсисок для храния графика в виде: Пн 10:00 - 21:00
List<string> listGrafic = new List<string>();
int a = 0; //счетчик
string day = ""; //день недели
string time = ""; //рабочее время
string rgx = ""; //данные от регулярки
//перебираем массив
while (a < arrGraf.Length)
{
string data = arrGraf[a].Trim();
switch (data.ToLower().Trim())
{
case "пн":
day = data.Trim();
break;
case "вт":
day = data.Trim();
break;
case "ср":
day = data.Trim();
break;
case "чт":
day = data.Trim();
break;
case "пт":
day = data.Trim();
break;
case "сб":
day = data.Trim();
break;
case "вс":
day = data.Trim();
break;
default:
rgx = new Regex("^\\d+").Match(data.Trim()).ToString();
time = rgx == "" ? "" : data.Trim();
break;
}
if (day != "" && time != "")
{
string dayTime = day + " " + time;
listGrafic.Add(dayTime); //добавляем в список
day = ""; time = ""; //обнуляем
}
a++;//счетчик
}
Console.WriteLine(String.Join("\n", listGrafic));
Console.WriteLine();
Console.WriteLine();
string res = "";
//перебор массива с графиком
for (int i = 0; i < listGrafic.Count; i++)
{
string temp1 = Regex.Replace(listGrafic[i].Trim().ToLower(), @"\D+", "").Trim();
string temp2 = "";
if (i + 1 < listGrafic.Count)
{
temp2 = Regex.Replace(listGrafic[i + 1].Trim().ToLower(), @"\D+", "").Trim();
}
res += temp1 == temp2 ? listGrafic[i] + "|" : listGrafic[i] + Environment.NewLine;
continue;
}
StringBuilder sb = new StringBuilder();
string result = "";
//делаем перебор полученного результата
foreach (var rowGraf in res.Trim('|').Split('\n'))
{
if (rowGraf.Trim() == "") continue;
//получаем сторку вида: Пн 10:00 - 21:00|Вт 10:00 - 21:00|Ср 10:00 - 21:00|
if (rowGraf.Length > 1)
{
string[] arr = rowGraf.Split(new string[] { "|", "\r" }, StringSplitOptions.RemoveEmptyEntries);
if (arr.Length == 1)
{
result = arr[0].Trim('|') ;
sb.AppendLine(result);
continue;
}
result = arr[0].Trim('|');
string replase = result.Substring(2);
string lastRow = arr.Last().Trim('|');
result = result.Replace(replase, "-" + lastRow);
sb.AppendLine(result);
}
}
Console.WriteLine(sb.ToString().Trim());
Console.ReadKey();
}
Ответы (2 шт):
Смотрите как можно сделать...
Делаем класс, который будет содержать в себе нужную информацию, а именно начало, конец и день недели.
public class JobTime { public JobTime(string dayOfWeek, TimeSpan begin, TimeSpan end) => (DayOfWeek, Begin, End) = (dayOfWeek, begin, end); public string DayOfWeek { get; } public TimeSpan Begin { get; } public TimeSpan End { get; } }Время тут в понятном формате -
TimeSpan. Неделя же, ее лучше сделать тоже нужным форматом (DayOfWeek), но тут возникнет ряд проблем с конвертацией и отображением, по этому я решил для примера оставить в видеstring.Далее давайте сделаем метод, который вернет нам заполненный массив этого класса, разобрав входную строку:
private static List<JobTime> ParseData(string text) { var lines = text.Split(Environment.NewLine).Skip(1).ToArray(); var result = new List<JobTime>(); for (int i = 1; i < lines.Length; i += 2) { var dayOfWeak = lines[i - 1]; TimeSpan begin = default; TimeSpan end = default; var time = lines[i].Split('–'); if (time.Length == 2) { TimeSpan.TryParse(time[0], out begin); TimeSpan.TryParse(time[1], out end); } result.Add(new JobTime(dayOfWeak, begin, end)); } return result; }Логика метода думаю вам понятна, если в двух словах, то:
- Разбиваем входной текст построчно (
Environment.NewLine- символ новой строки). - При помощи LINQ берем из полученного массива строк все, кроме первой (это "График работы").
- Преобразуем в массив, что бы можно было обратиться без проблем по индексу к нужному объекту.
- Далее делаем цикл, который будет выдавать нам значения с шагом 2, начиная с 1 (1, 3, 5 и тд.).
- В этом цикле берем значения из массива, где
i -1будет днем недели, а простоiбудет временем. - Полученное время разбиваем по нужному символу
- Если получился массив из 2-х объектов, то конвертируем каждое в
TimeSpan. - Создаем объект нашего класса и добавляем в коллекцию.
- Разбиваем входной текст построчно (
Собственно это уже больше половины дела, осталось просто сформировать данные так, как нам нужно. Если посмотреть на ваши выходные данные, то можно назвать это некими группами, так давайте это сгруппируем:
var data = ParseData(str); var group = data.GroupBy(x => new { x.Begin, x.End }) .Select(x => { var times = x.Key.Begin == default ? "Выходной" : $"{x.Key.Begin:hh\\:mm} - {x.Key.End:hh\\:mm}"; var days = x.First() == x.Last() ? x.First().DayOfWeek : $"{x.First().DayOfWeek}-{x.Last().DayOfWeek}"; return $"{days} {times}"; });- При помощи
GroupBy()группируем все по двух значениям (начало и конец). Select()поможет сформировать нужный вид.x.Key.Begin == default- если начальное время будет равно 00:00:00, то можем считать этот день выходным, иначе формируем текст в виде "времяНачала - времяКонца" убирая секунды.x.First() == x.Last()(можно и кол-во объектов вxпроверить) - если первый и последний объект группы одинаковы, то выводим только день недели от первого, иначе "первый-последний".
- При помощи
Собственно вот и вся ваша задача в паре строк кода. Он не идеален, ибо если будет время идти в разнобой (а не как у вас по порядку), то выдаст кашу, решить которую можно простым циклом, а не группировкой, который будет проверять, подряд-ли идут дни или нет. Моя задача была лишь показать саму суть, что надо все преобразовать в нужные типы, сделать класс, хранящий в себе это все и уже дальше выводить как надо. Не перебирайте строки до посинения, это не правильно!
Онлайн пример: https://dotnetfiddle.net/XchIJM
Жутко соскучился по код-гольфу, так что вот вам LINQ-однострочник:
string.Join(Environment.NewLine, (graf.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) is { } arr ? arr : arr = null).Where((x, i) => i % 2 == 1).Zip(arr.Skip(1).Where((x, i) => i % 2 == 1))
.Select(pair => (Day: pair.First.Trim(), From: (new Regex(@"(\d\d:\d\d)\s*[-–]\s*(\d\d:\d\d)").Match(pair.Second).Groups is { Count: 3 } groups ? groups : groups = null)?[1].Value, To: groups?[2].Value))
.Select(x => (x.Day, From: x.From is null ? default(TimeSpan?) : TimeSpan.Parse(x.From), To: x.To is null ? default(TimeSpan?) : TimeSpan.Parse(x.To)))
.Aggregate(new List<(List<string> Days, TimeSpan? From, TimeSpan? To)>(7), (acc, day) => { if (acc.Count == 0 || (acc[^1].From, acc[^1].To) != (day.From, day.To)) { acc.Add((new List<string> { day.Day }, day.From, day.To)); } else { acc[^1].Days.Add(day.Day); } return acc; })
.Select(x => (x.Days.Count == 0 ? x.Days[0] : $"{x.Days[0]}-{x.Days[^1]}") + " " + (x.From is null ? "Выходной" : $@"{x.From:hh\:mm} – {x.To:hh\:mm}")));
Для
string graf = @"График работы
Пн
10:00 – 21:00
Вт
10:00 – 21:00
Ср
10:00 – 21:00
Чт
10:00 – 21:00
Пт
10:00 – 18:00
Сб
Выходной
Вс
Выходной";
Отдает:
Пн-Чт 10:00 – 21:00
Пт 10:00 – 18:00
Сб-Вс Выходной
Чуть более развернуто и с пояснениями:
// Делим входную строку по символам конца строки, удаляя пустые вхождения
string[] lines = graf.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
// Это регулярное выражение извлекает из строк вида "10:00 – 21:00" значения
// "10:00" и "21:00"
Regex timeExtractor = new Regex(@"(\d\d:\d\d)\s*[-–]\s*(\d\d:\d\d)", RegexOptions.Compiled);
IEnumerable<string> entries =
// Берем каждую вторую строку (Пн, Вт, Ср и т.д.),
// и объединяем с каждой второй сточкой, но уже после первой линии
// (10:00 – 21:00 и далее).
// Таким образом в паре окажутся дни с их графиком работы:
// (Пн, 10:00 – 21:00), (Вт, 10:00 – 21:00) и т.д.
lines.Where((_, index) => index % 2 == 1).Zip(lines.Skip(1).Where((_, index) => index % 2 == 1))
// Преобразуем пару (день, график) в кортеж (день, от, до)
.Select(pair =>
{
// Если регулярное выражение отработало успешно,
// то в первой группе будет время начала работы,
// а во второй - время окончания рабочего дня
Match match = timeExtractor.Match(pair.Second);
if (match.Success)
return (Day: pair.First.Trim(), From: TimeSpan.Parse(match.Groups[1].Value), To: TimeSpan.Parse(match.Groups[2].Value));
else
return (Day: pair.First.Trim(), From: default(TimeSpan?), To: default(TimeSpan?));
})
// Теперь же сожмем список, объединив дни, которые стоят рядом и имеют общий график работы
.Aggregate(new List<(List<string> Days, TimeSpan? From, TimeSpan? To)>(7), (list, info) =>
{
// Если в списке ничего нет, либо же график последней записи отличается -
// создать новую запись
if (list.Count == 0 || (list[^1].From, list[^1].To) != (info.From, info.To))
{
list.Add((new List<string> { info.Day }, info.From, info.To));
}
// Иначе же добавить текущий день в предыдущую запись
else
{
list[^1].Days.Add(info.Day);
}
return list;
})
// Ну а тут уже все просто:
// лист дней ужимаем до "ПервыйДень-ПоследнийДень"/"ЕдинственныйДень",
// а время работы, если такое имеется, приводим к строке, иначе отдаем "Выходной"
.Select(x => (Days: x.Days.Count == 1 ? x.Days[0] : $"{x.Days[0]}-{x.Days[^1]}", Time: x.From is null ? "Выходной" : $@"{x.From:hh\:mm} – {x.To:hh\:mm}"))
// После чего две полученные строки совмещаем
// Можно было сделать и в предудыщем методе, но так нагляднее
.Select(x => $"{x.Days} {x.Time}");
// Полученную выше коллекцию строк объединяем,
// разделяя вхождения новой строкой
string result = string.Join(Environment.NewLine, entries);