1
0
mirror of https://github.com/MontFerret/ferret.git synced 2024-12-16 11:37:36 +02:00
ferret/pkg/runtime/collections/sort.go
2018-09-18 16:42:38 -04:00

152 lines
2.6 KiB
Go

package collections
import (
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/pkg/errors"
"sort"
"strings"
)
type (
SortDirection int
Comparator func(first core.Value, second core.Value) (int, error)
Sorter struct {
fn Comparator
direction SortDirection
}
SortIterator struct {
src Iterator
sorters []*Sorter
ready bool
values *SliceIterator
}
)
const (
SortDirectionAsc SortDirection = 1
SortDirectionDesc SortDirection = -1
)
func SortDirectionFromString(str string) SortDirection {
if strings.ToUpper(str) == "DESC" {
return SortDirectionDesc
}
return SortDirectionAsc
}
func IsValidSortDirection(direction SortDirection) bool {
switch direction {
case SortDirectionAsc, SortDirectionDesc:
return true
default:
return false
}
}
func NewSorter(fn Comparator, direction SortDirection) (*Sorter, error) {
if fn == nil {
return nil, core.Error(core.ErrMissedArgument, "fn")
}
if IsValidSortDirection(direction) == false {
return nil, core.Error(core.ErrInvalidArgument, "direction")
}
return &Sorter{fn, direction}, nil
}
func NewSortIterator(
src Iterator,
comparators ...*Sorter,
) (*SortIterator, error) {
if core.IsNil(src) {
return nil, errors.Wrap(core.ErrMissedArgument, "source")
}
if comparators == nil || len(comparators) == 0 {
return nil, errors.Wrap(core.ErrMissedArgument, "comparator")
}
return &SortIterator{src, comparators, false, nil}, nil
}
func (iterator *SortIterator) HasNext() bool {
// we need to initialize the iterator
if iterator.ready == false {
iterator.ready = true
values, err := iterator.sort()
if err != nil {
// set to true because we do not want to initialize next time anymore
iterator.values = NewSliceIterator(make([]core.Value, 0, 0))
return false
}
iterator.values = values
}
return iterator.values.HasNext()
}
func (iterator *SortIterator) Next() (core.Value, core.Value, error) {
return iterator.values.Next()
}
func (iterator *SortIterator) sort() (*SliceIterator, error) {
res, err := ToSlice(iterator.src)
if err != nil {
return nil, err
}
var failure error
sort.SliceStable(res, func(i, j int) bool {
// ignore next execution
if failure != nil {
return false
}
var out bool
for _, comp := range iterator.sorters {
left := res[i]
right := res[j]
eq, err := comp.fn(left, right)
if err != nil {
failure = err
out = false
break
}
eq = eq * int(comp.direction)
if eq == -1 {
out = true
break
}
if eq == 1 {
out = false
break
}
}
return out
})
if failure != nil {
return nil, failure
}
return NewSliceIterator(res), nil
}