github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/functions/utils/heap_test.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 "math" 25 "math/rand" 26 "sort" 27 "testing" 28 29 "github.com/m3db/m3/src/query/test/compare" 30 31 "github.com/stretchr/testify/assert" 32 ) 33 34 type maxSlice []ValueIndexPair 35 36 func (s maxSlice) Len() int { return len(s) } 37 func (s maxSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 38 func (s maxSlice) Less(i, j int) bool { 39 if s[i].Val == s[j].Val { 40 return s[i].Index > s[j].Index 41 } 42 return s[i].Val < s[j].Val 43 } 44 45 type minSlice []ValueIndexPair 46 47 func (s minSlice) Len() int { return len(s) } 48 func (s minSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 49 func (s minSlice) Less(i, j int) bool { 50 if s[i].Val == s[j].Val { 51 return s[i].Index > s[j].Index 52 } 53 54 return s[i].Val > s[j].Val 55 } 56 57 var heapTests = []struct { 58 name string 59 capacity int 60 values []float64 61 expectedMax []ValueIndexPair 62 expectedMin []ValueIndexPair 63 }{ 64 { 65 "capacity 0", 66 0, 67 []float64{1, 8, 2, 4, 2, 3, 0, -3, 3}, 68 []ValueIndexPair{ 69 {-3, 7}, 70 {0, 6}, 71 {1, 0}, 72 {2, 4}, 73 {2, 2}, 74 {3, 8}, 75 {3, 5}, 76 {4, 3}, 77 {8, 1}, 78 }, 79 []ValueIndexPair{ 80 {8, 1}, 81 {4, 3}, 82 {3, 8}, 83 {3, 5}, 84 {2, 4}, 85 {2, 2}, 86 {1, 0}, 87 {0, 6}, 88 {-3, 7}, 89 }, 90 }, 91 { 92 "capacity 1", 93 1, 94 []float64{1, 8, 2, 4, 2, 3, 0, -3, 3}, 95 []ValueIndexPair{ 96 {8, 1}, 97 }, 98 []ValueIndexPair{ 99 {-3, 7}, 100 }, 101 }, 102 { 103 "capacity 3", 104 3, 105 []float64{1, 8, 2, 4, 2, 3, 0, -3, 3}, 106 []ValueIndexPair{ 107 // NB: since two values at 3, index is first one to come in 108 {3, 5}, 109 {4, 3}, 110 {8, 1}, 111 }, 112 []ValueIndexPair{ 113 {1, 0}, 114 {0, 6}, 115 {-3, 7}, 116 }, 117 }, 118 { 119 "capacity 4", 120 4, 121 []float64{1, 8, 2, 4, 2, 3, 0, -3, 3}, 122 []ValueIndexPair{ 123 {3, 8}, 124 {3, 5}, 125 {4, 3}, 126 {8, 1}, 127 }, 128 []ValueIndexPair{ 129 {2, 2}, 130 {1, 0}, 131 {0, 6}, 132 {-3, 7}, 133 }, 134 }, 135 } 136 137 func TestMaxHeap(t *testing.T) { 138 for _, tt := range heapTests { 139 t.Run(tt.name, func(t *testing.T) { 140 capacity := tt.capacity 141 h := NewFloatHeap(true, capacity) 142 assert.Equal(t, capacity, h.Cap()) 143 _, seen := h.Peek() 144 assert.False(t, seen) 145 146 for i, v := range tt.values { 147 h.Push(v, i) 148 if capacity < 1 { 149 // No max size; length should be index + 1 150 assert.Equal(t, i+1, h.Len(), "capacity <= 0, no max capacity") 151 } else { 152 assert.True(t, h.Len() <= capacity, "length is larger than capacity") 153 } 154 } 155 156 peek, seen := h.Peek() 157 assert.True(t, seen) 158 assert.Equal(t, peek, tt.expectedMax[0]) 159 160 // Flush and sort results (Flush does not care about order) 161 actual := h.Flush() 162 sort.Sort(maxSlice(actual)) 163 assert.Equal(t, tt.expectedMax, actual) 164 // Assert Flush flushes the heap 165 assert.Equal(t, 0, h.floatHeap.Len()) 166 _, seen = h.Peek() 167 assert.False(t, seen) 168 }) 169 } 170 } 171 172 func TestMinHeap(t *testing.T) { 173 for _, tt := range heapTests { 174 t.Run(tt.name, func(t *testing.T) { 175 capacity := tt.capacity 176 h := NewFloatHeap(false, capacity) 177 assert.Equal(t, capacity, h.Cap()) 178 _, seen := h.Peek() 179 assert.False(t, seen) 180 181 for i, v := range tt.values { 182 h.Push(v, i) 183 } 184 185 peek, seen := h.Peek() 186 assert.True(t, seen) 187 assert.Equal(t, peek, tt.expectedMin[0]) 188 189 // Flush and sort results (Flush does not care about order) 190 actual := h.Flush() 191 sort.Sort(minSlice(actual)) 192 assert.Equal(t, tt.expectedMin, actual) 193 // Assert Flush flushes the heap 194 assert.Equal(t, 0, h.floatHeap.Len()) 195 _, seen = h.Peek() 196 assert.False(t, seen) 197 }) 198 } 199 } 200 201 func TestNegativeCapacityHeap(t *testing.T) { 202 h := NewFloatHeap(false, -1) 203 assert.Equal(t, 0, h.Cap()) 204 _, seen := h.Peek() 205 assert.False(t, seen) 206 207 length := 10000 208 testArray := make([]float64, length) 209 for i := range testArray { 210 testArray[i] = rand.Float64() 211 h.Push(testArray[i], i) 212 } 213 214 assert.Equal(t, length, h.Len()) 215 flushed := h.Flush() 216 assert.Equal(t, length, len(flushed)) 217 assert.Equal(t, 0, h.Len()) 218 for _, pair := range flushed { 219 assert.Equal(t, testArray[pair.Index], pair.Val) 220 } 221 } 222 223 func equalPairs(t *testing.T, expected, actual []ValueIndexPair) { 224 assert.Equal(t, len(expected), len(actual)) 225 for i, e := range expected { 226 compare.EqualsWithNans(t, e.Val, actual[i].Val) 227 assert.Equal(t, e.Index, actual[i].Index) 228 } 229 } 230 231 func TestFlushOrdered(t *testing.T) { 232 maxHeap := NewFloatHeap(true, 3) 233 234 maxHeap.Push(0.1, 0) 235 maxHeap.Push(1.1, 1) 236 maxHeap.Push(2.1, 2) 237 maxHeap.Push(3.1, 3) 238 239 actualMax := maxHeap.OrderedFlush() 240 241 assert.Equal(t, []ValueIndexPair{ 242 {Val: 3.1, Index: 3}, 243 {Val: 2.1, Index: 2}, 244 {Val: 1.1, Index: 1}, 245 }, actualMax) 246 assert.Equal(t, 0, maxHeap.Len()) 247 248 minHeap := NewFloatHeap(false, 3) 249 minHeap.Push(0.1, 0) 250 minHeap.Push(1.1, 1) 251 minHeap.Push(2.1, 2) 252 minHeap.Push(3.1, 3) 253 254 actualMin := minHeap.OrderedFlush() 255 256 assert.Equal(t, []ValueIndexPair{ 257 {Val: 0.1, Index: 0}, 258 {Val: 1.1, Index: 1}, 259 {Val: 2.1, Index: 2}, 260 }, actualMin) 261 assert.Equal(t, 0, minHeap.Len()) 262 } 263 264 func TestFlushOrderedWhenRandomInsertionOrder(t *testing.T) { 265 maxHeap := NewFloatHeap(true, 3) 266 267 maxHeap.Push(math.NaN(), 4) 268 maxHeap.Push(0.1, 0) 269 maxHeap.Push(2.1, 2) 270 maxHeap.Push(1.1, 1) 271 maxHeap.Push(3.1, 3) 272 maxHeap.Push(math.NaN(), 5) 273 274 actualMax := maxHeap.OrderedFlush() 275 276 assert.Equal(t, []ValueIndexPair{ 277 {Val: 3.1, Index: 3}, 278 {Val: 2.1, Index: 2}, 279 {Val: 1.1, Index: 1}, 280 }, actualMax) 281 assert.Equal(t, 0, maxHeap.Len()) 282 283 minHeap := NewFloatHeap(false, 3) 284 maxHeap.Push(math.NaN(), 4) 285 minHeap.Push(0.1, 0) 286 minHeap.Push(2.1, 2) 287 minHeap.Push(1.1, 1) 288 minHeap.Push(3.1, 3) 289 maxHeap.Push(math.NaN(), 5) 290 291 actualMin := minHeap.OrderedFlush() 292 293 assert.Equal(t, []ValueIndexPair{ 294 {Val: 0.1, Index: 0}, 295 {Val: 1.1, Index: 1}, 296 {Val: 2.1, Index: 2}, 297 }, actualMin) 298 assert.Equal(t, 0, minHeap.Len()) 299 } 300 301 func TestFlushOrderedWhenRandomInsertionOrderAndTakeNaNs(t *testing.T) { 302 maxHeap := NewFloatHeap(true, 3) 303 maxHeap.Push(math.NaN(), 4) 304 maxHeap.Push(1.1, 1) 305 maxHeap.Push(3.1, 3) 306 maxHeap.Push(math.NaN(), 5) 307 308 actualMax := maxHeap.OrderedFlush() 309 310 equalPairs(t, []ValueIndexPair{ 311 {Val: 3.1, Index: 3}, 312 {Val: 1.1, Index: 1}, 313 {Val: math.NaN(), Index: 4}, 314 }, actualMax) 315 assert.Equal(t, 0, maxHeap.Len()) 316 317 minHeap := NewFloatHeap(false, 3) 318 minHeap.Push(math.NaN(), 4) 319 minHeap.Push(0.1, 0) 320 minHeap.Push(2.1, 2) 321 minHeap.Push(math.NaN(), 5) 322 323 actualMin := minHeap.OrderedFlush() 324 325 equalPairs(t, []ValueIndexPair{ 326 {Val: 0.1, Index: 0}, 327 {Val: 2.1, Index: 2}, 328 {Val: math.NaN(), Index: 4}, 329 }, actualMin) 330 assert.Equal(t, 0, minHeap.Len()) 331 } 332 333 func TestSortLesserWithNaNs(t *testing.T) { 334 actual := []float64{ 5.0, 4.1, math.NaN(), 8.6, 0.1 } 335 expected := []float64{ 0.1, 4.1, 5.0, 8.6, math.NaN() } 336 337 sort.Slice(actual, func(i, j int) bool { 338 return LesserWithNaNs(actual[i], actual[j]) 339 }) 340 341 compare.EqualsWithNans(t, expected, actual) 342 } 343 344 func TestSortGreaterWithNaNs(t *testing.T) { 345 actual := []float64{ 5.0, 4.1, math.NaN(), 8.6, 0.1 } 346 expected := []float64{ 8.6, 5.0, 4.1, 0.1, math.NaN() } 347 348 sort.Slice(actual, func(i, j int) bool { 349 return GreaterWithNaNs(actual[i], actual[j]) 350 }) 351 352 compare.EqualsWithNans(t, expected, actual) 353 }