github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/functions/utils/heap.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package utils 22 23 import ( 24 "container/heap" 25 "math" 26 "sort" 27 ) 28 29 // ValueIndexPair is a pair of float value and index at which it exists 30 type ValueIndexPair struct { 31 Val float64 32 Index int 33 } 34 35 type lessFn func(ValueIndexPair, ValueIndexPair) bool 36 37 func maxHeapLess(i, j ValueIndexPair) bool { 38 if equalWithNaNs(i.Val, j.Val) { 39 return i.Index > j.Index 40 } 41 return i.Val < j.Val || lesserIfNaNs(i.Val, j.Val) 42 } 43 44 func minHeapLess(i, j ValueIndexPair) bool { 45 if equalWithNaNs(i.Val, j.Val) { 46 return i.Index > j.Index 47 } 48 return i.Val > j.Val || lesserIfNaNs(i.Val, j.Val) 49 } 50 51 // Compares two floats for equality with NaNs taken into account. 52 func equalWithNaNs(i,j float64) bool { 53 return i == j || math.IsNaN(i) && math.IsNaN(j) 54 } 55 56 // Compares NaNs. 57 // Basically, we do not want to add NaNs to the heap when it has reached it's cap so this fn should be used to prevent this. 58 func lesserIfNaNs(i,j float64) bool { 59 return math.IsNaN(i) && !math.IsNaN(j) 60 } 61 62 // Compares two float64 values which one is lesser with NaNs. NaNs are always sorted away. 63 func LesserWithNaNs(i, j float64) bool { 64 return i < j || math.IsNaN(j) && !math.IsNaN(i) 65 } 66 67 // Compares two float64 values which one is greater with NaNs. NaNs are always sorted away. 68 func GreaterWithNaNs(i, j float64) bool { 69 return i > j || math.IsNaN(j) && !math.IsNaN(i) 70 } 71 72 // FloatHeap is a heap that can be given a maximum size 73 type FloatHeap struct { 74 isMaxHeap bool 75 capacity int 76 floatHeap *floatHeap 77 } 78 79 // NewFloatHeap builds a new FloatHeap based on first parameter 80 // and a capacity given by second parameter. Zero and negative 81 // values for maxSize provide an unbounded FloatHeap 82 func NewFloatHeap(isMaxHeap bool, capacity int) FloatHeap { 83 var less lessFn 84 if isMaxHeap { 85 less = maxHeapLess 86 } else { 87 less = minHeapLess 88 } 89 90 if capacity < 1 { 91 capacity = 0 92 } 93 94 floatHeap := &floatHeap{ 95 heap: make([]ValueIndexPair, 0, capacity), 96 less: less, 97 } 98 99 heap.Init(floatHeap) 100 return FloatHeap{ 101 isMaxHeap: isMaxHeap, 102 capacity: capacity, 103 floatHeap: floatHeap, 104 } 105 } 106 107 // Push pushes a value and index pair to the heap 108 func (fh *FloatHeap) Push(value float64, index int) { 109 h := fh.floatHeap 110 // If capacity is zero or negative, allow infinite size heap 111 if fh.capacity > 0 { 112 // At max size, drop or replace incoming value. 113 // Otherwise, continue as normal 114 if len(h.heap) >= fh.capacity { 115 peek := h.heap[0] 116 // Compare incoming value with current top of heap. 117 // Decide if to replace the current top, or to drop incoming 118 // value as appropriate for this min/max heap 119 // 120 // If values are equal, do not add incoming value regardless 121 // 122 // NB(arnikola): unfortunately, can't just replace first 123 // element as it may not respect internal order. Need to 124 // run heap.Fix() to rectify this 125 if (fh.isMaxHeap && GreaterWithNaNs(value, peek.Val)) || 126 (!fh.isMaxHeap && LesserWithNaNs(value, peek.Val)) { 127 h.heap[0] = ValueIndexPair{ 128 Val: value, 129 Index: index, 130 } 131 132 heap.Fix(h, 0) 133 } 134 135 return 136 } 137 138 // Otherwise, fallthrough 139 } 140 141 heap.Push(h, ValueIndexPair{ 142 Val: value, 143 Index: index, 144 }) 145 } 146 147 // Len returns the current length of the heap 148 func (fh *FloatHeap) Len() int { 149 return fh.floatHeap.Len() 150 } 151 152 // Cap returns the capacity of the heap 153 func (fh *FloatHeap) Cap() int { 154 return fh.capacity 155 } 156 157 // Reset resets the heap 158 func (fh *FloatHeap) Reset() { 159 fh.floatHeap.heap = fh.floatHeap.heap[:0] 160 } 161 162 // Flush flushes the float heap and resets it. Does not guarantee order. 163 func (fh *FloatHeap) Flush() []ValueIndexPair { 164 values := fh.floatHeap.heap 165 fh.Reset() 166 return values 167 } 168 169 // OrderedFlush flushes the float heap and returns values in order. 170 func (fh *FloatHeap) OrderedFlush() []ValueIndexPair { 171 flushed := fh.Flush() 172 sort.Slice(flushed, func(i, j int) bool { 173 return !fh.floatHeap.less(flushed[i], flushed[j]) //reverse sort 174 }) 175 return flushed 176 } 177 178 // Peek reveals the top value of the heap without mutating the heap. 179 func (fh *FloatHeap) Peek() (ValueIndexPair, bool) { 180 h := fh.floatHeap.heap 181 if len(h) == 0 { 182 return ValueIndexPair{}, false 183 } 184 return h[0], true 185 } 186 187 // floatHeap is a heap that can be given a maximum size 188 type floatHeap struct { 189 less lessFn 190 heap []ValueIndexPair 191 } 192 193 // Assert that floatHeap is a heap.Interface 194 var _ heap.Interface = (*floatHeap)(nil) 195 196 // Len gives the length of items in the heap 197 func (h *floatHeap) Len() int { 198 return len(h.heap) 199 } 200 201 // Less is true if value of i is less than value of j 202 func (h *floatHeap) Less(i, j int) bool { 203 return h.less(h.heap[i], h.heap[j]) 204 } 205 206 // Swap swaps values at these indices 207 func (h *floatHeap) Swap(i, j int) { 208 h.heap[i], h.heap[j] = h.heap[j], h.heap[i] 209 } 210 211 // Push pushes a ValueIndexPair to the FloatMaxHeap 212 func (h *floatHeap) Push(x interface{}) { 213 pair := x.(ValueIndexPair) 214 h.heap = append(h.heap, pair) 215 } 216 217 // Pop pops a ValueIndexPair from the FloatMaxHeap 218 func (h *floatHeap) Pop() interface{} { 219 old := h.heap 220 n := len(old) 221 tail := old[n-1] 222 h.heap = old[:n-1] 223 return tail 224 }