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 }