Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhanced min and max built-in functions to Support Arrays of Integers and Floats #576

Merged
merged 2 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,38 +394,53 @@ var Builtins = []*Function{
Name: "max",
Func: Max,
Validate: func(args []reflect.Type) (reflect.Type, error) {
if len(args) == 0 {
switch len(args) {
case 0:
return anyType, fmt.Errorf("not enough arguments to call max")
}
for _, arg := range args {
switch kind(arg) {
case reflect.Interface:
case 1:
if kindName := kind(args[0]); kindName == reflect.Array || kindName == reflect.Slice {
return anyType, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
default:
return anyType, fmt.Errorf("invalid argument for max (type %s)", arg)
}
fallthrough
default:
for _, arg := range args {
switch kind(arg) {
case reflect.Interface, reflect.Array, reflect.Slice:
return anyType, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
default:
return anyType, fmt.Errorf("invalid argument for max (type %s)", arg)
}
}
return args[0], nil
}
return args[0], nil
},
},
{
Name: "min",
Func: Min,
Validate: func(args []reflect.Type) (reflect.Type, error) {
if len(args) == 0 {
switch len(args) {
case 0:
return anyType, fmt.Errorf("not enough arguments to call min")
}
for _, arg := range args {
switch kind(arg) {
case reflect.Interface:
case 1:
if kindName := kind(args[0]); kindName == reflect.Array || kindName == reflect.Slice {
return anyType, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
default:
return anyType, fmt.Errorf("invalid argument for min (type %s)", arg)
}
fallthrough
default:
for _, arg := range args {
switch kind(arg) {
case reflect.Interface, reflect.Array, reflect.Slice:
return anyType, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
default:
return anyType, fmt.Errorf("invalid argument for min (type %s)", arg)
}
}
return args[0], nil

}
return args[0], nil
},
},
{
Expand Down
8 changes: 8 additions & 0 deletions builtin/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,14 @@ func TestBuiltin(t *testing.T) {
{`hasSuffix("foo,bar,baz", "baz")`, true},
{`max(1, 2, 3)`, 3},
{`max(1.5, 2.5, 3.5)`, 3.5},
{`max([1, 2, 3])`, 3},
{`max([1.5, 2.5, 3.5])`, 3.5},
{`max([1, 2, 4, 10], 20, [29, 23, -19])`, 29},
{`min([1, 2, 4, 10], 20, [29, 23, -19])`, -19},
{`min(1, 2, 3)`, 1},
{`min(1.5, 2.5, 3.5)`, 1.5},
{`min([1, 2, 3])`, 1},
{`min([1.5, 2.5, 3.5])`, 1.5},
{`sum(1..9)`, 45},
{`sum([.5, 1.5, 2.5])`, 4.5},
{`sum([])`, 0},
Expand Down Expand Up @@ -197,8 +203,10 @@ func TestBuiltin_errors(t *testing.T) {
{`trim()`, `not enough arguments to call trim`},
{`max()`, `not enough arguments to call max`},
{`max(1, "2")`, `invalid argument for max (type string)`},
{`max([1, "2"])`, `invalid argument for max (type string)`},
{`min()`, `not enough arguments to call min`},
{`min(1, "2")`, `invalid argument for min (type string)`},
{`min([1, "2"])`, `invalid argument for min (type string)`},
{`duration("error")`, `invalid duration`},
{`date("error")`, `invalid date`},
{`get()`, `invalid number of arguments (expected 2, got 0)`},
Expand Down
45 changes: 34 additions & 11 deletions builtin/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,21 +255,44 @@ func String(arg any) any {
}

func Max(args ...any) (any, error) {
var max any
for _, arg := range args {
if max == nil || runtime.Less(max, arg) {
max = arg
}
}
return max, nil
return minMaxFunc("max", runtime.Less, args)
}

func Min(args ...any) (any, error) {
var min any
return minMaxFunc("min", runtime.More, args)
}

func minMaxFunc(name string, fn func(any, any) bool, args []any) (any, error) {
var val any
for _, arg := range args {
if min == nil || runtime.More(min, arg) {
min = arg
switch v := arg.(type) {
case []float32, []float64, []uint, []uint8, []uint16, []uint32, []uint64, []int, []int8, []int16, []int32, []int64:
rv := reflect.ValueOf(v)
if rv.Len() == 0 {
return nil, fmt.Errorf("not enough arguments to call %s", name)
}
arg = rv.Index(0).Interface()
for i := 1; i < rv.Len(); i++ {
elem := rv.Index(i).Interface()
if fn(arg, elem) {
arg = elem
}
}
case []any:
var err error
if arg, err = minMaxFunc(name, fn, v); err != nil {
return nil, err
}
case float32, float64, uint, uint8, uint16, uint32, uint64, int, int8, int16, int32, int64:
default:
if len(args) == 1 {
return arg, nil
}
return nil, fmt.Errorf("invalid argument for %s (type %T)", name, v)
}
if val == nil || fn(val, arg) {
val = arg
}
}
return min, nil
return val, nil
}
16 changes: 16 additions & 0 deletions expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,22 @@ func TestExpr(t *testing.T) {
`len({a: 1, b: 2, c: 2})`,
3,
},
{
`max([1, 2, 3])`,
3,
},
{
`max(1, 2, 3)`,
3,
},
{
`min([1, 2, 3])`,
1,
},
{
`min(1, 2, 3)`,
1,
},
{
`{foo: 0, bar: 1}`,
map[string]any{"foo": 0, "bar": 1},
Expand Down
2 changes: 1 addition & 1 deletion testdata/examples.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13822,7 +13822,7 @@ min(ok ? array : false)
min(ok ? f64 : i)
min(ok ? half : ok)
min(ok ? i : f32)
min(ok ? list : score)
min(ok ? array : score)
min(ok ? true : div)
min(reduce(array, #))
min(reduce(array, 0.5))
Expand Down
Loading