<commit_before>package expr

import (
	"math"
	"sort"

	"github.com/grafana/metrictank/consolidation"
	schema "gopkg.in/raintank/schema.v1"

	"github.com/grafana/metrictank/api/models"
)

type FuncHighestLowest struct {
	in      GraphiteFunc
	n       int64
	fn      string
	highest bool
}

func NewHighestLowestConstructor(fn string, highest bool) func() GraphiteFunc {
	return func() GraphiteFunc {
		return &FuncHighestLowest{fn: fn, highest: highest}
	}
}

func (s *FuncHighestLowest) Signature() ([]Arg, []Arg) {
	if s.fn != "" {
		return []Arg{
			ArgSeriesList{val: &s.in},
			ArgInt{key: "n", val: &s.n},
		}, []Arg{ArgSeriesList{}}
	}
	return []Arg{
		ArgSeriesList{val: &s.in},
		ArgInt{key: "n", val: &s.n},
		ArgString{key: "func", val: &s.fn, validator: []Validator{IsConsolFunc}},
	}, []Arg{ArgSeriesList{}}
}

func (s *FuncHighestLowest) Context(context Context) Context {
	return context
}

func (s *FuncHighestLowest) Exec(cache map[Req][]models.Series) ([]models.Series, error) {
	series, err := s.in.Exec(cache)
	if err != nil {
		return nil, err
	}

	if len(series) == 0 {
		return series, nil
	}

	consolidationFunc := consolidation.GetAggFunc(consolidation.FromConsolidateBy(s.fn))

	// Calculates the consolidated value for each series and stores it in a map
	// where the key is a pointer to the first datapoint
	consolidationVals := make(map[*schema.Point]float64, len(series))
	for _, serie := range series {
		consolidationVals[&serie.Datapoints[0]] = consolidationFunc(serie.Datapoints)
	}

	// Compares two series based on value in consolidationVals
	seriesLess := func(i, j int) bool {
		iVal := consolidationVals[&series[i].Datapoints[0]]
		jVal := consolidationVals[&series[j].Datapoints[0]]
		if s.highest {
			return math.IsNaN(jVal) && !math.IsNaN(iVal) || iVal > jVal
		}
		return math.IsNaN(jVal) && !math.IsNaN(iVal) || iVal < jVal
	}
	sort.SliceStable(series, seriesLess)

	if s.n > int64(len(series)) {
		s.n = int64(len(series))
	}

	return series[:s.n], nil
}<commit_msg>perform the sorting in a more standard fashion<commit_after>package expr

import (
	"math"
	"sort"

	"github.com/grafana/metrictank/consolidation"

	"github.com/grafana/metrictank/api/models"
)

type FuncHighestLowest struct {
	in      GraphiteFunc
	n       int64
	fn      string
	highest bool
}

func NewHighestLowestConstructor(fn string, highest bool) func() GraphiteFunc {
	return func() GraphiteFunc {
		return &FuncHighestLowest{fn: fn, highest: highest}
	}
}

func (s *FuncHighestLowest) Signature() ([]Arg, []Arg) {
	if s.fn != "" {
		return []Arg{
			ArgSeriesList{val: &s.in},
			ArgInt{key: "n", val: &s.n},
		}, []Arg{ArgSeriesList{}}
	}
	return []Arg{
		ArgSeriesList{val: &s.in},
		ArgInt{key: "n", val: &s.n},
		ArgString{key: "func", val: &s.fn, validator: []Validator{IsConsolFunc}},
	}, []Arg{ArgSeriesList{}}
}

func (s *FuncHighestLowest) Context(context Context) Context {
	return context
}

type ScoredSeries struct {
	score float64
	serie models.Series
}

func (s *FuncHighestLowest) Exec(cache map[Req][]models.Series) ([]models.Series, error) {
	series, err := s.in.Exec(cache)
	if err != nil {
		return nil, err
	}

	if len(series) == 0 {
		return series, nil
	}

	consolidationFunc := consolidation.GetAggFunc(consolidation.FromConsolidateBy(s.fn))

	// score series by their consolidated value
	scored := make([]ScoredSeries, len(series))
	for i, serie := range series {
		scored[i] = ScoredSeries{
			score: consolidationFunc(serie.Datapoints),
			serie: serie,
		}
	}

	sort.SliceStable(scored, func(i, j int) bool {
		iVal := scored[i].score
		jVal := scored[j].score
		if s.highest {
			return math.IsNaN(jVal) && !math.IsNaN(iVal) || iVal > jVal
		}
		return math.IsNaN(jVal) && !math.IsNaN(iVal) || iVal < jVal
	})

	if s.n > int64(len(series)) {
		s.n = int64(len(series))
	}

	for i := 0; i < int(s.n); i++ {
		series[i] = scored[i].serie
	}

	return series[:s.n], nil
}
{'fraction_non_alphanumeric': 0.15288412455334355, 'fraction_numerical': 0.00587034201123022, 'mean_word_length': 4.211436170212766, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7456123', 'n_tokens_mistral': 1596, 'n_tokens_neox': 1489, 'n_words': 338}