Created
March 11, 2021 07:50
-
-
Save zetroot/65cb76cba2b5a9b4e9175491d461231b to your computer and use it in GitHub Desktop.
Группировка по агрегациям
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class EnumerableAggregator | |
{ | |
private class AggregateGrouping<TKey, TElement> : IGrouping<TKey, TElement> | |
{ | |
private readonly IReadOnlyList<TElement> items; | |
public IEnumerator<TElement> GetEnumerator() => items.GetEnumerator(); | |
IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator(); | |
public TKey Key { get; } | |
public AggregateGrouping([NotNull] IEnumerable<TElement> items, [NotNull] TKey key) | |
{ | |
this.items = items?.ToList() ?? throw new ArgumentNullException(nameof(items)); | |
Key = key ?? throw new ArgumentNullException(nameof(key)); | |
} | |
} | |
/// <summary> | |
/// Сгруппировать последовательность в группы, такие что результат агрегирования по группе не приводит к срабатыванию пороговой функции, | |
/// но добавление следующего элемента приведет к срабатыванию порога. | |
/// Элементы для формирования групп берутся последовательно из последовательности. | |
/// Ни один элемент не должен приводить к срабатыванию пороговой функции | |
/// </summary> | |
/// <param name="src">Исходная последовательноть над которой происходит преобразование</param> | |
/// <param name="aggregation">Функция агрегат - для данной последовательности элементов возвращает значение агрегата</param> | |
/// <param name="threshold">Пороговая функция, при превышении которой последний добавленный в группу элемент выбрасывается</param> | |
/// <typeparam name="TAggregate">Тип значения агрегата</typeparam> | |
/// <typeparam name="TElement">Тип элементов над которыми происходит группировка</typeparam> | |
/// <returns>Последовательность группировок, где ключом является значение агрегата для группы</returns> | |
public static IEnumerable<IGrouping<TAggregate, TElement>> GroupByRunningAggregation<TAggregate, TElement>( | |
[NotNull] this IEnumerable<TElement> src, | |
[NotNull] Func<IEnumerable<TElement>, TAggregate> aggregation, | |
[NotNull] Func<TAggregate, bool> threshold) | |
{ | |
if (src == null) throw new ArgumentNullException(nameof(src)); | |
if (aggregation == null) throw new ArgumentNullException(nameof(aggregation)); | |
if (threshold == null) throw new ArgumentNullException(nameof(threshold)); | |
var currentGroupRunning = new List<TElement>(); | |
foreach (var item in src) | |
{ | |
currentGroupRunning.Add(item); | |
var currentAggregate = aggregation(currentGroupRunning); | |
if (threshold(currentAggregate)) | |
{ | |
var tail = currentGroupRunning.Last(); | |
var iterationItems = currentGroupRunning.SkipLast(1).ToList(); | |
var iterationAggregate = aggregation(iterationItems); | |
currentGroupRunning.Clear(); | |
currentGroupRunning.Add(tail); | |
yield return new AggregateGrouping<TAggregate, TElement>(iterationItems, iterationAggregate); | |
} | |
} | |
var finalAggregate = aggregation(currentGroupRunning); | |
yield return new AggregateGrouping<TAggregate, TElement>(currentGroupRunning, finalAggregate); | |
} | |
/// <summary> | |
/// Сгруппировать по группам по факту срабатывания пороговой функции. В группу добавляются все элементы, предшествовавшие срабатыванию порога. | |
/// Функция агрегации позволяет вычислить новый агрегат на основе аккумулятора и текущего элемента и записывает результат в аккумулятор | |
/// </summary> | |
/// <param name="src">Исходная последовательно</param> | |
/// <param name="groupSeed">начальное значение аккумулятора для группы</param> | |
/// <param name="aggregate">Функция вычисления нового значения аккумулятора</param> | |
/// <param name="threshold">Пороговая функция</param> | |
/// <typeparam name="TAcc">Тип аккумулятора</typeparam> | |
/// <typeparam name="TElement">Тип элементов последовательности</typeparam> | |
/// <returns>Последовательность групп</returns> | |
/// <exception cref="ArgumentNullException">Кинется, если входные параметры null</exception> | |
public static IEnumerable<IGrouping<TAcc, TElement>> GroupByAggregation<TAcc, TElement>( | |
[NotNull] this IEnumerable<TElement> src, | |
TAcc groupSeed, | |
[NotNull] Func<TAcc, TElement, TAcc> aggregate, | |
[NotNull] Func<TAcc, TElement, bool> threshold) | |
{ | |
if (src == null) throw new ArgumentNullException(nameof(src)); | |
if (aggregate == null) throw new ArgumentNullException(nameof(aggregate)); | |
if (threshold == null) throw new ArgumentNullException(nameof(threshold)); | |
var acc = groupSeed; | |
var currentGroup = new List<TElement>(); | |
foreach (var item in src) | |
{ | |
if (threshold(acc, item)) | |
{ | |
var iterationResult = new AggregateGrouping<TAcc, TElement>(currentGroup, acc); | |
currentGroup.Clear(); | |
acc = groupSeed; | |
yield return iterationResult; | |
} | |
currentGroup.Add(item); | |
acc = aggregate(acc, item); | |
} | |
yield return new AggregateGrouping<TAcc, TElement>(currentGroup, acc); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment