package mathext import ( "gogs.mikescher.com/BlackForestBytes/goext/exerr" "gogs.mikescher.com/BlackForestBytes/goext/langext" ) func Sum[T langext.NumberConstraint](v []T) T { total := T(0) for _, v := range v { total += v } return total } func Mean[T langext.FloatConstraint](v []T) T { return Sum(v) / T(len(v)) } func Median[T langext.FloatConstraint](v []T) T { if len(v)%2 == 1 { return v[len(v)/2] } else { return (v[len(v)/2-1] + v[len(v)/2]) / T(2) } } func ArrMin[T langext.OrderedConstraint](v []T) T { r := v[0] for _, val := range v { if val < r { r = val } } return r } func ArrMax[T langext.OrderedConstraint](v []T) T { r := v[0] for _, val := range v { if val > r { r = val } } return r } func MustPercentile[T langext.NumberConstraint](rawdata []T, percentile float64) T { v, err := Percentile(rawdata, percentile) if err != nil { panic(err) } return v } func Percentile[T langext.NumberConstraint](rawdata []T, percentile float64) (T, error) { v, err := FloatPercentile(rawdata, percentile) if err != nil { return T(0), err } return T(v), nil } func FloatPercentile[T langext.NumberConstraint](rawdata []T, percentile float64) (float64, error) { if len(rawdata) == 0 { return 0, exerr.New(exerr.TypeAssert, "no data to calculate percentile").Any("percentile", percentile).Build() } if percentile < 0 || percentile > 100 { return 0, exerr.New(exerr.TypeAssert, "percentile out of range").Any("percentile", percentile).Build() } data := langext.ArrCopy(rawdata) langext.Sort(data) idxFloat := float64(len(data)-1) * (percentile / float64(100)) idxInt := int(idxFloat) // exact match on index if idxFloat == float64(idxInt) { return float64(data[idxInt]), nil } // linear interpolation v1 := data[idxInt] v2 := data[idxInt+1] weight := idxFloat - float64(idxInt) valFloat := (float64(v1) * (1 - weight)) + (float64(v2) * weight) return valFloat, nil }