Merge pull request #272744 from hercules-ci/lib-lists-sortOn

`lib.lists.sortOn`: init
This commit is contained in:
Silvan Mosberger 2023-12-08 23:11:05 +01:00 committed by GitHub
commit bf67c02b1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 71 additions and 6 deletions

View File

@ -5,7 +5,7 @@ let
intersectAttrs;
inherit (lib)
functionArgs isFunction mirrorFunctionArgs isAttrs setFunctionArgs
optionalAttrs attrNames filter elemAt concatStringsSep sort take length
optionalAttrs attrNames filter elemAt concatStringsSep sortOn take length
filterAttrs optionalString flip pathIsDirectory head pipe isDerivation listToAttrs
mapAttrs seq flatten deepSeq warnIf isInOldestRelease extends
;
@ -174,7 +174,7 @@ rec {
# levenshteinAtMost is only fast for 2 or less.
(filter (levenshteinAtMost 2 arg))
# Put strings with shorter distance first
(sort (x: y: levenshtein x arg < levenshtein y arg))
(sortOn (levenshtein arg))
# Only take the first couple results
(take 3)
# Quote all entries

View File

@ -91,7 +91,7 @@ let
inherit (self.lists) singleton forEach foldr fold foldl foldl' imap0 imap1
concatMap flatten remove findSingle findFirst any all count
optional optionals toList range replicate partition zipListsWith zipLists
reverseList listDfs toposort sort naturalSort compareLists take
reverseList listDfs toposort sort sortOn naturalSort compareLists take
drop sublist last init crossLists unique allUnique intersectLists
subtractLists mutuallyExclusive groupBy groupBy';
inherit (self.strings) concatStrings concatMapStrings concatImapStrings

View File

@ -4,6 +4,7 @@ let
inherit (lib.strings) toInt;
inherit (lib.trivial) compare min id;
inherit (lib.attrsets) mapAttrs;
inherit (lib.lists) sort;
in
rec {
@ -591,9 +592,15 @@ rec {
the second argument. The returned list is sorted in an increasing
order. The implementation does a quick-sort.
See also [`sortOn`](#function-library-lib.lists.sortOn), which applies the
default comparison on a function-derived property, and may be more efficient.
Example:
sort (a: b: a < b) [ 5 3 7 ]
sort (p: q: p < q) [ 5 3 7 ]
=> [ 3 5 7 ]
Type:
sort :: (a -> a -> Bool) -> [a] -> [a]
*/
sort = builtins.sort or (
strictLess: list:
@ -612,6 +619,42 @@ rec {
if len < 2 then list
else (sort strictLess pivot.left) ++ [ first ] ++ (sort strictLess pivot.right));
/*
Sort a list based on the default comparison of a derived property `b`.
The items are returned in `b`-increasing order.
**Performance**:
The passed function `f` is only evaluated once per item,
unlike an unprepared [`sort`](#function-library-lib.lists.sort) using
`f p < f q`.
**Laws**:
```nix
sortOn f == sort (p: q: f p < f q)
```
Example:
sortOn stringLength [ "aa" "b" "cccc" ]
=> [ "b" "aa" "cccc" ]
Type:
sortOn :: (a -> b) -> [a] -> [a], for comparable b
*/
sortOn = f: list:
let
# Heterogenous list as pair may be ugly, but requires minimal allocations.
pairs = map (x: [(f x) x]) list;
in
map
(x: builtins.elemAt x 1)
(sort
# Compare the first element of the pairs
# Do not factor out the `<`, to avoid calls in hot code; duplicate instead.
(a: b: head a < head b)
pairs);
/* Compare two lists element-by-element.
Example:

View File

@ -650,6 +650,28 @@ runTests {
expected = [2 30 40 42];
};
testSortOn = {
expr = sortOn stringLength [ "aa" "b" "cccc" ];
expected = [ "b" "aa" "cccc" ];
};
testSortOnEmpty = {
expr = sortOn (throw "nope") [ ];
expected = [ ];
};
testSortOnIncomparable = {
expr =
map
(x: x.f x.ok)
(sortOn (x: x.ok) [
{ ok = 1; f = x: x; }
{ ok = 3; f = x: x + 3; }
{ ok = 2; f = x: x; }
]);
expected = [ 1 2 6 ];
};
testReplicate = {
expr = replicate 3 "a";
expected = ["a" "a" "a"];

View File

@ -13,7 +13,7 @@ let
mkIf
mkOption
optionalString
sort
sortOn
types
;
@ -37,7 +37,7 @@ let
genConfig = set:
let
pairs = mapAttrsToList (name: value: { inherit name value; }) set;
sortedPairs = sort (a: b: prioOf a < prioOf b) pairs;
sortedPairs = sortOn prioOf pairs;
in
concatMap genPair sortedPairs;
genSection = sec: secName: value: