min/max operators (#1992)

* min/max operators

* min, max operator headers
This commit is contained in:
Matt Benson 2024-03-29 21:34:36 -05:00 committed by GitHub
parent 3283c65dc4
commit 101cf14b8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 209 additions and 0 deletions

View File

@ -0,0 +1,3 @@
# Max
Computes the maximum among an incoming sequence of scalar values.

View File

@ -0,0 +1,3 @@
# Min
Computes the minimum among an incoming sequence of scalar values.

View File

@ -0,0 +1,48 @@
## Maximum int
Given a sample.yml file of:
```yaml
- 99
- 16
- 12
- 6
- 66
```
then
```bash
yq 'max' sample.yml
```
will output
```yaml
99
```
## Maximum string
Given a sample.yml file of:
```yaml
- foo
- bar
- baz
```
then
```bash
yq 'max' sample.yml
```
will output
```yaml
foo
```
## Maximum of empty
Given a sample.yml file of:
```yaml
[]
```
then
```bash
yq 'max' sample.yml
```
will output
```yaml
```

View File

@ -0,0 +1,48 @@
## Minimum int
Given a sample.yml file of:
```yaml
- 99
- 16
- 12
- 6
- 66
```
then
```bash
yq 'min' sample.yml
```
will output
```yaml
6
```
## Minimum string
Given a sample.yml file of:
```yaml
- foo
- bar
- baz
```
then
```bash
yq 'min' sample.yml
```
will output
```yaml
bar
```
## Minimum of empty
Given a sample.yml file of:
```yaml
[]
```
then
```bash
yq 'min' sample.yml
```
will output
```yaml
```

View File

@ -199,6 +199,9 @@ var participleYqRules = []*participleYqRule{
{"GreaterThan", `\s*>\s*`, opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: false, Greater: true}), 0},
{"LessThan", `\s*<\s*`, opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: false, Greater: false}), 0},
simpleOp("min", minOpType),
simpleOp("max", maxOpType),
{"AssignRelative", `\|=[c]*`, assignOpToken(true), 0},
{"Assign", `=[c]*`, assignOpToken(false), 0},

View File

@ -73,6 +73,8 @@ var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Ha
var notEqualsOpType = &operationType{Type: "NOT_EQUALS", NumArgs: 2, Precedence: 40, Handler: notEqualsOperator}
var compareOpType = &operationType{Type: "COMPARE", NumArgs: 2, Precedence: 40, Handler: compareOperator}
var minOpType = &operationType{Type: "MIN", NumArgs: 0, Precedence: 40, Handler: minOperator}
var maxOpType = &operationType{Type: "MAX", NumArgs: 0, Precedence: 40, Handler: maxOperator}
// createmap needs to be above union, as we use union to build the components of the objects
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 15, Handler: createMapOperator}

View File

@ -1,6 +1,7 @@
package yqlib
import (
"container/list"
"fmt"
"strconv"
)
@ -129,3 +130,40 @@ func compareScalars(context Context, prefs compareTypePref, lhs *CandidateNode,
return false, fmt.Errorf("%v not yet supported for comparison", lhs.Tag)
}
func superlativeByComparison(d *dataTreeNavigator, context Context, prefs compareTypePref) (Context, error) {
fn := compare(prefs)
var results = list.New()
for seq := context.MatchingNodes.Front(); seq != nil; seq = seq.Next() {
splatted, err := splat(context.SingleChildContext(seq.Value.(*CandidateNode)), traversePreferences{})
if err != nil {
return Context{}, err
}
result := splatted.MatchingNodes.Front()
if result != nil {
for el := result.Next(); el != nil; el = el.Next() {
cmp, err := fn(d, context, el.Value.(*CandidateNode), result.Value.(*CandidateNode))
if err != nil {
return Context{}, err
}
if isTruthyNode(cmp) {
result = el
}
}
results.PushBack(result.Value)
}
}
return context.ChildContext(results), nil
}
func minOperator(d *dataTreeNavigator, context Context, _ *ExpressionNode) (Context, error) {
log.Debug(("Min"))
return superlativeByComparison(d, context, compareTypePref{Greater: false})
}
func maxOperator(d *dataTreeNavigator, context Context, _ *ExpressionNode) (Context, error) {
log.Debug(("Max"))
return superlativeByComparison(d, context, compareTypePref{Greater: true})
}

View File

@ -383,3 +383,67 @@ func TestCompareOperatorScenarios(t *testing.T) {
}
documentOperatorScenarios(t, "compare", compareOperatorScenarios)
}
var minOperatorScenarios = []expressionScenario{
{
description: "Minimum int",
document: "[99, 16, 12, 6, 66]\n",
expression: `min`,
expected: []string{
"D0, P[3], (!!int)::6\n",
},
},
{
description: "Minimum string",
document: "[foo, bar, baz]\n",
expression: `min`,
expected: []string{
"D0, P[1], (!!str)::bar\n",
},
},
{
description: "Minimum of empty",
document: "[]\n",
expression: `min`,
expected: []string{},
},
}
func TestMinOperatorScenarios(t *testing.T) {
for _, tt := range minOperatorScenarios {
testScenario(t, &tt)
}
documentOperatorScenarios(t, "min", minOperatorScenarios)
}
var maxOperatorScenarios = []expressionScenario{
{
description: "Maximum int",
document: "[99, 16, 12, 6, 66]\n",
expression: `max`,
expected: []string{
"D0, P[0], (!!int)::99\n",
},
},
{
description: "Maximum string",
document: "[foo, bar, baz]\n",
expression: `max`,
expected: []string{
"D0, P[0], (!!str)::foo\n",
},
},
{
description: "Maximum of empty",
document: "[]\n",
expression: `max`,
expected: []string{},
},
}
func TestMaxOperatorScenarios(t *testing.T) {
for _, tt := range maxOperatorScenarios {
testScenario(t, &tt)
}
documentOperatorScenarios(t, "max", maxOperatorScenarios)
}