From 7b161c64b1c8d5a77942a10757f835f36a67e5cf Mon Sep 17 00:00:00 2001 From: Chris Novakovic Date: Wed, 11 Jun 2025 11:33:31 +0100 Subject: [PATCH] Implement `reverse` parameter for `sorted` built-in function This is optional, and behaves similarly to the `reverse` parameter in Python's `sorted` built-in function: if the value is `True`, the list is sorted in reverse order. This is a more efficient equivalent to `reversed(sorted(...))`. --- docs/lexicon.html | 6 ++++-- rules/builtins.build_defs | 2 +- src/parse/asp/builtins.go | 10 ++++++++-- src/parse/asp/interpreter_test.go | 2 ++ src/parse/asp/test_data/interpreter/sorted.build | 4 ++++ 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/lexicon.html b/docs/lexicon.html index 3090834c8b..49ee4c2660 100644 --- a/docs/lexicon.html +++ b/docs/lexicon.html @@ -159,11 +159,13 @@

sorted(seq[,key])][,reverse]) - returns a copy of seq with the contents sorted. key is a function that is applied - to each item before comparison. + to each item before comparison. If reverse is true, the items are sorted in reverse order.
  • diff --git a/rules/builtins.build_defs b/rules/builtins.build_defs index 5488f9f105..a8c9fcfd2e 100644 --- a/rules/builtins.build_defs +++ b/rules/builtins.build_defs @@ -125,7 +125,7 @@ def glob(include:list|str, exclude:list|str&excludes=[], hidden:bool=CONFIG.BAZE def package(): pass -def sorted(seq:list, key:function=None) -> list: +def sorted(seq:list, key:function=None, reverse:bool=False) -> list: pass def reversed(seq:list) -> list: diff --git a/src/parse/asp/builtins.go b/src/parse/asp/builtins.go index 8ce3e24680..69c2564c17 100644 --- a/src/parse/asp/builtins.go +++ b/src/parse/asp/builtins.go @@ -800,11 +800,17 @@ func dictCopy(s *scope, args []pyObject) pyObject { func sorted(s *scope, args []pyObject) pyObject { l, isList := args[0].(pyList) key, isFunc := args[1].(*pyFunc) + reverse, isBool := args[2].(pyBool) s.Assert(isList, "Argument seq must be a list, not %s", args[0].Type()) + s.Assert(isBool, "Argument reverse must be a bool, not %s", args[2].Type()) + order := LessThan + if reverse { + order = GreaterThan + } l = l[:] if key == nil { sort.Slice(l, func(i, j int) bool { - return s.operator(LessThan, l[i], l[j]).IsTruthy() + return s.operator(order, l[i], l[j]).IsTruthy() }) } else { s.Assert(isFunc, "Argument key must be callable, not %s", args[1].Type()) @@ -819,7 +825,7 @@ func sorted(s *scope, args []pyObject) pyObject { Value: Expression{optimised: &optimisedExpression{Constant: l[j]}}, }}, }) - return s.operator(LessThan, iKey, jKey).IsTruthy() + return s.operator(order, iKey, jKey).IsTruthy() }) } return l diff --git a/src/parse/asp/interpreter_test.go b/src/parse/asp/interpreter_test.go index 6a8faa3c11..d94fce0f6c 100644 --- a/src/parse/asp/interpreter_test.go +++ b/src/parse/asp/interpreter_test.go @@ -172,6 +172,8 @@ func TestInterpreterSorting(t *testing.T) { // N.B. sorted() sorts in-place, unlike Python's one. We may change that later. assert.Equal(t, pyList{pyInt(1), pyInt(2), pyInt(3)}, s.Lookup("r1")) assert.Equal(t, pyList{pyString("ONE"), pyString("THREE"), pyString("two")}, s.Lookup("r2")) + assert.Equal(t, pyList{pyInt(4), pyInt(3), pyInt(2), pyInt(1)}, s.Lookup("r3")) + assert.Equal(t, pyList{pyInt(1), pyInt(2), pyInt(3), pyInt(4)}, s.Lookup("r4")) } func TestReversed(t *testing.T) { diff --git a/src/parse/asp/test_data/interpreter/sorted.build b/src/parse/asp/test_data/interpreter/sorted.build index 29e2d58f33..4125a388cb 100644 --- a/src/parse/asp/test_data/interpreter/sorted.build +++ b/src/parse/asp/test_data/interpreter/sorted.build @@ -4,3 +4,7 @@ r1 = sorted(l1) # key parameter test l2 = ["ONE", "two", "THREE"] r2 = sorted(l2, key=lambda s: s.lower()) + +# reverse parameter test +r3 = sorted([2, 4, 1, 3], reverse=True) +r4 = sorted([2, 4, 1, 3], reverse=False)