< Summary

Information
Class: MyNet.Observable.Collections.Statistics.DoubleStatisticsComputer
Assembly: MyNet.Observable
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Collections/Statistics/CollectionStatistics.cs
Tag: 323_28699572109
Line coverage
66%
Covered lines: 2
Uncovered lines: 1
Coverable lines: 3
Total lines: 195
Line coverage: 66.6%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
get_Empty()100%210%
Compute(...)100%11100%

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
 32    private CollectionStatistics(ExtendedCollection<T> collection, Func<T, TValue> valueSelector, IStatisticsComputer<TV
 33    {
 34        _collection = collection ?? throw new ArgumentNullException(nameof(collection));
 35        _valueSelector = valueSelector ?? throw new ArgumentNullException(nameof(valueSelector));
 36        _computer = computer;
 37
 38        Disposables.Add(_collection.Connect().Subscribe(_ => RecalculateValues()));
 39        _collection.PropertyChanged += OnCollectionPropertyChanged;
 40        Disposables.Add(Disposable.Create(() => _collection.PropertyChanged -= OnCollectionPropertyChanged));
 41
 42        UpdateFilteredPercentage();
 43        RecalculateValues();
 44    }
 45
 46    /// <summary>
 47    /// Gets the ratio of filtered items to source items, or <c>0</c> when the source is empty.
 48    /// </summary>
 49    public double FilteredPercentage => _filteredPercentage;
 50
 51    /// <summary>
 52    /// Gets the sum of values across filtered items.
 53    /// </summary>
 54    public TValue Sum => _sum;
 55
 56    /// <summary>
 57    /// Gets the average of values across filtered items.
 58    /// </summary>
 59    public TValue Average => _average;
 60
 61    /// <summary>
 62    /// Gets the minimum value across filtered items.
 63    /// </summary>
 64    public TValue Min => _min;
 65
 66    /// <summary>
 67    /// Gets the maximum value across filtered items.
 68    /// </summary>
 69    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
 78        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
 87        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    {
 96        if (e.PropertyName is nameof(ExtendedCollection<>.Count) or nameof(ExtendedCollection<>.SourceCount))
 97            UpdateFilteredPercentage();
 98
 99        if (e.PropertyName is nameof(ExtendedCollection<>.Count))
 100            RecalculateValues();
 101    }
 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    {
 108        var value = _collection.SourceCount == 0
 109            ? 0
 110            : (double)_collection.Count / _collection.SourceCount;
 111
 112        SetProperty(ref _filteredPercentage, value, nameof(FilteredPercentage));
 113    }
 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    {
 120        var values = _collection.Items.Select(_valueSelector).ToList();
 121        var (sum, average, min, max) = values.Count == 0
 122            ? _computer.Empty
 123            : _computer.Compute(values);
 124
 125        SetProperty(ref _sum, sum, nameof(Sum));
 126        SetProperty(ref _average, average, nameof(Average));
 127        SetProperty(ref _min, min, nameof(Min));
 128        SetProperty(ref _max, max, nameof(Max));
 129    }
 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>
 3159    public static DoubleStatisticsComputer Instance { get; } = new();
 160
 161    /// <inheritdoc/>
 162    public (double Sum, double Average, double Min, double Max) Empty =>
 0163        (0, double.NaN, double.NaN, double.NaN);
 164
 165    /// <inheritdoc/>
 166    public (double Sum, double Average, double Min, double Max) Compute(IReadOnlyList<double> values) =>
 42167        (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