LINQ для строк
Недавно проводил на работе мини-олимпиаду по программированию. Включил одну задачу на LINQ, так как эта технология уже в каждом проекте так и лезет.
Задача звучит так:
Имеется длинная строка символов. Необходимо одним выражением LINQ получить набор строк, представляющих собой разбиение исходной строки на подстроки с одинаковым числом символов (тип результата IEnumerable<string>).
| Исходная строка | Результат |
Разбить на строки ABCDEFGHIJKLMNOPQRSTUVWXYZZZZZZZZ. | Разбить на с |
Один из наиболее “навороченных” вариантов решения:
var res = str.Select((c, i) => // Разбиваем строку на символы с номерами new { Value = c, // Символ Index = i, // Позиция символа в изначальной строке LineNumber = i / width // Номер строки } ) .GroupBy(g => g.LineNumber, e => e) // Объединяем символы одной строки .Select(ar => ar.Select(ars => ars.Value)) .Select(outs => new string(outs.ToArray())); // Склеиваем символы обратно в строки
Это наиболее “чистый” LINQ-запрос, не использующий никаких дополнительных средств, кроме создания строк из массива символов. Используется вариант метода Select(), позволяющий работать с индексом элемента во входной последовательности.
Другой вариант жюри предполагал использование метода Enumerable.Range() как замену цикла. Здесь основная идея в том, что мы можем заранее определить, сколько строк получится в результате, а по номеру символа в строке узнать, к какой строке он относится:
var res = Enumerable .Range(0, str.Length / width + 1) // Массив номеров будущих строк .Join( str.ToCharArray().Select((c, i) => { // Разбиваем строку на символы с номерами return new { Value = c, Index = i }; }), s => s, d => d.Index / width, // По номеру символа определяем, к какой строке он будет относится (s, d) => new { s, d }) .GroupBy(g => g.s, e => e) // Объединяем символы одной строки .Select(ar => ar.Select(ars => ars.d.Value)) .Select(outs => new string(outs.ToArray())); // Склеиваем символы обратно в строки
Это решение можно упростить до примитивного (но тем не менее корректного):
var res = Enumerable .Range(0, str.Length / width + 1) // Массив номеров будущих строк .Select(s => str.Substring(s * width, Math.Min(width, str.Length - s * width))); // Выберем подстроки