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  }