Add expr function for sorting an array

It allows ascending/descending sorting along with sorting of native and non-native types
This commit is contained in:
Vivek Patel
2022-01-18 21:50:18 +01:00
committed by Denis Arh
parent 937ec1d584
commit f893e8834a
2 changed files with 298 additions and 0 deletions
+76
View File
@@ -3,6 +3,7 @@ package expr
import (
"fmt"
"reflect"
"sort"
"strings"
"github.com/PaesslerAG/gval"
@@ -17,6 +18,7 @@ func ArrayFunctions() []gval.Language {
gval.Function("has", has),
gval.Function("hasAll", hasAll),
gval.Function("find", find),
gval.Function("sort", sortSlice),
}
}
@@ -220,3 +222,77 @@ func slice(arr interface{}, start, end int) interface{} {
return v.Slice(start, end).Interface()
}
// sortSlice sorts slice
func sortSlice(arr interface{}, desc bool) (out interface{}, err error) {
if arr, err = toSlice(arr); err != nil {
return
}
// sort slice of native type values
var (
c = reflect.ValueOf(arr)
v = reflect.MakeSlice(c.Type(), c.Len(), c.Cap())
vi = v.Interface()
)
reflect.Copy(v, c)
// sortStable sorts the slice x using the provided less
// function, keeping equal elements in their original order
//
// sort the array if error is nil;
// we trap the error(s) in the outer scope from the less function,
// and returning the last error at a time since all error will the same in almost every scenario.
sort.SliceStable(vi, func(i, j int) bool {
if err != nil {
return false
}
var (
nVal = v.Index(i)
mVal = v.Index(j)
)
if desc {
nVal = v.Index(j)
mVal = v.Index(i)
}
switch getKind(vi) {
case reflect.String:
return nVal.String() < mVal.String()
case reflect.Int, reflect.Int32, reflect.Int64:
return nVal.Int() < mVal.Int()
case reflect.Float32, reflect.Float64:
return nVal.Float() < mVal.Float()
default:
// sort slice of typed values
if stv, is := vi.([]TypedValue); is {
if casted, ok := stv[i].(Comparable); ok {
var cmp int
cmp, err = casted.Compare(stv[j])
if err != nil {
return false
}
if desc {
return cmp > 0
} else {
return cmp < 0
}
} else {
err = fmt.Errorf("cannot compare %s and %s: unknown state", stv[i].Type(), stv[j].Type())
return false
}
}
err = fmt.Errorf("cannot compare %s and %s: unknown state", getKind(nVal), getKind(mVal))
return false
}
})
// returns error from less function
if err != nil {
return arr, err
}
return vi, nil
}
+222
View File
@@ -12,12 +12,14 @@ var (
stringArr = []string{"first"}
boolArr = []bool{true, true, false}
floatArr = []float64{69.420}
strArr = []string{"5", "3", "1", "2"}
vals = map[string]interface{}{
"intArr": intArr,
"stringArr": stringArr,
"boolArr": boolArr,
"floatArr": floatArr,
"strArr": strArr,
"intVal": 42,
"stringVal": "foobar",
"boolVal": false,
@@ -73,6 +75,20 @@ func Example_pop_float() {
// 69.42
}
func Example_sort_string_asc() {
eval(`sort(strArr, false)`, vals)
// output:
// [1 2 3 5]
}
func Example_sort_string_desc() {
eval(`sort(strArr, true)`, vals)
// output:
// [5 3 2 1]
}
func Test_push(t *testing.T) {
tcc := []struct {
base interface{}
@@ -476,6 +492,212 @@ func Test_slice(t *testing.T) {
}
}
func Test_sortSlice(t *testing.T) {
var (
s1 = Must(NewString("1"))
s2 = Must(NewString("2"))
s3 = Must(NewString("3"))
s5 = Must(NewString("5"))
f1 = Must(NewFloat(11.1))
f2 = Must(NewString(22.2))
f3 = Must(NewString(33.3))
f5 = Must(NewString(55.5))
a1 = Must(NewAny("1"))
a2 = Must(NewAny("2"))
a3 = Must(NewAny("3"))
a5 = Must(NewAny("5"))
tcc = []struct {
name string
desc bool
arr interface{}
cloneArr interface{}
expect interface{}
expectErr error
}{
{
name: "ascending sorting for string array",
arr: []string{"3", "1", "2", "5"},
cloneArr: []string{"3", "1", "2", "5"},
expect: []string{"1", "2", "3", "5"},
},
{
name: "ascending sorting for string array with multiple identical element",
arr: []string{"1", "3", "2", "2"},
cloneArr: []string{"1", "3", "2", "2"},
expect: []string{"1", "2", "2", "3"},
},
{
name: "descending sorting for string array",
desc: true,
arr: []string{"3", "1", "2", "5"},
cloneArr: []string{"3", "1", "2", "5"},
expect: []string{"5", "3", "2", "1"},
},
{
name: "ascending sorting for int array",
arr: []int{1, 3, 5, 2},
cloneArr: []int{1, 3, 5, 2},
expect: []int{1, 2, 3, 5},
},
{
name: "descending sorting for int array",
desc: true,
arr: []int{1, 3, 5, 2},
cloneArr: []int{1, 3, 5, 2},
expect: []int{5, 3, 2, 1},
},
{
name: "ascending sorting for float32 array",
arr: []float32{11.1, 33.3, 55.5, 22.2},
cloneArr: []float32{11.1, 33.3, 55.5, 22.2},
expect: []float32{11.1, 22.2, 33.3, 55.5},
},
{
name: "descending sorting for float32 array",
desc: true,
arr: []float32{11.1, 33.3, 55.5, 22.2},
cloneArr: []float32{11.1, 33.3, 55.5, 22.2},
expect: []float32{55.5, 33.3, 22.2, 11.1},
},
{
name: "ascending sorting for float64 array",
arr: []float64{11.1, 33.3, 55.5, 22.2},
cloneArr: []float64{11.1, 33.3, 55.5, 22.2},
expect: []float64{11.1, 22.2, 33.3, 55.5},
},
{
name: "descending sorting for float64 array",
desc: true,
arr: []float64{11.1, 33.3, 55.5, 22.2},
cloneArr: []float64{11.1, 33.3, 55.5, 22.2},
expect: []float64{55.5, 33.3, 22.2, 11.1},
},
{
name: "ascending sorting for typedValue array of string",
arr: []TypedValue{
s5,
s3,
s2,
s1,
},
cloneArr: []TypedValue{
s5,
s3,
s2,
s1,
},
expect: []TypedValue{
s1,
s2,
s3,
s5,
},
},
{
name: "descending sorting for typedValue array of string",
desc: true,
arr: []TypedValue{
s3,
s1,
s2,
s5,
},
cloneArr: []TypedValue{
s3,
s1,
s2,
s5,
},
expect: []TypedValue{
s5,
s3,
s2,
s1,
},
},
{
name: "ascending sorting for typedValue array of float",
arr: []TypedValue{
f5,
f3,
f2,
f1,
},
cloneArr: []TypedValue{
f5,
f3,
f2,
f1,
},
expect: []TypedValue{
f1,
f2,
f3,
f5,
},
},
{
name: "descending sorting for typedValue array of float",
desc: true,
arr: []TypedValue{
f3,
f1,
f2,
f5,
},
cloneArr: []TypedValue{
f3,
f1,
f2,
f5,
},
expect: []TypedValue{
f5,
f3,
f2,
f1,
},
},
{
name: "expect error due to sorting for typedValue array of Any(Not Comparable)",
arr: []TypedValue{
a5,
a3,
a2,
a1,
},
cloneArr: []TypedValue{
a5,
a3,
a2,
a1,
},
expectErr: fmt.Errorf("cannot compare Any and Any: unknown state"),
},
}
)
for _, tc := range tcc {
t.Run(tc.name, func(t *testing.T) {
var (
req = require.New(t)
)
ss, err := sortSlice(tc.arr, tc.desc)
if tc.expectErr != nil {
req.Equal(tc.expectErr, err)
return
}
req.NoError(err)
req.Equal(tc.expect, ss)
req.Equal(tc.cloneArr, tc.arr)
})
}
}
func must(v []TypedValue, err error) []TypedValue {
if err != nil {
panic(err)