cuelang.org/go@v0.13.0/pkg/list/sort.go (about)

     1  // Copyright 2018 The CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Copyright 2018 The Go Authors. All rights reserved.
    16  // Use of this source code is governed by a BSD-style
    17  // license that can be found in the LICENSE file.
    18  
    19  package list
    20  
    21  import (
    22  	"slices"
    23  	"sort"
    24  
    25  	"cuelang.org/go/cue"
    26  	"cuelang.org/go/internal"
    27  	"cuelang.org/go/internal/core/adt"
    28  	"cuelang.org/go/internal/core/eval"
    29  	"cuelang.org/go/internal/types"
    30  )
    31  
    32  // valueSorter defines a sort.Interface; implemented in cue/builtinutil.go.
    33  type valueSorter struct {
    34  	ctx *adt.OpContext
    35  	a   []cue.Value
    36  	err error
    37  
    38  	cmp  *adt.Vertex
    39  	less *adt.Vertex
    40  	x    *adt.Vertex
    41  	y    *adt.Vertex
    42  }
    43  
    44  func (s *valueSorter) ret() ([]cue.Value, error) {
    45  	if s.err != nil {
    46  		return nil, s.err
    47  	}
    48  	// The input slice is already a copy and that we can modify it safely.
    49  	return s.a, nil
    50  }
    51  
    52  func (s *valueSorter) Len() int      { return len(s.a) }
    53  func (s *valueSorter) Swap(i, j int) { s.a[i], s.a[j] = s.a[j], s.a[i] }
    54  func (s *valueSorter) Less(i, j int) bool {
    55  	if s.err != nil {
    56  		return false
    57  	}
    58  
    59  	if s.ctx.Version == internal.DevVersion {
    60  		return s.lessNew(i, j)
    61  	}
    62  
    63  	var x, y types.Value
    64  	s.a[i].Core(&x)
    65  	s.a[j].Core(&y)
    66  
    67  	// Save the state of all relevant arcs and restore later for the
    68  	// next comparison.
    69  	saveCmp := *s.cmp
    70  	saveLess := *s.less
    71  	saveX := *s.x
    72  	saveY := *s.y
    73  
    74  	s.x.InsertConjunctsFrom(x.V)
    75  	s.y.InsertConjunctsFrom(y.V)
    76  
    77  	// TODO(perf): if we can determine that the comparator values for
    78  	// x and y are idempotent (no arcs and a basevalue being top or
    79  	// a struct or list marker), then we do not need to reevaluate the input.
    80  	// In that case, we can use the below code instead of the above two loops
    81  	// setting the conjuncts. This may improve performance significantly.
    82  	//
    83  	// s.x.BaseValue = x.V.BaseValue
    84  	// s.x.Arcs = x.V.Arcs
    85  	// s.y.BaseValue = y.V.BaseValue
    86  	// s.y.Arcs = y.V.Arcs
    87  
    88  	s.less.Finalize(s.ctx)
    89  	isLess := s.ctx.BoolValue(s.less)
    90  	if b := s.less.Err(s.ctx); b != nil && s.err == nil {
    91  		s.err = b.Err
    92  		return true
    93  	}
    94  
    95  	*s.less = saveLess
    96  	*s.cmp = saveCmp
    97  	*s.x = saveX
    98  	*s.y = saveY
    99  
   100  	return isLess
   101  }
   102  
   103  func (s *valueSorter) lessNew(i, j int) bool {
   104  	ctx := s.ctx
   105  
   106  	n := &adt.Vertex{
   107  		Label:     s.cmp.Label,
   108  		Parent:    s.cmp.Parent,
   109  		Conjuncts: s.cmp.Conjuncts,
   110  	}
   111  
   112  	n.Init(ctx)
   113  
   114  	less := getArc(ctx, n, "less")
   115  	xa := getArc(ctx, n, "x")
   116  	ya := getArc(ctx, n, "y")
   117  
   118  	var x, y types.Value
   119  	s.a[i].Core(&x)
   120  	s.a[j].Core(&y)
   121  
   122  	xa.InsertConjunctsFrom(x.V)
   123  	ya.InsertConjunctsFrom(y.V)
   124  
   125  	// TODO(perf): if we can determine that the comparator values for
   126  	// x and y are idempotent (no arcs and a basevalue being top or
   127  	// a struct or list marker), then we do not need to reevaluate the input.
   128  	// In that case, we can use the below code instead of the above two loops
   129  	// setting the conjuncts. This may improve performance significantly.
   130  	//
   131  	// s.x.BaseValue = x.V.BaseValue
   132  	// s.x.Arcs = x.V.Arcs
   133  	// s.y.BaseValue = y.V.BaseValue
   134  	// s.y.Arcs = y.V.Arcs
   135  
   136  	less.Finalize(s.ctx)
   137  
   138  	isLess := s.ctx.BoolValue(less)
   139  	if b := less.Err(s.ctx); b != nil && s.err == nil {
   140  		s.err = b.Err
   141  		return true
   142  	}
   143  
   144  	return isLess
   145  }
   146  
   147  var less = cue.ParsePath("less")
   148  
   149  func makeValueSorter(list []cue.Value, cmp cue.Value) (s valueSorter) {
   150  	if v := cmp.LookupPath(less); !v.Exists() {
   151  		return valueSorter{err: v.Err()}
   152  	}
   153  
   154  	var v types.Value
   155  	cmp.Core(&v)
   156  	ctx := eval.NewContext(v.R, v.V)
   157  
   158  	n := &adt.Vertex{
   159  		Label:     v.V.Label,
   160  		Parent:    v.V.Parent,
   161  		Conjuncts: v.V.Conjuncts,
   162  	}
   163  	n.CompleteArcs(ctx)
   164  
   165  	s = valueSorter{
   166  		a:    list,
   167  		ctx:  ctx,
   168  		cmp:  n,
   169  		less: getArc(ctx, n, "less"),
   170  		x:    getArc(ctx, n, "x"),
   171  		y:    getArc(ctx, n, "y"),
   172  	}
   173  
   174  	// TODO(perf): see comment in the Less method. If we can determine
   175  	// the conjuncts for x and y are idempotent, we can pre finalize here and
   176  	// ignore the values in the Less method.
   177  	// s.x.UpdateStatus(adt.Finalized)
   178  	// s.y.UpdateStatus(adt.Finalized)
   179  
   180  	return s
   181  }
   182  
   183  // Sort sorts data while keeping the original order of equal elements.
   184  // It does O(n*log(n)) comparisons.
   185  //
   186  // cmp is a struct of the form {T: _, x: T, y: T, less: bool}, where
   187  // less should reflect x < y.
   188  //
   189  // Example:
   190  //
   191  //	Sort([2, 3, 1], list.Ascending)
   192  //
   193  //	Sort([{a: 2}, {a: 3}, {a: 1}], {x: {}, y: {}, less: x.a < y.a})
   194  func Sort(list []cue.Value, cmp cue.Value) (sorted []cue.Value, err error) {
   195  	s := makeValueSorter(list, cmp)
   196  
   197  	// The input slice is already a copy and that we can modify it safely.
   198  	sort.Stable(&s)
   199  	return s.ret()
   200  }
   201  
   202  func getArc(ctx *adt.OpContext, v *adt.Vertex, s string) *adt.Vertex {
   203  	f := ctx.StringLabel(s)
   204  	arc, _ := v.GetArc(ctx, f, 0)
   205  	return arc
   206  }
   207  
   208  // Deprecated: use [Sort], which is always stable
   209  func SortStable(list []cue.Value, cmp cue.Value) (sorted []cue.Value, err error) {
   210  	s := makeValueSorter(list, cmp)
   211  	sort.Stable(&s)
   212  	return s.ret()
   213  }
   214  
   215  // SortStrings sorts a list of strings in increasing order.
   216  func SortStrings(a []string) []string {
   217  	slices.Sort(a)
   218  	return a
   219  }
   220  
   221  // IsSorted tests whether a list is sorted.
   222  //
   223  // See Sort for an example comparator.
   224  func IsSorted(list []cue.Value, cmp cue.Value) bool {
   225  	s := makeValueSorter(list, cmp)
   226  	return sort.IsSorted(&s)
   227  }
   228  
   229  // IsSortedStrings tests whether a list is a sorted lists of strings.
   230  func IsSortedStrings(a []string) bool {
   231  	return slices.IsSorted(a)
   232  }