diff --git a/ClosedXML/Excel/CalcEngine/Functions/Tally.cs b/ClosedXML/Excel/CalcEngine/Functions/Tally.cs index 78e0842..5cf212e 100644 --- a/ClosedXML/Excel/CalcEngine/Functions/Tally.cs +++ b/ClosedXML/Excel/CalcEngine/Functions/Tally.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace ClosedXML.Excel.CalcEngine { @@ -10,6 +11,9 @@ private readonly List _list = new List(); private readonly bool NumbersOnly; + private double[] _numericValues; + + public Tally() : this(false) { } @@ -32,7 +36,7 @@ } } - this.NumbersOnly = numbersOnly; + NumbersOnly = numbersOnly; } public void Add(Expression e) @@ -45,6 +49,7 @@ { _list.Add(value); } + _numericValues = null; return; } @@ -56,40 +61,45 @@ else foreach (var v in valEnumerable) _list.Add(v); + + _numericValues = null; } public void AddValue(Object v) { _list.Add(v); + _numericValues = null; } public double Count() { - return this.Count(this.NumbersOnly); + return Count(NumbersOnly); } public double Count(bool numbersOnly) { if (numbersOnly) - return NumericValues().Count(); + return NumericValuesInternal().Length; else - return _list.Where(o => !Statistical.IsBlank(o)).Count(); + return _list.Count(o => !Statistical.IsBlank(o)); } - public IEnumerable NumericValues() + IEnumerable NumericValuesEnumerable() { - var retVal = new List(); foreach (var value in _list) { - Double tmp; + double tmp; var vEnumerable = value as IEnumerable; - if (vEnumerable == null && Double.TryParse(value.ToString(), out tmp)) - yield return tmp; + if (vEnumerable == null) + { + if (double.TryParse(value.ToString(), out tmp)) + yield return tmp; + } else { foreach (var v in vEnumerable) { - if (Double.TryParse(v.ToString(), out tmp)) + if (double.TryParse(v.ToString(), out tmp)) yield return tmp; break; } @@ -97,84 +107,88 @@ } } + double[] NumericValuesInternal() + => LazyInitializer.EnsureInitialized(ref _numericValues, () => NumericValuesEnumerable().ToArray()); + + public IEnumerable NumericValues() + => NumericValuesInternal().AsEnumerable(); + public double Product() { - var nums = NumericValues(); - if (!nums.Any()) return 0; - - Double retVal = 1; - nums.ForEach(n => retVal *= n); - - return retVal; + var nums = NumericValuesInternal(); + return nums.Length == 0 + ? 0 + : nums.Aggregate(1d, (a, b) => a * b); } - public double Sum() { return NumericValues().Sum(); } + public double Sum() => NumericValuesInternal().Sum(); public double Average() { - if (NumericValues().Any()) - return NumericValues().Average(); - else - throw new ApplicationException("No values"); + var nums = NumericValuesInternal(); + if (nums.Length == 0) throw new ApplicationException("No values"); + return nums.Average(); } public double Min() { - return NumericValues().Any() ? NumericValues().Min() : 0; + var nums = NumericValuesInternal(); + return nums.Length == 0 ? 0 : nums.Min(); } public double Max() { - return NumericValues().Any() ? NumericValues().Max() : 0; + var nums = NumericValuesInternal(); + return nums.Length == 0 ? 0 : nums.Max(); } - public double Range() - { - var nums = NumericValues(); - return nums.Max() - nums.Min(); - } + public double Range() => Max() - Min(); - private double Sum2(List nums) + static double Sum2(IEnumerable nums) { return nums.Sum(d => d * d); } public double VarP() { - var nums = NumericValues(); + var nums = NumericValuesInternal(); var avg = nums.Average(); - var sum2 = nums.Sum(d => d * d); - return nums.Count() <= 1 ? 0 : sum2 / nums.Count() - avg * avg; + var sum2 = Sum2(nums); + var count = nums.Length; + return count <= 1 ? 0 : sum2 / count - avg * avg; } public double StdP() { - var nums = NumericValues(); + var nums = NumericValuesInternal(); var avg = nums.Average(); var sum2 = nums.Sum(d => d * d); - return nums.Count() <= 1 ? 0 : Math.Sqrt(sum2 / nums.Count() - avg * avg); + var count = nums.Length; + return count <= 1 ? 0 : Math.Sqrt(sum2 / count - avg * avg); } public double Var() { - var nums = NumericValues(); + var nums = NumericValuesInternal(); var avg = nums.Average(); - var sum2 = nums.Sum(d => d * d); - return nums.Count() <= 1 ? 0 : (sum2 / nums.Count() - avg * avg) * nums.Count() / (nums.Count() - 1); + var sum2 = Sum2(nums); + var count = nums.Length; + return count <= 1 ? 0 : (sum2 / count - avg * avg) * count / (count - 1); } public double Std() { - var values = NumericValues(); + var values = NumericValuesInternal(); + var count = values.Length; double ret = 0; - if (values.Any()) + if (count != 0) { //Compute the Average double avg = values.Average(); //Perform the Sum of (value-avg)_2_2 double sum = values.Sum(d => Math.Pow(d - avg, 2)); //Put it all together - ret = Math.Sqrt((sum) / (values.Count() - 1)); + ret = Math.Sqrt((sum) / (count - 1)); } else {