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

Enable Support for Arrays in Sum, Mean, and Median Functions #580

Merged
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
201 changes: 29 additions & 172 deletions builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,42 +135,21 @@ var Builtins = []*Function{
Name: "ceil",
Fast: Ceil,
Validate: func(args []reflect.Type) (reflect.Type, error) {
if len(args) != 1 {
return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
}
switch kind(args[0]) {
case reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Interface:
return floatType, nil
}
return anyType, fmt.Errorf("invalid argument for ceil (type %s)", args[0])
return validateRoundFunc("ceil", args)
},
},
{
Name: "floor",
Fast: Floor,
Validate: func(args []reflect.Type) (reflect.Type, error) {
if len(args) != 1 {
return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
}
switch kind(args[0]) {
case reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Interface:
return floatType, nil
}
return anyType, fmt.Errorf("invalid argument for floor (type %s)", args[0])
return validateRoundFunc("floor", args)
},
},
{
Name: "round",
Fast: Round,
Validate: func(args []reflect.Type) (reflect.Type, error) {
if len(args) != 1 {
return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
}
switch kind(args[0]) {
case reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Interface:
return floatType, nil
}
return anyType, fmt.Errorf("invalid argument for floor (type %s)", args[0])
return validateRoundFunc("round", args)
},
},
{
Expand Down Expand Up @@ -392,185 +371,63 @@ var Builtins = []*Function{
},
{
Name: "max",
Func: Max,
Func: func(args ...any) (any, error) {
return minMax("max", runtime.Less, args...)
},
Validate: func(args []reflect.Type) (reflect.Type, error) {
switch len(args) {
case 0:
return anyType, fmt.Errorf("not enough arguments to call max")
case 1:
if kindName := kind(args[0]); kindName == reflect.Array || kindName == reflect.Slice {
return anyType, nil
}
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 validateAggregateFunc("max", args)
},
},
{
Name: "min",
Func: Min,
Func: func(args ...any) (any, error) {
return minMax("min", runtime.More, args...)
},
Validate: func(args []reflect.Type) (reflect.Type, error) {
switch len(args) {
case 0:
return anyType, fmt.Errorf("not enough arguments to call min")
case 1:
if kindName := kind(args[0]); kindName == reflect.Array || kindName == reflect.Slice {
return anyType, nil
}
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 validateAggregateFunc("min", args)
},
},
{
Name: "sum",
Func: func(args ...any) (any, error) {
if len(args) != 1 {
return nil, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
}
v := reflect.ValueOf(args[0])
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
return nil, fmt.Errorf("cannot sum %s", v.Kind())
}
sum := int64(0)
i := 0
for ; i < v.Len(); i++ {
it := deref.Value(v.Index(i))
if it.CanInt() {
sum += it.Int()
} else if it.CanFloat() {
goto float
} else {
return nil, fmt.Errorf("cannot sum %s", it.Kind())
}
}
return int(sum), nil
float:
fSum := float64(sum)
for ; i < v.Len(); i++ {
it := deref.Value(v.Index(i))
if it.CanInt() {
fSum += float64(it.Int())
} else if it.CanFloat() {
fSum += it.Float()
} else {
return nil, fmt.Errorf("cannot sum %s", it.Kind())
}
}
return fSum, nil
},
Func: sum,
Validate: func(args []reflect.Type) (reflect.Type, error) {
if len(args) != 1 {
return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
}
switch kind(args[0]) {
case reflect.Interface, reflect.Slice, reflect.Array:
default:
return anyType, fmt.Errorf("cannot sum %s", args[0])
}
return anyType, nil
return validateAggregateFunc("sum", args)
},
},
{
Name: "mean",
Func: func(args ...any) (any, error) {
if len(args) != 1 {
return nil, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
}
v := reflect.ValueOf(args[0])
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
return nil, fmt.Errorf("cannot mean %s", v.Kind())
count, sum, err := mean(args...)
if err != nil {
return nil, err
}
if v.Len() == 0 {
if count == 0 {
return 0.0, nil
}
sum := float64(0)
i := 0
for ; i < v.Len(); i++ {
it := deref.Value(v.Index(i))
if it.CanInt() {
sum += float64(it.Int())
} else if it.CanFloat() {
sum += it.Float()
} else {
return nil, fmt.Errorf("cannot mean %s", it.Kind())
}
}
return sum / float64(i), nil
return sum / float64(count), nil
},
Validate: func(args []reflect.Type) (reflect.Type, error) {
if len(args) != 1 {
return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
}
switch kind(args[0]) {
case reflect.Interface, reflect.Slice, reflect.Array:
default:
return anyType, fmt.Errorf("cannot avg %s", args[0])
}
return floatType, nil
return validateAggregateFunc("mean", args)
},
},
{
Name: "median",
Func: func(args ...any) (any, error) {
if len(args) != 1 {
return nil, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
}
v := reflect.ValueOf(args[0])
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
return nil, fmt.Errorf("cannot median %s", v.Kind())
}
if v.Len() == 0 {
return 0.0, nil
values, err := median(args...)
if err != nil {
return nil, err
}
s := make([]float64, v.Len())
for i := 0; i < v.Len(); i++ {
it := deref.Value(v.Index(i))
if it.CanInt() {
s[i] = float64(it.Int())
} else if it.CanFloat() {
s[i] = it.Float()
} else {
return nil, fmt.Errorf("cannot median %s", it.Kind())
if n := len(values); n > 0 {
sort.Float64s(values)
if n%2 == 1 {
return values[n/2], nil
}
return (values[n/2-1] + values[n/2]) / 2, nil
}
sort.Float64s(s)
if len(s)%2 == 0 {
return (s[len(s)/2-1] + s[len(s)/2]) / 2, nil
}
return s[len(s)/2], nil
return 0.0, nil
},
Validate: func(args []reflect.Type) (reflect.Type, error) {
if len(args) != 1 {
return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
}
switch kind(args[0]) {
case reflect.Interface, reflect.Slice, reflect.Array:
default:
return anyType, fmt.Errorf("cannot median %s", args[0])
}
return floatType, nil
return validateAggregateFunc("median", args)
},
},
{
Expand Down
13 changes: 13 additions & 0 deletions builtin/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,29 @@ func TestBuiltin(t *testing.T) {
{`min(1.5, 2.5, 3.5)`, 1.5},
{`min([1, 2, 3])`, 1},
{`min([1.5, 2.5, 3.5])`, 1.5},
{`min(-1, [1.5, 2.5, 3.5])`, -1},
{`sum(1..9)`, 45},
{`sum([.5, 1.5, 2.5])`, 4.5},
{`sum([])`, 0},
{`sum([1, 2, 3.0, 4])`, 10.0},
{`sum(10, [1, 2, 3], 1..9)`, 61},
{`sum(-10, [1, 2, 3, 4])`, 0},
{`sum(-10.9, [1, 2, 3, 4, 9])`, 8.1},
{`mean(1..9)`, 5.0},
{`mean([.5, 1.5, 2.5])`, 1.5},
{`mean([])`, 0.0},
{`mean([1, 2, 3.0, 4])`, 2.5},
{`mean(10, [1, 2, 3], 1..9)`, 4.6923076923076925},
{`mean(-10, [1, 2, 3, 4])`, 0.0},
{`mean(10.9, 1..9)`, 5.59},
{`median(1..9)`, 5.0},
{`median([.5, 1.5, 2.5])`, 1.5},
{`median([])`, 0.0},
{`median([1, 2, 3])`, 2.0},
{`median([1, 2, 3, 4])`, 2.5},
{`median(10, [1, 2, 3], 1..9)`, 4.0},
{`median(-10, [1, 2, 3, 4])`, 2.0},
{`median(1..5, 4.9)`, 3.5},
{`toJSON({foo: 1, bar: 2})`, "{\n \"bar\": 2,\n \"foo\": 1\n}"},
{`fromJSON("[1, 2, 3]")`, []any{1.0, 2.0, 3.0}},
{`toBase64("hello")`, "aGVsbG8="},
Expand Down Expand Up @@ -207,6 +217,9 @@ func TestBuiltin_errors(t *testing.T) {
{`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)`},
{`median(1..9, "t")`, "invalid argument for median (type string)"},
{`mean("s", 1..9)`, "invalid argument for mean (type string)"},
{`sum("s", "h")`, "invalid argument for sum (type string)"},
{`duration("error")`, `invalid duration`},
{`date("error")`, `invalid date`},
{`get()`, `invalid number of arguments (expected 2, got 0)`},
Expand Down
Loading
Loading