github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/spatial/kdtree/medians.go (about)

     1  // Copyright ©2019 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package kdtree
     6  
     7  import (
     8  	"sort"
     9  
    10  	"golang.org/x/exp/rand"
    11  )
    12  
    13  // Partition partitions list such that all elements less than the value at
    14  // pivot prior to the call are placed before that element and all elements
    15  // greater than that value are placed after it. The final location of the
    16  // element at pivot prior to the call is returned.
    17  func Partition(list sort.Interface, pivot int) int {
    18  	var index, last int
    19  	if last = list.Len() - 1; last < 0 {
    20  		return -1
    21  	}
    22  	list.Swap(pivot, last)
    23  	for i := 0; i < last; i++ {
    24  		if !list.Less(last, i) {
    25  			list.Swap(index, i)
    26  			index++
    27  		}
    28  	}
    29  	list.Swap(last, index)
    30  	return index
    31  }
    32  
    33  // SortSlicer satisfies the sort.Interface and is able to slice itself.
    34  type SortSlicer interface {
    35  	sort.Interface
    36  	Slice(start, end int) SortSlicer
    37  }
    38  
    39  // Select partitions list such that all elements less than the kth element
    40  // are placed before k in the resulting list and all elements greater than
    41  // it are placed after the position k.
    42  func Select(list SortSlicer, k int) int {
    43  	var (
    44  		start int
    45  		end   = list.Len()
    46  	)
    47  	if k >= end {
    48  		if k == 0 {
    49  			return 0
    50  		}
    51  		panic("kdtree: index out of range")
    52  	}
    53  	if start == end-1 {
    54  		return k
    55  	}
    56  
    57  	for {
    58  		if start == end {
    59  			panic("kdtree: internal inconsistency")
    60  		}
    61  		sub := list.Slice(start, end)
    62  		pivot := Partition(sub, rand.Intn(sub.Len()))
    63  		switch {
    64  		case pivot == k:
    65  			return k
    66  		case k < pivot:
    67  			end = pivot + start
    68  		default:
    69  			k -= pivot
    70  			start += pivot
    71  		}
    72  	}
    73  }
    74  
    75  func min(a, b int) int {
    76  	if a < b {
    77  		return a
    78  	}
    79  	return b
    80  }
    81  
    82  // MedianOfMedians returns the index to the median value of the medians
    83  // of groups of 5 consecutive elements.
    84  func MedianOfMedians(list SortSlicer) int {
    85  	n := list.Len() / 5
    86  	for i := 0; i < n; i++ {
    87  		left := i * 5
    88  		sub := list.Slice(left, min(left+5, list.Len()-1))
    89  		Select(sub, 2)
    90  		list.Swap(i, left+2)
    91  	}
    92  	Select(list.Slice(0, min(n, list.Len()-1)), min(list.Len(), n/2))
    93  	return n / 2
    94  }
    95  
    96  // MedianOfRandoms returns the index to the median value of up to n randomly
    97  // chosen elements in list.
    98  func MedianOfRandoms(list SortSlicer, n int) int {
    99  	if l := list.Len(); l < n {
   100  		n = l
   101  	} else {
   102  		rand.Shuffle(n, func(i, j int) { list.Swap(i, j) })
   103  	}
   104  	Select(list.Slice(0, n), n/2)
   105  	return n / 2
   106  }