1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-11-23 21:54:45 +02:00

Feature/#9 array functions (#57)

* #9 Added 'APPEND' function

* #9 Added 'FIRST' function

* #9 Added 'FLATTEN' function

* #9 Added 'INTERSECTION' function

* #9 Added 'LAST' function

* #9 Added 'MINUS' function

* #9 Added 'NTH' function

* #9 Added 'OUTERSECTION' function

* #9 Added 'POP' function

* #9 Added 'POSITION' function

* #9 Added 'PUSH' function

* Fixed nil pointer exception in value parser

* #9 Added 'REMOVE_NTH' function

* #9 Added 'REMOVE_VALUE' function

* #9 Added 'REMOVE_VALUES' function

* #9 Added 'REVERSE' function

* #9 Added 'SHIFT' function

* #9 Added 'SLICE' function

* Removed meme

* #9 Added 'SORTED' function

* #9 Added SORTED_UNIQUE function

* #9 Added 'UNION' function

* #9 Added 'UNION_DISTINCT' function

* #9 Added 'UNIQUE' function

* #9 Added 'UNSHIFT' function

* #9 Made more strict optional arg validation

* #9 Fixed linting errors
This commit is contained in:
Tim Voronov
2018-10-05 21:27:34 -04:00
committed by GitHub
parent 9e947ea9c4
commit ec2d6a659b
53 changed files with 3053 additions and 5 deletions

View File

@@ -0,0 +1,74 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Appends a new item to an array and returns a new array with a given element.
* If ``uniqueOnly`` is set to true, then will add the item only if it's unique.
* @param arr (Array) - Target array.
* @param item (Value) - Target value to add.
* @returns arr (Array) - New array.
*/
func Append(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
arg := args[1]
unique := values.False
if len(args) > 2 {
err = core.ValidateType(args[2], core.BooleanType)
if err != nil {
return values.None, err
}
unique = args[2].(values.Boolean)
}
next := values.NewArray(int(arr.Length()) + 1)
if !unique {
arr.ForEach(func(item core.Value, idx int) bool {
next.Push(item)
return true
})
next.Push(arg)
return next, nil
}
hasDuplicate := false
arr.ForEach(func(item core.Value, idx int) bool {
next.Push(item)
if !hasDuplicate {
hasDuplicate = item.Compare(arg) == 0
}
return true
})
if !hasDuplicate {
next.Push(arg)
}
return next, nil
}

View File

@@ -0,0 +1,50 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/collections"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestAppend(t *testing.T) {
Convey("Should return a copy of an array", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Append(context.Background(), arr, values.NewInt(6))
So(err, ShouldBeNil)
So(out, ShouldNotEqual, arr)
So(out.(collections.Collection).Length(), ShouldBeGreaterThan, arr.Length())
})
Convey("Should ignore non-unique items", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Append(context.Background(), arr, values.NewInt(5), values.True)
So(err, ShouldBeNil)
So(out, ShouldNotEqual, arr)
So(out.(collections.Collection).Length(), ShouldEqual, arr.Length())
out2, err := arrays.Append(context.Background(), arr, values.NewInt(6), values.True)
So(err, ShouldBeNil)
So(out2, ShouldNotEqual, arr)
So(out2.(collections.Collection).Length(), ShouldBeGreaterThan, arr.Length())
})
}

View File

@@ -0,0 +1,30 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns a first element from a given array.
* @param arr (Array) - Target array.
* @returns element (Value) - First element in a given array.
*/
func First(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, nil
}
arr := args[0].(*values.Array)
return arr.Get(0), nil
}

View File

@@ -0,0 +1,35 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestFirst(t *testing.T) {
Convey("Should return a first element form a given array", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.First(context.Background(), arr)
So(err, ShouldBeNil)
So(out, ShouldEqual, 1)
})
Convey("Should return NONE if a given array is empty", t, func() {
arr := values.NewArray(0)
out, err := arrays.First(context.Background(), arr)
So(err, ShouldBeNil)
So(out, ShouldEqual, values.None)
})
}

View File

@@ -0,0 +1,67 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Turn an array of arrays into a flat array.
* All array elements in array will be expanded in the result array.
* Non-array elements are added as they are.
* The function will recurse into sub-arrays up to the specified depth.
* Duplicates will not be removed.
* @param arr (Array) - Target array.
* @param depth (Int, optional) - Depth level.
* @returns (Array) - Flat array.
*/
func Flatten(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
level := 1
if len(args) > 1 {
err = core.ValidateType(args[1], core.IntType)
if err != nil {
return values.None, err
}
level = int(args[1].(values.Int))
}
currentLevel := 0
result := values.NewArray(int(arr.Length()) * 2)
var unwrap func(input *values.Array)
unwrap = func(input *values.Array) {
currentLevel++
input.ForEach(func(value core.Value, idx int) bool {
if value.Type() != core.ArrayType || currentLevel > level {
result.Push(value)
} else {
unwrap(value.(*values.Array))
currentLevel--
}
return true
})
}
unwrap(arr)
return result, nil
}

View File

@@ -0,0 +1,71 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestFlatten(t *testing.T) {
Convey("Should flatten an array with depth 1", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewArrayWith(
values.NewInt(3),
values.NewInt(4),
values.NewArrayWith(
values.NewInt(5),
values.NewInt(6),
),
),
values.NewInt(7),
values.NewArrayWith(
values.NewInt(8),
values.NewArrayWith(
values.NewInt(9),
values.NewArrayWith(
values.NewInt(10),
),
),
),
)
out, err := arrays.Flatten(context.Background(), arr)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,3,4,[5,6],7,8,[9,[10]]]")
})
Convey("Should flatten an array with depth more than 1", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewArrayWith(
values.NewInt(3),
values.NewInt(4),
values.NewArrayWith(
values.NewInt(5),
values.NewInt(6),
),
),
values.NewInt(7),
values.NewArrayWith(
values.NewInt(8),
values.NewArrayWith(
values.NewInt(9),
values.NewArrayWith(
values.NewInt(10),
),
),
),
)
out, err := arrays.Flatten(context.Background(), arr, values.NewInt(2))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,3,4,5,6,7,8,9,[10]]")
})
}

View File

@@ -0,0 +1,70 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Return the intersection of all arrays specified.
* The result is an array of values that occur in all arguments.
* @param arrays (Array, repeated) - An arbitrary number of arrays as multiple arguments (at least 2).
* @returns (Array) - A single array with only the elements, which exist in all provided arrays.
* The element order is random. Duplicates are removed.
*/
func Intersection(_ context.Context, args ...core.Value) (core.Value, error) {
return sections(args, len(args))
}
func sections(args []core.Value, count int) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
intersections := make(map[uint64][]core.Value)
capacity := len(args)
for _, i := range args {
err := core.ValidateType(i, core.ArrayType)
if err != nil {
return values.None, err
}
arr := i.(*values.Array)
arr.ForEach(func(value core.Value, idx int) bool {
h := value.Hash()
bucket, exists := intersections[h]
if !exists {
bucket = make([]core.Value, 0, 5)
}
bucket = append(bucket, value)
intersections[h] = bucket
bucketLen := len(bucket)
if bucketLen > capacity {
capacity = bucketLen
}
return true
})
}
result := values.NewArray(capacity)
required := count
for _, bucket := range intersections {
if len(bucket) == required {
result.Push(bucket[0])
}
}
return result, nil
}

View File

@@ -0,0 +1,102 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestIntersection(t *testing.T) {
Convey("Should find intersections between 2 arrays", t, func() {
arr1 := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
)
arr2 := values.NewArrayWith(
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
values.NewInt(7),
values.NewInt(8),
values.NewInt(9),
)
out, err := arrays.Intersection(context.Background(), arr1, arr2)
check := map[int]bool{
4: true,
5: true,
6: true,
}
So(err, ShouldBeNil)
arr := out.(*values.Array)
So(arr.Length(), ShouldEqual, 3)
arr.ForEach(func(value core.Value, idx int) bool {
_, exists := check[int(value.(values.Int))]
So(exists, ShouldBeTrue)
return true
})
})
Convey("Should find intersections between more than 2 arrays", t, func() {
arr1 := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
arr2 := values.NewArrayWith(
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
)
arr3 := values.NewArrayWith(
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
values.NewInt(7),
)
out, err := arrays.Intersection(context.Background(), arr1, arr2, arr3)
check := map[int]bool{
3: true,
4: true,
5: true,
}
So(err, ShouldBeNil)
arr := out.(*values.Array)
So(arr.Length(), ShouldEqual, 3)
arr.ForEach(func(value core.Value, idx int) bool {
_, exists := check[int(value.(values.Int))]
So(exists, ShouldBeTrue)
return true
})
})
}

30
pkg/stdlib/arrays/last.go Normal file
View File

@@ -0,0 +1,30 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns the last element of an array.
* @param array (Array) - The target array.
* @returns (Value) - Last element of an array.
*/
func Last(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, nil
}
arr := args[0].(*values.Array)
return arr.Get(arr.Length() - 1), nil
}

View File

@@ -0,0 +1,35 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestLast(t *testing.T) {
Convey("Should return a last element form a given array", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Last(context.Background(), arr)
So(err, ShouldBeNil)
So(out, ShouldEqual, 5)
})
Convey("Should return NONE if a given array is empty", t, func() {
arr := values.NewArray(0)
out, err := arrays.Last(context.Background(), arr)
So(err, ShouldBeNil)
So(out, ShouldEqual, values.None)
})
}

31
pkg/stdlib/arrays/lib.go Normal file
View File

@@ -0,0 +1,31 @@
package arrays
import "github.com/MontFerret/ferret/pkg/runtime/core"
func NewLib() map[string]core.Function {
return map[string]core.Function{
"APPEND": Append,
"FIRST": First,
"FLATTEN": Flatten,
"INTERSECTION": Intersection,
"LAST": Last,
"MINUS": Minus,
"NTH": Nth,
"OUTERSECTION": Outersection,
"POP": Pop,
"POSITION": Position,
"PUSH": Push,
"REMOVE_NTH": RemoveNth,
"REMOVE_VALUE": RemoveValue,
"REMOVE_VALUES": RemoveValues,
"REVERSE": Reverse,
"SHIFT": Shift,
"SLICE": Slice,
"SORTED": Sorted,
"SORTED_UNIQUE": SortedUnique,
"UNION": Union,
"UNION_DISTINCT": UnionDistinct,
"UNIQUE": Unique,
"UNSHIFT": Unshift,
}
}

View File

@@ -0,0 +1,63 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Return the difference of all arrays specified.
* @param arrays (Array, repeated) - An arbitrary number of arrays as multiple arguments (at least 2).
* @returns array (Array) - An array of values that occur in the first array, but not in any of the subsequent arrays.
* The order of the result array is undefined and should not be relied on. Duplicates will be removed.
*/
func Minus(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
intersections := make(map[uint64]core.Value)
capacity := values.NewInt(0)
for idx, i := range args {
err := core.ValidateType(i, core.ArrayType)
if err != nil {
return values.None, err
}
arr := i.(*values.Array)
arr.ForEach(func(value core.Value, _ int) bool {
h := value.Hash()
// first array, fill out the map
if idx == 0 {
capacity = arr.Length()
intersections[h] = value
return true
}
_, exists := intersections[h]
// if it exists in the first array, remove it
if exists {
delete(intersections, h)
}
return true
})
}
result := values.NewArray(int(capacity))
for _, item := range intersections {
result.Push(item)
}
return result, nil
}

View File

@@ -0,0 +1,94 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestMinus(t *testing.T) {
Convey("Should find differences between 2 arrays", t, func() {
arr1 := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
)
arr2 := values.NewArrayWith(
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
)
out, err := arrays.Minus(context.Background(), arr1, arr2)
check := map[int]bool{
1: true,
2: true,
}
So(err, ShouldBeNil)
arr := out.(*values.Array)
So(arr.Length(), ShouldEqual, 2)
arr.ForEach(func(value core.Value, idx int) bool {
_, exists := check[int(value.(values.Int))]
So(exists, ShouldBeTrue)
return true
})
})
Convey("Should find differences between more than 2 arrays", t, func() {
arr1 := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
)
arr2 := values.NewArrayWith(
values.NewInt(3),
values.NewInt(9),
values.NewInt(5),
values.NewInt(6),
)
arr3 := values.NewArrayWith(
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
values.NewInt(7),
values.NewInt(8),
)
out, err := arrays.Minus(context.Background(), arr1, arr2, arr3)
check := map[int]bool{
1: true,
2: true,
}
So(err, ShouldBeNil)
arr := out.(*values.Array)
So(arr.Length(), ShouldEqual, 2)
arr.ForEach(func(value core.Value, idx int) bool {
_, exists := check[int(value.(values.Int))]
So(exists, ShouldBeTrue)
return true
})
})
}

40
pkg/stdlib/arrays/nth.go Normal file
View File

