< Summary

Information
Class: MyNet.Observable.Collections.Statistics.CollectionStatistics<T1, T2>
Assembly: MyNet.Observable
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Collections/Statistics/CollectionStatistics.cs
Tag: 323_28699572109
Line coverage
100%
Covered lines: 34
Uncovered lines: 0
Coverable lines: 34
Total lines: 195
Line coverage: 100%
Branch coverage
43%
Covered branches: 7
Total branches: 16
Branch coverage: 43.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%44100%
get_FilteredPercentage()100%11100%
get_Sum()100%11100%
get_Average()100%11100%
get_Min()100%11100%
get_Max()100%11100%
ForDouble(...)100%11100%
ForTimeSpan(...)100%11100%
OnCollectionPropertyChanged(...)37.5%88100%
UpdateFilteredPercentage()50%22100%
RecalculateValues()50%22100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Collections/Statistics/CollectionStatistics.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="CollectionStatistics.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Generic;
 9using System.ComponentModel;
 10using System.Linq;
 11using System.Reactive.Disposables;
 12
 13namespace MyNet.Observable.Collections.Statistics;
 14
 15/// <summary>
 16/// Reactive view statistics over the filtered items of an <see cref="ExtendedCollection{T}"/>.
 17/// </summary>
 18/// <typeparam name="T">The item type.</typeparam>
 19/// <typeparam name="TValue">The aggregated value type.</typeparam>
 20public sealed class CollectionStatistics<T, TValue> : ObservableObject
 21    where T : notnull
 22{
 23    private readonly ExtendedCollection<T> _collection;
 24    private readonly Func<T, TValue> _valueSelector;
 25    private readonly IStatisticsComputer<TValue> _computer;
 26    private double _filteredPercentage;
 27    private TValue _sum = default!;
 28    private TValue _average = default!;
 29    private TValue _min = default!;
 30    private TValue _max = default!;
 31
 1532    private CollectionStatistics(ExtendedCollection<T> collection, Func<T, TValue> valueSelector, IStatisticsComputer<TV
 33    {
 1534        _collection = collection ?? throw new ArgumentNullException(nameof(collection));
 1535        _valueSelector = valueSelector ?? throw new ArgumentNullException(nameof(valueSelector));
 1536        _computer = computer;
 37
 38        Disposables.Add(_collection.Connect().Subscribe(_ => RecalculateValues()));
 1539        _collection.PropertyChanged += OnCollectionPropertyChanged;
 40        Disposables.Add(Disposable.Create(() => _collection.PropertyChanged -= OnCollectionPropertyChanged));
 41
 1542        UpdateFilteredPercentage();
 1543        RecalculateValues();
 1544    }
 45
 46    /// <summary>
 47    /// Gets the ratio of filtered items to source items, or <c>0</c> when the source is empty.
 48    /// </summary>
 349    public double FilteredPercentage => _filteredPercentage;
 50
 51    /// <summary>
 52    /// Gets the sum of values across filtered items.
 53    /// </summary>
 1254    public TValue Sum => _sum;
 55
 56    /// <summary>
 57    /// Gets the average of values across filtered items.
 58    /// </summary>
 359    public TValue Average => _average;
 60
 61    /// <summary>
 62    /// Gets the minimum value across filtered items.
 63    /// </summary>
 664    public TValue Min => _min;
 65
 66    /// <summary>
 67    /// Gets the maximum value across filtered items.
 68    /// </summary>
 669    public TValue Max => _max;
 70
 71    /// <summary>
 72    /// Creates a <see cref="CollectionStatistics{T, TType}"/> instance to track view metrics and numeric aggregates (su
 73    /// </summary>
 74    /// <param name="collection">The collection whose filtered view is aggregated.</param>
 75    /// <param name="valueSelector">Selects the numeric value for each item.</param>
 76    /// <returns>A <see cref="CollectionStatistics{T, TType}"/> instance.</returns>
 77    internal static CollectionStatistics<T, double> ForDouble(ExtendedCollection<T> collection, Func<T, double> valueSel
 1278        new(collection, valueSelector, DoubleStatisticsComputer.Instance);
 79
 80    /// <summary>
 81    /// Creates a <see cref="CollectionStatistics{T, TType}"/> instance to track view metrics and <see cref="TimeSpan"/>
 82    /// </summary>
 83    /// <param name="collection">The collection whose filtered view is aggregated.</param>
 84    /// <param name="valueSelector">Selects the duration for each item.</param>
 85    /// <returns>A <see cref="CollectionStatistics{T, TType}"/> instance.</returns>
 86    internal static CollectionStatistics<T, TimeSpan> ForTimeSpan(ExtendedCollection<T> collection, Func<T, TimeSpan> va
 387        new(collection, valueSelector, TimeSpanStatisticsComputer.Instance);
 88
 89    /// <summary>
 90    /// Handles property changes of the underlying collection to update the filtered percentage and recalculate aggregat
 91    /// </summary>
 92    /// <param name="sender">The source of the event.</param>
 93    /// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
 94    private void OnCollectionPropertyChanged(object? sender, PropertyChangedEventArgs e)
 95    {
 996        if (e.PropertyName is nameof(ExtendedCollection<>.Count) or nameof(ExtendedCollection<>.SourceCount))
 997            UpdateFilteredPercentage();
 98
 999        if (e.PropertyName is nameof(ExtendedCollection<>.Count))
 9100            RecalculateValues();
 9101    }
 102
 103    /// <summary>
 104    /// Updates the filtered percentage based on the current count of filtered items and the total source count. If the 
 105    /// </summary>
 106    private void UpdateFilteredPercentage()
 107    {
 24108        var value = _collection.SourceCount == 0
 24109            ? 0
 24110            : (double)_collection.Count / _collection.SourceCount;
 111
 24112        SetProperty(ref _filteredPercentage, value, nameof(FilteredPercentage));
 24113    }
 114
 115    /// <summary>
 116    /// Recalculates the sum, average, minimum, and maximum values across the filtered items in the collection. It retri
 117    /// </summary>
 118    private void RecalculateValues()
 119    {
 48120        var values = _collection.Items.Select(_valueSelector).ToList();
 48121        var (sum, average, min, max) = values.Count == 0
 48122            ? _computer.Empty
 48123            : _computer.Compute(values);
 124
 48125        SetProperty(ref _sum, sum, nameof(Sum));
 48126        SetProperty(ref _average, average, nameof(Average));
 48127        SetProperty(ref _min, min, nameof(Min));
 48128        SetProperty(ref _max, max, nameof(Max));
 48129    }
 130}
 131
 132/// <summary>
 133/// Defines an interface for computing statistics such as sum, average, minimum, and maximum values from a list of value
 134/// </summary>
 135/// <typeparam name="TValue">The type of value for which statistics are computed.</typeparam>
 136internal interface IStatisticsComputer<TValue>
 137{
 138    /// <summary>
 139    /// Gets the default statistics values for an empty list of values. This property returns a tuple containing the sum
 140    /// </summary>
 141    (TValue Sum, TValue Average, TValue Min, TValue Max) Empty { get; }
 142
 143    /// <summary>
 144    /// Computes the sum, average, minimum, and maximum values from the provided list of values. This method takes a rea
 145    /// </summary>
 146    /// <param name="values">The list of values for which to compute statistics.</param>
 147    /// <returns>A tuple containing the computed sum, average, minimum, and maximum values.</returns>
 148    (TValue Sum, TValue Average, TValue Min, TValue Max) Compute(IReadOnlyList<TValue> values);
 149}
 150
 151/// <summary>
 152/// Provides an implementation of the <see cref="IStatisticsComputer{TValue}"/> interface for computing statistics on do
 153/// </summary>
 154internal sealed class DoubleStatisticsComputer : IStatisticsComputer<double>
 155{
 156    /// <summary>
 157    /// Gets a singleton instance of the <see cref="DoubleStatisticsComputer"/> class. This instance can be used to comp
 158    /// </summary>
 159    public static DoubleStatisticsComputer Instance { get; } = new();
 160
 161    /// <inheritdoc/>
 162    public (double Sum, double Average, double Min, double Max) Empty =>
 163        (0, double.NaN, double.NaN, double.NaN);
 164
 165    /// <inheritdoc/>
 166    public (double Sum, double Average, double Min, double Max) Compute(IReadOnlyList<double> values) =>
 167        (values.Sum(), values.Average(), values.Min(), values.Max());
 168}
 169
 170/// <summary>
 171/// Provides an implementation of the <see cref="IStatisticsComputer{TValue}"/> interface for computing statistics on <s
 172/// </summary>
 173internal sealed class TimeSpanStatisticsComputer : IStatisticsComputer<TimeSpan>
 174{
 175    /// <summary>
 176    /// Gets a singleton instance of the <see cref="TimeSpanStatisticsComputer"/> class. This instance can be used to co
 177    /// </summary>
 178    public static TimeSpanStatisticsComputer Instance { get; } = new();
 179
 180    /// <inheritdoc/>
 181    public (TimeSpan Sum, TimeSpan Average, TimeSpan Min, TimeSpan Max) Empty =>
 182        (TimeSpan.Zero, TimeSpan.Zero, TimeSpan.Zero, TimeSpan.Zero);
 183
 184    /// <inheritdoc/>
 185    public (TimeSpan Sum, TimeSpan Average, TimeSpan Min, TimeSpan Max) Compute(IReadOnlyList<TimeSpan> values)
 186    {
 187        var ticks = values.Select(v => v.Ticks).ToList();
 188        return (
 189            new(ticks.Sum()),
 190            new((long)ticks.Average()),
 191            new(ticks.Min()),
 192            new(ticks.Max()));
 193    }
 194}
 195