Merge pull request #3 from mattbostock/avoid_map_iter
Avoid iterating on maps
Björn Rabenstein authored 5 years ago
GitHub committed 5 years ago
76 | 76 | // is guaranteed to be within (Quantile±Epsilon). |
77 | 77 | // |
78 | 78 | // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties. |
79 | func NewTargeted(targets map[float64]float64) *Stream { | |
79 | func NewTargeted(targetMap map[float64]float64) *Stream { | |
80 | // Convert map to slice to avoid slow iterations on a map. | |
81 | // ƒ is called on the hot path, so converting the map to a slice | |
82 | // beforehand results in significant CPU savings. | |
83 | targets := targetMapToSlice(targetMap) | |
84 | ||
80 | 85 | ƒ := func(s *stream, r float64) float64 { |
81 | 86 | var m = math.MaxFloat64 |
82 | 87 | var f float64 |
83 | for quantile, epsilon := range targets { | |
84 | if quantile*s.n <= r { | |
85 | f = (2 * epsilon * r) / quantile | |
88 | for _, t := range targets { | |
89 | if t.quantile*s.n <= r { | |
90 | f = (2 * t.epsilon * r) / t.quantile | |
86 | 91 | } else { |
87 | f = (2 * epsilon * (s.n - r)) / (1 - quantile) | |
92 | f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile) | |
88 | 93 | } |
89 | 94 | if f < m { |
90 | 95 | m = f |
93 | 98 | return m |
94 | 99 | } |
95 | 100 | return newStream(ƒ) |
101 | } | |
102 | ||
103 | type target struct { | |
104 | quantile float64 | |
105 | epsilon float64 | |
106 | } | |
107 | ||
108 | func targetMapToSlice(targetMap map[float64]float64) []target { | |
109 | targets := make([]target, 0, len(targetMap)) | |
110 | ||
111 | for quantile, epsilon := range targetMap { | |
112 | t := target{ | |
113 | quantile: quantile, | |
114 | epsilon: epsilon, | |
115 | } | |
116 | targets = append(targets, t) | |
117 | } | |
118 | ||
119 | return targets | |
96 | 120 | } |
97 | 121 | |
98 | 122 | // Stream computes quantiles for a stream of float64s. It is not thread-safe by |