using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace ClosedXML.Excel.CalcEngine
{
internal class Tally : IEnumerable<Object>
{
private readonly List<object> _list = new List<object>();
private readonly bool NumbersOnly;
private double[] _numericValues;
public Tally()
: this(false)
{ }
public Tally(bool numbersOnly)
: this(null, numbersOnly)
{ }
public Tally(IEnumerable<Expression> p)
: this(p, false)
{ }
public Tally(IEnumerable<Expression> p, bool numbersOnly)
{
if (p != null)
{
foreach (var e in p)
{
Add(e);
}
}
NumbersOnly = numbersOnly;
}
public void Add(Expression e)
{
// handle enumerables
var ienum = e as IEnumerable;
if (ienum != null)
{
foreach (var value in ienum)
{
_list.Add(value);
}
_numericValues = null;
return;
}
// handle expressions
var val = e.Evaluate();
var valEnumerable = val as IEnumerable;
if (valEnumerable == null || val is string)
_list.Add(val);
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 Count(NumbersOnly);
}
public double Count(bool numbersOnly)
{
if (numbersOnly)
return NumericValuesInternal().Length;
else
return _list.Count(o => !CalcEngineHelpers.ValueIsBlank(o));
}
private IEnumerable<double> NumericValuesEnumerable()
{
foreach (var value in _list)
{
var vEnumerable = value as IEnumerable;
if (vEnumerable == null || vEnumerable is string)
{
if (TryParseToDouble(value, out double tmp))
yield return tmp;
}
else
{
foreach (var v in vEnumerable)
{
if (TryParseToDouble(v, out double tmp))
yield return tmp;
}
}
}
}
private bool TryParseToDouble(object value, out double d)
{
if (value.IsNumber())
{
d = Convert.ToDouble(value);
return true;
}
else if (value is DateTime)
{
d = Convert.ToDouble(((DateTime)value).ToOADate());
return true;
}
else
{
return double.TryParse(value.ToString(), out d);
}
}
private double[] NumericValuesInternal()
=> LazyInitializer.EnsureInitialized(ref _numericValues, () => NumericValuesEnumerable().ToArray());
public IEnumerable<double> NumericValues()
=> NumericValuesInternal().AsEnumerable();
public double Product()
{
var nums = NumericValuesInternal();
return nums.Length == 0
? 0
: nums.Aggregate(1d, (a, b) => a * b);
}
public double Sum() => NumericValuesInternal().Sum();
public double Average()
{
var nums = NumericValuesInternal();
if (nums.Length == 0) throw new ApplicationException("No values");
return nums.Average();
}
public double Min()
{
var nums = NumericValuesInternal();
return nums.Length == 0 ? 0 : nums.Min();
}
public double Max()
{
var nums = NumericValuesInternal();
return nums.Length == 0 ? 0 : nums.Max();
}
public double Range() => Max() - Min();
private static double Sum2(IEnumerable<double> nums)
{
return nums.Sum(d => d * d);
}
public double VarP()
{
var nums = NumericValuesInternal();
var avg = nums.Average();
var sum2 = Sum2(nums);
var count = nums.Length;
return count <= 1 ? 0 : sum2 / count - avg * avg;
}
public double StdP()
{
var nums = NumericValuesInternal();
var avg = nums.Average();
var sum2 = nums.Sum(d => d * d);
var count = nums.Length;
return count <= 1 ? 0 : Math.Sqrt(sum2 / count - avg * avg);
}
public double Var()
{
var nums = NumericValuesInternal();
var avg = nums.Average();
var sum2 = Sum2(nums);
var count = nums.Length;
return count <= 1 ? 0 : (sum2 / count - avg * avg) * count / (count - 1);
}
public double Std()
{
var values = NumericValuesInternal();
var count = values.Length;
double ret = 0;
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) / (count - 1));
}
else
{
throw new ApplicationException("No values");
}
return ret;
}
public IEnumerator<object> GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}