Кстати о Linq-однострочниках)
В общем, есть такая задача:
Имеется IEnumerable<string>, содержащий в себе некоторые строки. А также есть int[], содержащий в себе позиции, на которые в вышеуказанной коллекции нужно вставить пустые строки
Пример:
// IEnumerable<string> collection:
"Some"
"Strings"
"To"
"Test"
"Method"
// int[] mask:
1
2
4
// IEnumerable<string> result:
"Some"
""
""
"Strings"
""
"To"
"Test"
"Method"
Красота Linq в том, чтобы без каких-либо дополнительных переменных и явных циклов преобразовать коллекцию. Однако у меня без введения новой переменной решить задачу не получилось, так что хотел бы испросить у вас: какое решение будет более элегантным?)
Мои реализации:
В лоб:
int i = 0;
List<string> result = new List<string>();
foreach (string x in collection)
{
while (mask.Contains(i))
{
result.Add(string.Empty);
++i;
}
result.Add(x);
++i;
}
То же, но через SelectMany:
int i = 0;
IEnumerable<string> result = collection.SelectMany(x => {
List<string> part = new List<string>();
while (mask.Contains(i))
{
part.Add(string.Empty);
++i;
}
part.Add(x);
++i;
return part; });
UPD:
Обычно это важно в подобного рода задачах, так что уточняю: mask является упорядоченным массивом
Ещё одна вариация той же идеи с предварительной обработкой.
Создадим словарь:
var dict = mask.OrderBy(n => n)
.Select((n, idx) => n - idx - 1)
.GroupBy(n => n)
.ToDictionary(g => g.Key, g => g.Count());
Имея это, получаем такой запрос:
Enumerable.Repeat("", dict.TryGetValue(-1, out var k) ? k : 0).Concat(
collection.SelectMany((s, idx) =>
Enumerable.Repeat("", dict.TryGetValue(idx, out var t) ? t : 0)
.Prepend(s))
);
Первая строка нужна для случая, когда в маске есть индекс 0.
Собственно, отличную идею подал @VladD:
Если от mask отнять последовательность { 0, 1, 2, 3, ... }, получится как раз последовательность индексов исходной последовательности, где нужно добавлять элементы при помощи SelectMany
Действительно, если мы вычтем из каждого i-того элемента mask N0i (N0 - последовательность целых неотрицательных чисел), мы получим коллекцию предрассчитанных позиций, на которые необходимо будет вставить пустые строки
Если проще, нам нужно просто из каждого элемента mask вычесть его позицию в массиве
Получив коллекцию позиций, куда потребуется вставить пустые строки, мы уже используем все тот же SelectMany
Итоговый код:
// Определим данные
IEnumerable<string> collection = new List<string> { "Some", "Strings", "To", "Test", "Method" };
IEnumerable<int> mask = new int[] { 1, 2, 4 };
// Просчитаем маску
mask = mask.Select((x, i) => x - i);
// Для каждого элемента выберем коллекцию, которая состоит из такого числа пустых строк, сколько раз индекс объекта
// встречается в маске, а также из этого самого элемента
IEnumerable<string> result = collection.SelectMany((x, i) => new List<string>(Enumerable.Repeat(string.Empty, mask.Count(index => index == i))) { x });
Результат:
// IEnumerable<string> result:
"Some"
""
""
"Strings"
""
"To"
"Test"
"Method"
Собственно, мы получили как раз то, что и ожидали)
Еще раз большое спасибо за хорошую идею!
Сборка персонального компьютера от Artline: умный выбор для современных пользователей