@@ -0,0 +1,40 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns the element of an array at a given position.
* It is the same as anyArray[position] for positive positions, but does not support negative positions.
* @param array (Array) - An array with elements of arbitrary type.
* @param index (Int) - Position of desired element in array, positions start at 0.
* @returns (Value) - The array element at the given position.
* If position is negative or beyond the upper bound of the array, then NONE will be returned.
*/
func Nth(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 2)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[1], core.IntType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
idx := args[1].(values.Int)
return arr.Get(idx), nil
}

View File

@@ -0,0 +1,44 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestNth(t *testing.T) {
Convey("Should return item by index", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Nth(context.Background(), arr, values.NewInt(1))
So(err, ShouldBeNil)
So(out.Compare(values.NewInt(2)), ShouldEqual, 0)
})
Convey("Should return None when no value", t, func() {
arr := values.NewArrayWith()
out, err := arrays.Nth(context.Background(), arr, values.NewInt(1))
So(err, ShouldBeNil)
So(out.Compare(values.None), ShouldEqual, 0)
})
Convey("Should return None when passed negative value", t, func() {
arr := values.NewArrayWith()
out, err := arrays.Nth(context.Background(), arr, values.NewInt(-1))
So(err, ShouldBeNil)
So(out.Compare(values.None), ShouldEqual, 0)
})
}

View File

@@ -0,0 +1,16 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
)
/*
* Return the values that occur only once across all arrays specified.
* @param arrays (Array, repeated) - An arbitrary number of arrays as multiple arguments (at least 2).
* @returns (Array) - A single array with only the elements that exist only once across all provided arrays.
* The element order is random.
*/
func Outersection(_ context.Context, args ...core.Value) (core.Value, error) {
return sections(args, 1)
}

View File

@@ -0,0 +1,88 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestOutersection(t *testing.T) {
Convey("Should find intersections between 2 arrays", t, func() {
arr1 := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
)
arr2 := values.NewArrayWith(
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
)
out, err := arrays.Outersection(context.Background(), arr1, arr2)
check := map[int]bool{
1: true,
4: true,
}
So(err, ShouldBeNil)
arr := out.(*values.Array)
So(arr.Length(), ShouldEqual, 2)
arr.ForEach(func(value core.Value, idx int) bool {
_, exists := check[int(value.(values.Int))]
So(exists, ShouldBeTrue)
return true
})
})
Convey("Should find intersections between more than 2 arrays", t, func() {
arr1 := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
)
arr2 := values.NewArrayWith(
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
)
arr3 := values.NewArrayWith(
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Outersection(context.Background(), arr1, arr2, arr3)
check := map[int]bool{
1: true,
5: true,
}
So(err, ShouldBeNil)
arr := out.(*values.Array)
So(arr.Length(), ShouldEqual, 2)
arr.ForEach(func(value core.Value, idx int) bool {
_, exists := check[int(value.(values.Int))]
So(exists, ShouldBeTrue)
return true
})
})
}

44
pkg/stdlib/arrays/pop.go Normal file
View File

@@ -0,0 +1,44 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns a new array without last element.
* @param array (Array) - Target array.
* @returns (Array) - Copy of an array without last element.
*/
func Pop(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
length := int(arr.Length())
result := values.NewArray(length)
lastIdx := length - 1
arr.ForEach(func(value core.Value, idx int) bool {
if idx == lastIdx {
return false
}
result.Push(value)
return true
})
return result, nil
}

View File

@@ -0,0 +1,35 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestPop(t *testing.T) {
Convey("Should return a copy of an array without last element", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Pop(context.Background(), arr)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,3,4]")
})
Convey("Should return empty array if a given one is empty", t, func() {
arr := values.NewArray(0)
out, err := arrays.Pop(context.Background(), arr)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[]")
})
}

View File

@@ -0,0 +1,49 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns a value indicating whether an element is contained in array. Optionally returns its position.
* @param array (Array) - The source array.
* @param value (Value) - The target value.
* @param returnIndex (Boolean, optional) - Value which indicates whether to return item's position.
*/
func Position(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
el := args[1]
retIdx := false
if len(args) > 2 {
err = core.ValidateType(args[2], core.BooleanType)
if err != nil {
return values.None, err
}
retIdx = args[2].Compare(values.True) == 0
}
position := arr.IndexOf(el)
if !retIdx {
return values.NewBoolean(position > -1), nil
}
return position, nil
}

View File

@@ -0,0 +1,81 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestPosition(t *testing.T) {
Convey("Should return TRUE when a value exists in a given array", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Position(context.Background(), arr, values.NewInt(3))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "true")
})
Convey("Should return FALSE when a value does not exist in a given array", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Position(context.Background(), arr, values.NewInt(6))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "false")
})
Convey("Should return index when a value exists in a given array", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Position(
context.Background(),
arr,
values.NewInt(3),
values.NewBoolean(true),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "2")
})
Convey("Should return -1 when a value does not exist in a given array", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Position(
context.Background(),
arr,
values.NewInt(6),
values.NewBoolean(true),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "-1")
})
}

61
pkg/stdlib/arrays/push.go Normal file
View File

@@ -0,0 +1,61 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Create a new array with appended value.
* @param array (Array) - Source array.
* @param value (Value) - Target value.
* @param unique (Boolean, optional) - Value indicating whether to do uniqueness check.
* @returns (Array) - A new array with appended value.
*/
func Push(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
value := args[1]
uniq := false
if len(args) > 2 {
err = core.ValidateType(args[2], core.BooleanType)
if err != nil {
return values.None, err
}
uniq = args[2].Compare(values.True) == 0
}
result := values.NewArray(int(arr.Length() + 1))
push := true
arr.ForEach(func(item core.Value, idx int) bool {
if uniq && push {
push = !(item.Compare(value) == 0)
}
result.Push(item)
return true
})
if push {
result.Push(value)
}
return result, nil
}

View File

@@ -0,0 +1,55 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestPush(t *testing.T) {
Convey("Should create a new array with a new element in the end", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Push(context.Background(), arr, values.NewInt(6))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
})
Convey("Should not add a new element if not unique when uniqueness check is enabled", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Push(
context.Background(),
arr,
values.NewInt(6),
values.True,
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
out2, err := arrays.Push(
context.Background(),
arr,
values.NewInt(6),
values.True,
)
So(out2.String(), ShouldEqual, "[1,2,3,4,5,6]")
})
}

View File

@@ -0,0 +1,47 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns a new array without an element by a given position.
* @param array (Array) - Source array.
* @param position (Int) - Target element position.
* @return (Array) - A new array without an element by a given position.
*/
func RemoveNth(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 2)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[1], core.IntType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
index := int(args[1].(values.Int))
result := values.NewArray(int(arr.Length() - 1))
arr.ForEach(func(value core.Value, idx int) bool {
if idx != index {
result.Push(value)
}
return true
})
return result, nil
}

View File

@@ -0,0 +1,41 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestRemoveNth(t *testing.T) {
Convey("Should return a copy of an array without an element by its position", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.RemoveNth(context.Background(), arr, values.NewInt(2))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,4,5]")
})
Convey("Should return a copy of an array with all elements when a position is invalid", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.RemoveNth(context.Background(), arr, values.NewInt(6))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,3,4,5]")
})
}

View File

@@ -0,0 +1,64 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns a new array with removed all occurrences of value in a given array.
* Optionally with a limit to the number of removals.
* @param array (Array) - Source array.
* @param value (Value) - Target value.
* @param limit (Int, optional) - A limit to the number of removals.
* @returns (Array) - A new array with removed all occurrences of value in a given array.
*/
func RemoveValue(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
value := args[1]
limit := -1
if len(args) > 2 {
err = core.ValidateType(args[2], core.IntType)
if err != nil {
return values.None, err
}
limit = int(args[2].(values.Int))
}
result := values.NewArray(int(arr.Length()))
counter := 0
arr.ForEach(func(item core.Value, idx int) bool {
remove := item.Compare(value) == 0
if remove {
if counter == limit {
result.Push(item)
}
counter++
} else {
result.Push(item)
}
return true
})
return result, nil
}

View File

@@ -0,0 +1,48 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestRemoveValue(t *testing.T) {
Convey("Should return a copy of an array without given element(s)", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(3),
)
out, err := arrays.RemoveValue(context.Background(), arr, values.NewInt(3))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,4]")
})
Convey("Should return a copy of an array without given element(s) with limit", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(3),
values.NewInt(5),
values.NewInt(3),
)
out, err := arrays.RemoveValue(
context.Background(),
arr,
values.NewInt(3),
values.Int(2),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,4,5,3]")
})
}

View File

@@ -0,0 +1,59 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns a new array with removed all occurrences of values in a given array.
* @param array (Array) - Source array.
* @param values (Array) - Target values.
* @returns (Array) - A new array with removed all occurrences of values in a given array.
*/
func RemoveValues(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 2)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[1], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
vals := args[1].(*values.Array)
result := values.NewArray(int(arr.Length()))
lookupTable := make(map[uint64]bool)
vals.ForEach(func(value core.Value, idx int) bool {
lookupTable[value.Hash()] = true
return true
})
arr.ForEach(func(value core.Value, idx int) bool {
h := value.Hash()
_, exists := lookupTable[h]
if !exists {
result.Push(value)
}
return true
})
return result, nil
}

View File

@@ -0,0 +1,35 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestRemoveValues(t *testing.T) {
Convey("Should return a copy of an array without given elements", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
)
out, err := arrays.RemoveValues(
context.Background(),
arr,
values.NewArrayWith(
values.NewInt(3),
values.NewInt(5),
values.NewInt(6),
),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,4]")
})
}

View File

@@ -0,0 +1,36 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Return a new array with its elements reversed.
* @param array (Array) - Target array.
* @returns (Array) - A new array with its elements reversed.
*/
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
size := int(arr.Length())
result := values.NewArray(size)
for i := size - 1; i >= 0; i-- {
result.Push(arr.Get(values.NewInt(i)))
}
return result, nil
}

View File

@@ -0,0 +1,42 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestReverse(t *testing.T) {
Convey("Should return a copy of an array with reversed elements", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
)
out, err := arrays.Reverse(
context.Background(),
arr,
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[6,5,4,3,2,1]")
})
Convey("Should return an empty array when there no elements in a source one", t, func() {
arr := values.NewArray(0)
out, err := arrays.Reverse(
context.Background(),
arr,
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[]")
})
}

View File

@@ -0,0 +1,41 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns a new array without the first element.
* @param array (Array) - Target array.
* @returns (Array) - Copy of an array without the first element.
*/
func Shift(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
length := int(arr.Length())
result := values.NewArray(length)
arr.ForEach(func(value core.Value, idx int) bool {
if idx != 0 {
result.Push(value)
}
return true
})
return result, nil
}

View File

@@ -0,0 +1,35 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestShift(t *testing.T) {
Convey("Should return a copy of an array without the first element", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Shift(context.Background(), arr)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[2,3,4,5]")
})
Convey("Should return empty array if a given one is empty", t, func() {
arr := values.NewArray(0)
out, err := arrays.Shift(context.Background(), arr)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[]")
})
}

View File

@@ -0,0 +1,50 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns a new sliced array.
* @param array (Array) - Source array.
* @param start (Int) - Start position of extraction.
* @param length (Int, optional) - Value indicating how many elements to extract.
* @returns (Array) - Sliced array.
*/
func Slice(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[1], core.IntType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
start := args[1].(values.Int)
length := values.NewInt(int(arr.Length()))
if len(args) > 2 {
if args[2].Type() == core.IntType {
arg2 := args[2].(values.Int)
if arg2 > 0 {
length = start + args[2].(values.Int)
}
}
}
return arr.Slice(start, length), nil
}

View File

@@ -0,0 +1,80 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestSlice(t *testing.T) {
Convey("Should return a sliced array with a given start position ", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
)
out, err := arrays.Slice(context.Background(), arr, values.NewInt(3))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[4,5,6]")
})
Convey("Should return an empty array when start position is out of bounds", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
)
out, err := arrays.Slice(context.Background(), arr, values.NewInt(6))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[]")
})
Convey("Should return a sliced array with a given start position and length", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
)
out, err := arrays.Slice(
context.Background(),
arr,
values.NewInt(2),
values.NewInt(2),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[3,4]")
})
Convey("Should return an empty array when length is out of bounds", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
values.NewInt(6),
)
out, err := arrays.Slice(context.Background(), arr, values.NewInt(2), values.NewInt(10))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[3,4,5,6]")
})
}

View File

@@ -0,0 +1,53 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/collections"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Sorts all elements in anyArray.
* The function will use the default comparison order for FQL value types.
* @param array (Array) - Target array.
* @returns (Array) - Sorted array.
*/
func Sorted(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
if arr.Length() == 0 {
return values.NewArray(0), nil
}
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
return first.Compare(second), nil
}, collections.SortDirectionAsc)
if err != nil {
return values.None, err
}
iterator, err := collections.NewSortIterator(
collections.NewArrayIterator(arr),
sorter,
)
if err != nil {
return values.None, err
}
return collections.ToArray(iterator)
}

View File

@@ -0,0 +1,52 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestSorted(t *testing.T) {
Convey("Should sort numbers", t, func() {
arr := values.NewArrayWith(
values.NewInt(3),
values.NewInt(1),
values.NewInt(6),
values.NewInt(2),
values.NewInt(5),
values.NewInt(4),
)
out, err := arrays.Sorted(context.Background(), arr)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
})
Convey("Should sort strings", t, func() {
arr := values.NewArrayWith(
values.NewString("b"),
values.NewString("c"),
values.NewString("a"),
values.NewString("d"),
values.NewString("e"),
values.NewString("f"),
)
out, err := arrays.Sorted(context.Background(), arr)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `["a","b","c","d","e","f"]`)
})
Convey("Should return empty array", t, func() {
arr := values.NewArrayWith()
out, err := arrays.Sorted(context.Background(), arr)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `[]`)
})
}

View File

@@ -0,0 +1,60 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/collections"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Sorts all elements in anyArray.
* The function will use the default comparison order for FQL value types.
* Additionally, the values in the result array will be made unique
* @param array (Array) - Target array.
* @returns (Array) - Sorted array.
*/
func SortedUnique(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
if arr.Length() == 0 {
return values.NewArray(0), nil
}
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
return first.Compare(second), nil
}, collections.SortDirectionAsc)
if err != nil {
return values.None, err
}
uniqIterator, err := collections.NewUniqueIterator(collections.NewArrayIterator(arr))
if err != nil {
return values.None, err
}
iterator, err := collections.NewSortIterator(
uniqIterator,
sorter,
)
if err != nil {
return values.None, err
}
return collections.ToArray(iterator)
}

View File

@@ -0,0 +1,60 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestSortedUnique(t *testing.T) {
Convey("Should sort numbers", t, func() {
arr := values.NewArrayWith(
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
values.NewInt(1),
values.NewInt(6),
values.NewInt(2),
values.NewInt(6),
values.NewInt(5),
values.NewInt(1),
values.NewInt(4),
)
out, err := arrays.SortedUnique(context.Background(), arr)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
})
Convey("Should sort strings", t, func() {
arr := values.NewArrayWith(
values.NewString("e"),
values.NewString("b"),
values.NewString("a"),
values.NewString("c"),
values.NewString("a"),
values.NewString("d"),
values.NewString("f"),
values.NewString("d"),
values.NewString("e"),
values.NewString("f"),
)
out, err := arrays.SortedUnique(context.Background(), arr)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `["a","b","c","d","e","f"]`)
})
Convey("Should return empty array", t, func() {
arr := values.NewArrayWith()
out, err := arrays.SortedUnique(context.Background(), arr)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `[]`)
})
}

View File

@@ -0,0 +1,46 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns the union of all passed arrays.
* @param arrays (Array, repeated) - List of arrays to combine.
* @returns (Array) - All array elements combined in a single array, in any order.
*/
func Union(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
firstArrLen := args[0].(*values.Array).Length()
result := values.NewArray(len(args) * int(firstArrLen))
for _, arg := range args {
err := core.ValidateType(arg, core.ArrayType)
if err != nil {
return values.None, err
}
arr := arg.(*values.Array)
arr.ForEach(func(value core.Value, _ int) bool {
result.Push(value)
return true
})
}
return result, nil
}

View File

@@ -0,0 +1,50 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
func UnionDistinct(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
firstArrLen := args[0].(*values.Array).Length()
result := values.NewArray(len(args) * int(firstArrLen))
hashes := make(map[uint64]bool)
for _, arg := range args {
err := core.ValidateType(arg, core.ArrayType)
if err != nil {
return values.None, err
}
arr := arg.(*values.Array)
arr.ForEach(func(value core.Value, _ int) bool {
h := value.Hash()
_, exists := hashes[h]
if !exists {
hashes[h] = true
result.Push(value)
}
return true
})
}
return result, nil
}

View File

@@ -0,0 +1,51 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestUnionDistinct(t *testing.T) {
Convey("Should union all arrays with unique values", t, func() {
arr1 := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
)
arr2 := values.NewArrayWith(
values.NewInt(5),
values.NewInt(2),
values.NewInt(6),
values.NewInt(4),
)
arr3 := values.NewArrayWith(
values.NewString("a"),
values.NewString("b"),
values.NewString("c"),
values.NewString("d"),
)
arr4 := values.NewArrayWith(
values.NewString("e"),
values.NewString("b"),
values.NewString("f"),
values.NewString("d"),
)
out, err := arrays.UnionDistinct(
context.Background(),
arr1,
arr2,
arr3,
arr4,
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `[1,2,3,4,5,6,"a","b","c","d","e","f"]`)
})
}

