Страницы

Поиск по вопросам

воскресенье, 29 декабря 2019 г.

Сгруппировать последовательно идущие цифры в массив

#c_sharp #linq


Есть объект List для примера пусть будет

var src = new List {0,1,3,5,6,7,9};


В исходном списке дубликаты не возможны, т.е. {0,1,1,3,5} не рассматривается. Список
предварительно отсортирован

как написать функцию которая будет возвращать список объектов к котором будут последовательно
идущие цифры, т.е. необходимо получить следующий результат:

var result = new List>
{
    new List {0,1},
    new List {3},
    new List {5,6,7},
    new List {9}
};


мне в голову приходит только вариант сделать в цикле for но мне кажется что это можно
сделать более элегантней с использованием linq
    


Ответы

Ответ 1



В данном случае можно использовать метод Aggregate, с начальным значением для аккумулятора. src.Aggregate(new List>(), (acc, cur) => { //проверяем что мы, либо зашли в первый раз, либо разница между элементами больше 1. if (acc.Count == 0 || cur - acc.Last().Last() > 1) { //добавляем новый список с текущим элементом acc.Add(new List { cur }); } else { //иначе добавляем в последний список acc.Last().Add(cur); } //возвращаем аккумулятор return acc; });

Ответ 2



Это можно сделать при помощи группировки GroupBy. var values = new List { 0, 1, 3, 5, 6, 7, 9 }; int? prev = null, first = null; var grouped = values .GroupBy( v => v == prev + 1 ? first += prev++ * 0 : first = prev = v, (_, vs) => vs.ToList()) .ToList(); Есть небольшая проблема с тем, что в C# нет операции "запятая". Если бы она была, вместо странной формулы first += prev++ * 0 можно было бы записать просто (prev++, first) (то есть инкрементировать первую переменную, вернуть вторую). Разумеется, это можно записать и цивилизованно: var grouped2 = values .GroupBy( v => { if (v == prev + 1) { prev++; return first; } else { return first = prev = v; } }, (_, vs) => vs.ToList()) .ToList(); Правда это не будет так локанично. :)

Ответ 3



Ещё одна идея — использовать более богатый арсенал операторов LINQ. Для начала, подключим пакет MoreLinq (консоль менеджера пакетов → Install-Package morelinq). Для начала, нам нужно для каждого из элементов выяснить, нужно ли на нём заканчивать группу. Для этого нужно проконсультироваться с предыдущим элементом. Такую возможность нам даёт функция Pairwise. Теперь, при этом у нас «проглотится» первый (или последний) элемент, так что его нужно добавить при помощи функции Prepend. Покамест получаем: src.Pairwise((prev, next) => new { val = next, sameGroup = next - prev == 1 }) .Prepend(new { val = src[0], sameGroup = true }) Теперь, нам нужно сгруппировать последовательность в куски в зависимости от значения sameGroup. Такой функциональности из коробки я не нашёл, но её легко реализовать самостоятельно: static class EnumerableExtensions { public static IEnumerable> SplitBy( this IEnumerable source, Func mayAppend, Func selector) { var chunk = new List(); foreach (var x in source) { if (!mayAppend(x)) { yield return chunk; chunk = new List(); } chunk.Add(selector(x)); } if (chunk.Any()) yield return chunk; } } Итого: static void Main(string[] args) { var src = new List { 0, 1, 3, 5, 6, 7, 9 }; var result = src.Pairwise((prev, next) => new { val = next, sameGroup = next - prev == 1 }) .Prepend(new { val = src[0], sameGroup = true }) .SplitBy(vs => vs.sameGroup, vs => vs.val); foreach (var seq in result) Console.WriteLine(string.Join(" ", seq)); } выводит 0 1 3 5 6 7 9

Комментариев нет:

Отправить комментарий