Prosto do rzeczy (i C #> 6,0), odpowiedź Dynamis wygląda tak:
public static double StdDev(this IEnumerable<double> values)
{
var count = values?.Count() ?? 0;
if (count <= 1) return 0;
var avg = values.Average();
var sum = values.Sum(d => Math.Pow(d - avg, 2));
return Math.Sqrt(sum / count);
}
Edycja 2020-08-27:
Wziąłem komentarze @David Clarke, aby wykonać testy wydajności i oto wyniki:
public static (double stdDev, double avg) StdDevFast(this List<double> values)
{
var count = values?.Count ?? 0;
if (count <= 1) return (0, 0);
var avg = GetAverage(values);
var sum = GetSumOfSquareDiff(values, avg);
return (Math.Sqrt(sum / count), avg);
}
private static double GetAverage(List<double> values)
{
double sum = 0.0;
for (int i = 0; i < values.Count; i++)
sum += values[i];
return sum / values.Count;
}
private static double GetSumOfSquareDiff(List<double> values, double avg)
{
double sum = 0.0;
for (int i = 0; i < values.Count; i++)
{
var diff = values[i] - avg;
sum += diff * diff;
}
return sum;
}
Przetestowałem to z listą miliona losowych dubli
. Oryginalna implementacja miała czas działania ~ 48 ms,
a implementacja zoptymalizowana pod kątem wydajności 2-3 ms,
więc jest to znacząca poprawa.
Kilka interesujących szczegółów:
pozbycie się Math.Pow zapewnia przyspieszenie o 33 ms!
List zamiast IEnumerable 6ms
ręcznie Obliczanie średniej 4ms
For-loop zamiast ForEach-loop 2ms
Array zamiast List przynosi tylko poprawę o ~ 2%, więc pominąłem to
używając single zamiast double nic nie daje
Dalsze obniżanie kodu i używanie goto (tak, GOTO ... nie używałem tego od asemblera z lat 90 ...) zamiast pętli for nie opłaca się, dzięki Bogu!
Przetestowałem również obliczenia równoległe, ma to sens na liście> 200.000 pozycji. Wygląda na to, że sprzęt i oprogramowanie wymagają dużo inicjalizacji i jest to dla małych list nieproduktywne.
Wszystkie testy zostały wykonane dwa razy z rzędu, aby pozbyć się czasu rozgrzewki.