Commit 67cc7864 authored by Robert Hensing's avatar Robert Hensing
Browse files

lib.sortOn: init

A more efficient sort in some cases, and often convenient.

This exposes `lib.lists.sortOn` immediately on `lib`, because it is
a sibling of `sort`, which is already present there.
Omitting it would lead to more confusion, and worse outcomes.
There's no confusion about the types `sort` or `sortOn` operate on.

Haskell agrees about the type for `sortOn`, and it is in its `base`.
parent b3caa5f5
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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
+40 −0
Original line number Diff line number Diff line
@@ -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,6 +592,9 @@ 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 ]
       => [ 3 5 7 ]
@@ -612,6 +616,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:
+22 −0
Original line number Diff line number Diff line
@@ -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"];