pkg/list: adding aggregation for decimal lists
this commit adds the aggregation functions avg, max, min, product, sum
on lists containing only numbers. it also adds a new type to builtin.go
and gen.go to have a direct transformation of cue.Value to
[]*internal.Decimal
Issue #78
Change-Id: I94640726b93f8bb23f43cf85330e2e9056cbcf70
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3103
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin.go b/cue/builtin.go
index d6716eb..d7fea05 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -574,6 +574,26 @@
return v
}
+func (c *callCtxt) decimalList(i int) (a []*apd.Decimal) {
+ arg := c.args[i]
+ x := newValueRoot(c.ctx, arg)
+ v, err := x.List()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "list", err)
+ return nil
+ }
+ for j := 0; v.Next(); j++ {
+ num, err := v.Value().getNum(numKind)
+ if err != nil {
+ c.errf(c.src, err, "invalid list element %d in argument %d to %s: %v",
+ j, i, c.name(), err)
+ break
+ }
+ a = append(a, &num.v)
+ }
+ return a
+}
+
func (c *callCtxt) strList(i int) (a []string) {
arg := c.args[i]
x := newValueRoot(c.ctx, arg)
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index 73d3fd7..6e63183 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -135,6 +135,51 @@
test("strconv", `strconv.FormatFloat(3.02, 1.0, 4, 64)`),
`_|_(cannot use 1.0 (type float) as int in argument 2 to strconv.FormatFloat)`,
}, {
+ test("list", `list.Avg([1, 2, 3, 4])`),
+ `2.5`,
+ }, {
+ test("list", `list.Avg([])`),
+ `_|_(error in call to list.Avg: empty list)`,
+ }, {
+ test("list", `list.Avg("foo")`),
+ `_|_(cannot use "foo" (type string) as list in argument 1 to list.Avg)`,
+ }, {
+ test("list", `list.Max([1, 2, 3, 4])`),
+ `4`,
+ }, {
+ test("list", `list.Max([])`),
+ `_|_(error in call to list.Max: empty list)`,
+ }, {
+ test("list", `list.Max("foo")`),
+ `_|_(cannot use "foo" (type string) as list in argument 1 to list.Max)`,
+ }, {
+ test("list", `list.Min([1, 2, 3, 4])`),
+ `1`,
+ }, {
+ test("list", `list.Min([])`),
+ `_|_(error in call to list.Min: empty list)`,
+ }, {
+ test("list", `list.Min("foo")`),
+ `_|_(cannot use "foo" (type string) as list in argument 1 to list.Min)`,
+ }, {
+ test("list", `list.Product([1, 2, 3, 4])`),
+ `24`,
+ }, {
+ test("list", `list.Product([])`),
+ `1`,
+ }, {
+ test("list", `list.Product("foo")`),
+ `_|_(cannot use "foo" (type string) as list in argument 1 to list.Product)`,
+ }, {
+ test("list", `list.Sum([1, 2, 3, 4])`),
+ `10`,
+ }, {
+ test("list", `list.Sum([])`),
+ `0`,
+ }, {
+ test("list", `list.Sum("foo")`),
+ `_|_(cannot use "foo" (type string) as list in argument 1 to list.Sum)`,
+ }, {
// Panics
test("math", `math.Jacobi(1000, 2000)`),
`_|_(error in call to math.Jacobi: big: invalid 2nd argument to Int.Jacobi: need odd integer but got 2000)`,
diff --git a/cue/builtins.go b/cue/builtins.go
index 2e6ef2a..efc4ffd 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -717,6 +717,118 @@
return false
}()
},
+ }, {
+ Name: "Avg",
+ Params: []kind{listKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ xs := c.decimalList(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if 0 == len(xs) {
+ return nil, fmt.Errorf("empty list")
+ }
+
+ s := apd.New(0, 0)
+ for _, x := range xs {
+ _, err := internal.BaseContext.Add(s, x, s)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ var d apd.Decimal
+ l := apd.New(int64(len(xs)), 0)
+ _, err := internal.BaseContext.Quo(&d, s, l)
+ if err != nil {
+ return nil, err
+ }
+ return &d, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Max",
+ Params: []kind{listKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ xs := c.decimalList(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if 0 == len(xs) {
+ return nil, fmt.Errorf("empty list")
+ }
+
+ max := xs[0]
+ for _, x := range xs[1:] {
+ if -1 == max.Cmp(x) {
+ max = x
+ }
+ }
+ return max, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Min",
+ Params: []kind{listKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ xs := c.decimalList(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if 0 == len(xs) {
+ return nil, fmt.Errorf("empty list")
+ }
+
+ min := xs[0]
+ for _, x := range xs[1:] {
+ if +1 == min.Cmp(x) {
+ min = x
+ }
+ }
+ return min, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Product",
+ Params: []kind{listKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ xs := c.decimalList(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ d := apd.New(1, 0)
+ for _, x := range xs {
+ _, err := internal.BaseContext.Mul(d, x, d)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return d, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Sum",
+ Params: []kind{listKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ xs := c.decimalList(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ d := apd.New(0, 0)
+ for _, x := range xs {
+ _, err := internal.BaseContext.Add(d, x, d)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return d, nil
+ }()
+ }
+ },
}},
},
"math": &builtinPkg{
@@ -2027,6 +2139,7 @@
Func: func(c *callCtxt) {
s, min := c.string(0), c.int(1)
c.ret = func() interface{} {
+
return len([]rune(s)) >= min
}()
},
diff --git a/cue/gen.go b/cue/gen.go
index cd30512..7f607eb 100644
--- a/cue/gen.go
+++ b/cue/gen.go
@@ -399,6 +399,8 @@
return "bigRat"
case "internal.Decimal":
return "decimal"
+ case "[]*internal.Decimal":
+ return "decimalList"
case "cue.Struct":
return "structVal"
case "cue.Value":
@@ -440,6 +442,9 @@
cueKind += "numKind"
case "list":
cueKind += "listKind"
+ case "decimalList":
+ omitCheck = false
+ cueKind += "listKind"
case "strList":
omitCheck = false
cueKind += "listKind"
diff --git a/internal/internal.go b/internal/internal.go
index a7cdc42..f4e0ab7 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -69,6 +69,9 @@
// keys.
var CheckAndForkRuntime func(runtime, value interface{}) interface{}
+// BaseContext is used as CUEs default context for arbitrary-precision decimals
+var BaseContext = apd.BaseContext.WithPrecision(24)
+
// ListEllipsis reports the list type and remaining elements of a list. If we
// ever relax the usage of ellipsis, this function will likely change. Using
// this function will ensure keeping correct behavior or causing a compiler
diff --git a/pkg/list/math.go b/pkg/list/math.go
new file mode 100644
index 0000000..4ce4ba3
--- /dev/null
+++ b/pkg/list/math.go
@@ -0,0 +1,99 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package list
+
+import (
+ "fmt"
+
+ "cuelang.org/go/internal"
+ "github.com/cockroachdb/apd/v2"
+)
+
+// Avg returns the average value of a non empty list xs.
+func Avg(xs []*internal.Decimal) (*internal.Decimal, error) {
+ if 0 == len(xs) {
+ return nil, fmt.Errorf("empty list")
+ }
+
+ s := apd.New(0, 0)
+ for _, x := range xs {
+ _, err := internal.BaseContext.Add(s, x, s)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ var d apd.Decimal
+ l := apd.New(int64(len(xs)), 0)
+ _, err := internal.BaseContext.Quo(&d, s, l)
+ if err != nil {
+ return nil, err
+ }
+ return &d, nil
+}
+
+// Max returns the maximum value of a non empty list xs.
+func Max(xs []*internal.Decimal) (*internal.Decimal, error) {
+ if 0 == len(xs) {
+ return nil, fmt.Errorf("empty list")
+ }
+
+ max := xs[0]
+ for _, x := range xs[1:] {
+ if -1 == max.Cmp(x) {
+ max = x
+ }
+ }
+ return max, nil
+}
+
+// Min returns the minimum value of a non empty list xs.
+func Min(xs []*internal.Decimal) (*internal.Decimal, error) {
+ if 0 == len(xs) {
+ return nil, fmt.Errorf("empty list")
+ }
+
+ min := xs[0]
+ for _, x := range xs[1:] {
+ if +1 == min.Cmp(x) {
+ min = x
+ }
+ }
+ return min, nil
+}
+
+// Product returns the product of a non empty list xs.
+func Product(xs []*internal.Decimal) (*internal.Decimal, error) {
+ d := apd.New(1, 0)
+ for _, x := range xs {
+ _, err := internal.BaseContext.Mul(d, x, d)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return d, nil
+}
+
+// Sum returns the sum of a list non empty xs.
+func Sum(xs []*internal.Decimal) (*internal.Decimal, error) {
+ d := apd.New(0, 0)
+ for _, x := range xs {
+ _, err := internal.BaseContext.Add(d, x, d)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return d, nil
+}