Skip to content

Instantly share code, notes, and snippets.

@zetroot
Created March 11, 2021 07:50
Show Gist options
  • Save zetroot/65cb76cba2b5a9b4e9175491d461231b to your computer and use it in GitHub Desktop.
Save zetroot/65cb76cba2b5a9b4e9175491d461231b to your computer and use it in GitHub Desktop.
Группировка по агрегациям
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