github.com/jgbaldwinbrown/perf@v0.1.1/benchproc/sort.go (about)

     1  // Copyright 2022 The Go 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 benchproc
     6  
     7  import (
     8  	"math"
     9  	"regexp"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  )
    14  
    15  // Less reports whether k comes before o in the sort order implied by
    16  // their projection. It panics if k and o have different Projections.
    17  func (k Key) Less(o Key) bool {
    18  	if k.k.proj != o.k.proj {
    19  		panic("cannot compare Keys from different Projections")
    20  	}
    21  	return less(k.k.proj.FlattenedFields(), k.k.vals, o.k.vals)
    22  }
    23  
    24  func less(flat []*Field, a, b []string) bool {
    25  	// Walk the tuples in flattened order.
    26  	for _, node := range flat {
    27  		var aa, bb string
    28  		if node.idx < len(a) {
    29  			aa = a[node.idx]
    30  		}
    31  		if node.idx < len(b) {
    32  			bb = b[node.idx]
    33  		}
    34  		if aa != bb {
    35  			cmp := node.cmp(aa, bb)
    36  			if cmp != 0 {
    37  				return cmp < 0
    38  			}
    39  			// The values are equal/unordered according to
    40  			// the comparison function, but the strings
    41  			// differ. Because Keys are only == if
    42  			// their string representations are ==, this
    43  			// means we have to fall back to a secondary
    44  			// comparison that is only == if the strings
    45  			// are ==.
    46  			return aa < bb
    47  		}
    48  	}
    49  
    50  	// Tuples are equal.
    51  	return false
    52  }
    53  
    54  // SortKeys sorts a slice of Keys using Key.Less.
    55  // All Keys must have the same Projection.
    56  //
    57  // This is equivalent to using Key.Less with the sort package but
    58  // more efficient.
    59  func SortKeys(keys []Key) {
    60  	// Check all the Projections so we don't have to do this on every
    61  	// comparison.
    62  	if len(keys) == 0 {
    63  		return
    64  	}
    65  	s := commonProjection(keys)
    66  	flat := s.FlattenedFields()
    67  
    68  	sort.Slice(keys, func(i, j int) bool {
    69  		return less(flat, keys[i].k.vals, keys[j].k.vals)
    70  	})
    71  }
    72  
    73  // builtinOrders is the built-in comparison functions.
    74  var builtinOrders = map[string]func(a, b string) int{
    75  	"alpha": func(a, b string) int {
    76  		return strings.Compare(a, b)
    77  	},
    78  	"num": func(a, b string) int {
    79  		aa, erra := parseNum(a)
    80  		bb, errb := parseNum(b)
    81  		if erra == nil && errb == nil {
    82  			// Sort numerically, and put NaNs after other
    83  			// values.
    84  			if aa < bb || (!math.IsNaN(aa) && math.IsNaN(bb)) {
    85  				return -1
    86  			}
    87  			if aa > bb || (math.IsNaN(aa) && !math.IsNaN(bb)) {
    88  				return 1
    89  			}
    90  			// The values are unordered.
    91  			return 0
    92  		}
    93  		if erra != nil && errb != nil {
    94  			// The values are unordered.
    95  			return 0
    96  		}
    97  		// Put floats before non-floats.
    98  		if erra == nil {
    99  			return -1
   100  		}
   101  		return 1
   102  	},
   103  }
   104  
   105  const numPrefixes = `KMGTPEZY`
   106  
   107  var numRe = regexp.MustCompile(`([0-9.]+)([k` + numPrefixes + `]i?)?[bB]?`)
   108  
   109  // parseNum is a fuzzy number parser. It supports common patterns,
   110  // such as SI prefixes.
   111  func parseNum(x string) (float64, error) {
   112  	// Try parsing as a regular float.
   113  	v, err := strconv.ParseFloat(x, 64)
   114  	if err == nil {
   115  		return v, nil
   116  	}
   117  
   118  	// Try a suffixed number.
   119  	subs := numRe.FindStringSubmatch(x)
   120  	if subs != nil {
   121  		v, err := strconv.ParseFloat(subs[1], 64)
   122  		if err == nil {
   123  			exp := 0
   124  			if len(subs[2]) > 0 {
   125  				pre := subs[2][0]
   126  				if pre == 'k' {
   127  					pre = 'K'
   128  				}
   129  				exp = 1 + strings.IndexByte(numPrefixes, pre)
   130  			}
   131  			iec := strings.HasSuffix(subs[2], "i")
   132  			if iec {
   133  				return v * math.Pow(1024, float64(exp)), nil
   134  			}
   135  			return v * math.Pow(1000, float64(exp)), nil
   136  		}
   137  	}
   138  
   139  	return 0, strconv.ErrSyntax
   140  }