Skip to content
This repository has been archived by the owner on Nov 18, 2021. It is now read-only.

Commit

Permalink
pkg/list: adding aggregation for decimal lists
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
xinau authored and mpvl committed Sep 9, 2019
1 parent 4017875 commit 41e30c6
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 0 deletions.
20 changes: 20 additions & 0 deletions cue/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,26 @@ func (c *callCtxt) iter(i int) (a Iterator) {
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)
Expand Down
45 changes: 45 additions & 0 deletions cue/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,51 @@ func TestBuiltins(t *testing.T) {
// Find a better alternative, as this call should go.
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)`),
Expand Down
113 changes: 113 additions & 0 deletions cue/builtins.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions cue/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ func (g *generator) goKind(expr ast.Expr) string {
return "bigRat"
case "internal.Decimal":
return "decimal"
case "[]*internal.Decimal":
return "decimalList"
case "cue.Struct":
return "structVal"
case "cue.Value":
Expand Down Expand Up @@ -440,6 +442,9 @@ func (g *generator) goToCUE(expr ast.Expr) (cueKind string, omitCheck bool) {
cueKind += "numKind"
case "list":
cueKind += "listKind"
case "decimalList":
omitCheck = false
cueKind += "listKind"
case "strList":
omitCheck = false
cueKind += "listKind"
Expand Down
3 changes: 3 additions & 0 deletions internal/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ var GetRuntime func(instance interface{}) interface{}
// 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
Expand Down
99 changes: 99 additions & 0 deletions pkg/list/math.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 41e30c6

Please sign in to comment.