github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/segment/tracing/utils/lineartimefinding.go (about)

     1  package utils
     2  
     3  import (
     4  	"math"
     5  	"math/rand"
     6  	"sort"
     7  
     8  	log "github.com/sirupsen/logrus"
     9  )
    10  
    11  func FindPercentileData(arr []uint64, percentile int) uint64 {
    12  	if len(arr) == 0 {
    13  		log.Error("FindPercentileData: no duration exists")
    14  		return 0
    15  	}
    16  	if percentile > 100 || percentile < 0 {
    17  		log.Error("FindPercentileData: percentile should in this period: [0, 100]")
    18  		return 0
    19  	}
    20  
    21  	k := math.Floor(float64(percentile*(len(arr))) / float64(100))
    22  	return quickSelect(arr, int(k), &rand.Rand{})
    23  }
    24  
    25  // https://rcoh.me/posts/linear-time-median-finding/
    26  func quickSelect(arr []uint64, k int, rand *rand.Rand) uint64 {
    27  	if len(arr) == 1 {
    28  		return arr[0]
    29  	}
    30  
    31  	pivot := pickPivot(arr, rand)
    32  
    33  	var lows, highs, pivots []uint64
    34  	for _, el := range arr {
    35  		switch {
    36  		case el < pivot:
    37  			lows = append(lows, el)
    38  		case el > pivot:
    39  			highs = append(highs, el)
    40  		default:
    41  			pivots = append(pivots, el)
    42  		}
    43  	}
    44  
    45  	if k < len(lows) {
    46  		return quickSelect(lows, k, rand)
    47  	} else if k < len(lows)+len(pivots) {
    48  		return pivots[0]
    49  	} else {
    50  		return quickSelect(highs, k-len(lows)-len(pivots), rand)
    51  	}
    52  }
    53  
    54  func pickPivot(arr []uint64, rand *rand.Rand) uint64 {
    55  	if len(arr) < 5 {
    56  		return nLogNMedian(arr)
    57  	}
    58  
    59  	chunks := chunked(arr, 5)
    60  	var fullChunks [][]uint64
    61  	for _, chunk := range chunks {
    62  		if len(chunk) == 5 {
    63  			fullChunks = append(fullChunks, chunk)
    64  		}
    65  	}
    66  
    67  	var sortedGroups [][]uint64
    68  	for _, chunk := range fullChunks {
    69  		sort.Slice(chunk, func(i, j int) bool {
    70  			return chunk[i] < chunk[j]
    71  		})
    72  		sortedGroups = append(sortedGroups, chunk)
    73  	}
    74  
    75  	medians := make([]uint64, len(sortedGroups))
    76  	for i, group := range sortedGroups {
    77  		medians[i] = group[2]
    78  	}
    79  
    80  	return QuickSelectMedian(medians, rand)
    81  }
    82  
    83  func nLogNMedian(arr []uint64) uint64 {
    84  
    85  	sort.Slice(arr, func(i, j int) bool {
    86  		return arr[i] < arr[j]
    87  	})
    88  	if len(arr)%2 == 1 {
    89  		return arr[len(arr)/2]
    90  	} else {
    91  		return (arr[len(arr)/2-1] + arr[len(arr)/2]) / 2
    92  	}
    93  }
    94  
    95  func QuickSelectMedian(arr []uint64, rand *rand.Rand) uint64 {
    96  	n := len(arr)
    97  	if n%2 == 1 {
    98  		return quickSelect(arr, n/2, rand)
    99  	} else {
   100  		return (quickSelect(arr, n/2-1, rand) + quickSelect(arr, n/2, rand)) / 2
   101  	}
   102  }
   103  
   104  func chunked(arr []uint64, chunkSize int) [][]uint64 {
   105  	numChunks := (len(arr) + chunkSize - 1) / chunkSize
   106  	result := make([][]uint64, numChunks)
   107  
   108  	for i := 0; i < numChunks; i++ {
   109  		start := i * chunkSize
   110  		length := chunkSize
   111  		if len(arr)-start < chunkSize {
   112  			length = len(arr) - start
   113  		}
   114  		result[i] = arr[start : start+length]
   115  	}
   116  
   117  	return result
   118  }