View File

@@ -0,0 +1,53 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
/*
* Returns the union of distinct values of all passed arrays.
* @param arrays (Array, repeated) - List of arrays to combine.
* @returns (Array) - All array elements combined in a single array, without duplicates, in any order.
*/
func TestUnion(t *testing.T) {
Convey("Should union all arrays", t, func() {
arr1 := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
)
arr2 := values.NewArrayWith(
values.NewString("a"),
values.NewString("b"),
values.NewString("c"),
values.NewString("d"),
)
arr3 := values.NewArrayWith(
values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
),
values.NewArrayWith(
values.NewInt(3),
values.NewInt(4),
),
)
out, err := arrays.Union(
context.Background(),
arr1,
arr2,
arr3,
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, `[1,2,3,4,"a","b","c","d",[1,2],[3,4]]`)
})
}

View File

@@ -0,0 +1,43 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/collections"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns all unique elements from a given array.
* @param array (Array) - Target array.
* @returns (Array) - New array without duplicates.
*/
func Unique(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
if arr.Length() == 0 {
return values.NewArray(0), nil
}
iterator, err := collections.NewUniqueIterator(
collections.NewArrayIterator(arr),
)
if err != nil {
return values.None, err
}
return collections.ToArray(iterator)
}

View File

@@ -0,0 +1,35 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestUnique(t *testing.T) {
Convey("Should return only unique items", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(3),
values.NewInt(5),
values.NewInt(6),
values.NewInt(5),
values.NewInt(6),
)
res, err := arrays.Unique(
context.Background(),
arr,
)
So(err, ShouldBeNil)
So(res.String(), ShouldEqual, `[1,2,3,4,5,6]`)
})
}

View File

@@ -0,0 +1,80 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Prepends value to a given array.
* @param array (Array) - Target array.
* @param value (Value) - Target value to prepend.
* @param unique (Boolean, optional) - Optional value indicating whether a value must be unique to be prepended.
* Default is false.
* @returns (Array) - New array with prepended value.
*/
func Unshift(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
value := args[1]
uniq := values.False
if len(args) > 2 {
err = core.ValidateType(args[2], core.BooleanType)
if err != nil {
return values.None, err
}
uniq = args[2].(values.Boolean)
}
result := values.NewArray(int(arr.Length() + 1))
if !uniq {
result.Push(value)
arr.ForEach(func(el core.Value, _ int) bool {
result.Push(el)
return true
})
} else {
ok := true
// let's just hope it's unique
// if not, we will terminate the loop and return a copy of an array
result.Push(value)
arr.ForEach(func(el core.Value, idx int) bool {
if el.Compare(value) != 0 {
result.Push(el)
return true
}
// not unique
ok = false
return false
})
if !ok {
// value is not unique, just return a new copy with same elements
return arr.Clone(), nil
}
}
return result, nil
}

View File

@@ -0,0 +1,59 @@
package arrays_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestUnshift(t *testing.T) {
Convey("Should return a copy of an array", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Unshift(context.Background(), arr, values.NewInt(0))
So(err, ShouldBeNil)
So(out, ShouldNotEqual, arr)
So(out.String(), ShouldEqual, "[0,1,2,3,4,5]")
})
Convey("Should ignore non-unique items", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewInt(5),
)
out, err := arrays.Unshift(
context.Background(),
arr,
values.NewInt(0),
values.True,
)
So(err, ShouldBeNil)
So(out, ShouldNotEqual, arr)
So(out.String(), ShouldEqual, "[0,1,2,3,4,5]")
out2, err := arrays.Unshift(
context.Background(),
arr,
values.NewInt(0),
values.True,
)
So(err, ShouldBeNil)
So(out2, ShouldNotEqual, arr)
So(out.String(), ShouldEqual, "[0,1,2,3,4,5]")
})
}

View File

@@ -2,6 +2,7 @@ package stdlib
import (
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
"github.com/MontFerret/ferret/pkg/stdlib/collections"
"github.com/MontFerret/ferret/pkg/stdlib/html"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
@@ -21,6 +22,7 @@ func NewLib() map[string]core.Function {
add(types.NewLib())
add(strings.NewLib())
add(collections.NewLib())
add(arrays.NewLib())
add(html.NewLib())
add(utils.NewLib())