diff --git a/pkg/obiseq/language.go b/pkg/obiseq/language.go index 9f0297b..4738274 100644 --- a/pkg/obiseq/language.go +++ b/pkg/obiseq/language.go @@ -141,6 +141,20 @@ var OBILang = gval.NewLanguage( gval.Function("max", func(args ...interface{}) (interface{}, error) { return obiutils.Max(args[0]) }), + gval.Function("which_max", func(args ...interface{}) (interface{}, error) { + result, err := obiutils.WhichMax(args[0]) + if idx, ok := result.(int); ok { + return float64(idx), nil + } + return result, err + }), + gval.Function("which_min", func(args ...interface{}) (interface{}, error) { + result, err := obiutils.WhichMin(args[0]) + if idx, ok := result.(int); ok { + return float64(idx), nil + } + return result, err + }), gval.Function("filtermin", func(args ...interface{}) (interface{}, error) { return obiutils.FilterMin(args[0], args[1]) diff --git a/pkg/obiutils/minmax.go b/pkg/obiutils/minmax.go index 66ef179..08308f1 100644 --- a/pkg/obiutils/minmax.go +++ b/pkg/obiutils/minmax.go @@ -365,6 +365,116 @@ func filterMaxFromIterable(v reflect.Value, maximum interface{}) (interface{}, e return result.Interface(), nil } +// whichMaxFromIterable returns the index of the maximum element in a slice/array. +func whichMaxFromIterable(v reflect.Value) (int, error) { + var best reflect.Value + bestIdx := 0 + for i := 0; i < v.Len(); i++ { + elem := unwrapInterface(v.Index(i)) + if !isOrderedKind(elem.Kind()) { + return 0, fmt.Errorf("unsupported element type: %s", elem.Kind()) + } + if i == 0 || greater(elem, best) { + best = elem + bestIdx = i + } + } + return bestIdx, nil +} + +// whichMinFromIterable returns the index of the minimum element in a slice/array. +func whichMinFromIterable(v reflect.Value) (int, error) { + var minVal reflect.Value + minIdx := 0 + for i := 0; i < v.Len(); i++ { + elem := unwrapInterface(v.Index(i)) + if !isOrderedKind(elem.Kind()) { + return 0, fmt.Errorf("unsupported element type: %s", elem.Kind()) + } + if i == 0 || less(elem, minVal) { + minVal = elem + minIdx = i + } + } + return minIdx, nil +} + +// whichMaxFromMap returns the key associated with the maximum value in a map. +func whichMaxFromMap(v reflect.Value) (interface{}, error) { + var best reflect.Value + var bestKey reflect.Value + first := true + for _, key := range v.MapKeys() { + elem := unwrapInterface(v.MapIndex(key)) + if !isOrderedKind(elem.Kind()) { + return nil, fmt.Errorf("unsupported element type: %s", elem.Kind()) + } + if first || greater(elem, best) { + best = elem + bestKey = key + first = false + } + } + return bestKey.Interface(), nil +} + +// whichMinFromMap returns the key associated with the minimum value in a map. +func whichMinFromMap(v reflect.Value) (interface{}, error) { + var minVal reflect.Value + var minKey reflect.Value + first := true + for _, key := range v.MapKeys() { + elem := unwrapInterface(v.MapIndex(key)) + if !isOrderedKind(elem.Kind()) { + return nil, fmt.Errorf("unsupported element type: %s", elem.Kind()) + } + if first || less(elem, minVal) { + minVal = elem + minKey = key + first = false + } + } + return minKey.Interface(), nil +} + +// WhichMax returns the key (for a map) or index (for a slice/array) of the maximum value. +func WhichMax(data interface{}) (interface{}, error) { + v := reflect.ValueOf(data) + switch v.Kind() { + case reflect.Slice, reflect.Array: + if v.Len() == 0 { + return nil, errors.New("empty slice or array") + } + return whichMaxFromIterable(v) + case reflect.Map: + if v.Len() == 0 { + return nil, errors.New("empty map") + } + return whichMaxFromMap(v) + default: + return nil, fmt.Errorf("unsupported type: %s", v.Kind()) + } +} + +// WhichMin returns the key (for a map) or index (for a slice/array) of the minimum value. +func WhichMin(data interface{}) (interface{}, error) { + v := reflect.ValueOf(data) + switch v.Kind() { + case reflect.Slice, reflect.Array: + if v.Len() == 0 { + return nil, errors.New("empty slice or array") + } + return whichMinFromIterable(v) + case reflect.Map: + if v.Len() == 0 { + return nil, errors.New("empty map") + } + return whichMinFromMap(v) + default: + return nil, fmt.Errorf("unsupported type: %s", v.Kind()) + } +} + func filterMinFromMap(v reflect.Value, minimum interface{}) (interface{}, error) { minVal := reflect.ValueOf(minimum) result := reflect.MakeMap(v.Type())