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  }