Skip to content

Commit

Permalink
Enable Support for Arrays in Sum, Mean, and Median Functions (#580)
Browse files Browse the repository at this point in the history
  • Loading branch information
ckganesan authored and antonmedv committed Apr 13, 2024
1 parent 355fb28 commit ded019d
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 200 deletions.
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

0 comments on commit ded019d

Please sign in to comment.