-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathsummary.go
109 lines (95 loc) · 2.67 KB
/
summary.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// Copyright (C) 2018. See AUTHORS.
package random
import (
"math"
"sort"
)
// summaryElement is a list of elements for a summary for fast queries.
type summaryElement struct {
rank int64
value float64
}
// Summary is produced by a Random and can answer queries about the
// distribution that was observed.
type Summary struct {
n float64
elements []summaryElement
}
// numElements returns the number of elements stored in the finished Random.
func (r FinishedRandom) numElements() int {
if len(r.Buffers) == 0 {
return 0
}
return len(r.Buffers) * len(r.Buffers[0].Data)
}
// Summarize creates a Summary for querying.
func (r FinishedRandom) Summarize() Summary {
// factor out the allocation for benchmarking.
return r.summarize(make([]summaryElement, 0, r.numElements()))
}
func (r FinishedRandom) summarize(elements []summaryElement) Summary {
// we summarize in a two step process. step one is to create a slice of
// summary elements with the rank actually being the level. step two is
// to create a rolling sum of the levels and fix up the ranks.
// make the slices that we're going to sort with their associated levels.
items := make([]mergeItem, 0, len(r.Buffers))
for i := range r.Buffers {
buf := &r.Buffers[i]
// if the buffer has no data, there's no point in considering it for
// merging
if len(buf.Data) == 0 {
continue
}
// ensure the buffer is sorted
if !buf.Sorted {
buf.sort()
}
// add the merge buffer and associate the data with the level.
items = append(items, mergeItem{
data: buf.Data,
level: int64(buf.Level),
})
}
// create the list of summary elements sorted by value with the ranks
// growing by the buffer's level.
merge := newMergeSorter(items)
rank := int64(0)
for {
value, level, ok := merge.next()
if !ok {
break
}
elements = append(elements, summaryElement{
rank: rank,
value: value,
})
rank += (1 << uint64(level))
}
return Summary{
n: float64(r.N),
elements: elements,
}
}
// Query returns the estimated value at the given percentile.
func (s Summary) Query(ptile float64) float64 {
target := int64(math.Ceil(s.n * ptile))
idx := sort.Search(len(s.elements), func(idx int) bool {
return s.elements[idx].rank >= target
})
if idx >= len(s.elements) {
return s.elements[len(s.elements)-1].value
}
below_idx, above_idx := 0, len(s.elements)
if idx < len(s.elements) {
above_idx = idx
}
if idx > 1 {
below_idx = idx - 1
}
if above_idx == below_idx {
return s.elements[above_idx].value
}
below, above := s.elements[below_idx], s.elements[above_idx]
x := float64(target-below.rank) / float64(above.rank-below.rank)
return below.value + (above.value-below.value)*x
}