mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-12-22 13:43:22 +00:00
573 lines
16 KiB
Nix
573 lines
16 KiB
Nix
# General list operations.
|
||
{ lib }:
|
||
with lib.trivial;
|
||
let
|
||
inherit (lib.strings) toInt;
|
||
in
|
||
rec {
|
||
|
||
inherit (builtins) head tail length isList elemAt concatLists filter elem genList;
|
||
|
||
/* Create a list consisting of a single element. `singleton x' is
|
||
sometimes more convenient with respect to indentation than `[x]'
|
||
when x spans multiple lines.
|
||
|
||
Example:
|
||
singleton "foo"
|
||
=> [ "foo" ]
|
||
*/
|
||
singleton = x: [x];
|
||
|
||
/* “right fold” a binary function `op' between successive elements of
|
||
`list' with `nul' as the starting value, i.e.,
|
||
`foldr op nul [x_1 x_2 ... x_n] == op x_1 (op x_2 ... (op x_n nul))'.
|
||
Type:
|
||
foldr :: (a -> b -> b) -> b -> [a] -> b
|
||
|
||
Example:
|
||
concat = foldr (a: b: a + b) "z"
|
||
concat [ "a" "b" "c" ]
|
||
=> "abcz"
|
||
# different types
|
||
strange = foldr (int: str: toString (int + 1) + str) "a"
|
||
strange [ 1 2 3 4 ]
|
||
=> "2345a"
|
||
*/
|
||
foldr = op: nul: list:
|
||
let
|
||
len = length list;
|
||
fold' = n:
|
||
if n == len
|
||
then nul
|
||
else op (elemAt list n) (fold' (n + 1));
|
||
in fold' 0;
|
||
|
||
/* `fold' is an alias of `foldr' for historic reasons */
|
||
# FIXME(Profpatsch): deprecate?
|
||
fold = foldr;
|
||
|
||
|
||
/* “left fold”, like `foldr', but from the left:
|
||
`foldl op nul [x_1 x_2 ... x_n] == op (... (op (op nul x_1) x_2) ... x_n)`.
|
||
|
||
Type:
|
||
foldl :: (b -> a -> b) -> b -> [a] -> b
|
||
|
||
Example:
|
||
lconcat = foldl (a: b: a + b) "z"
|
||
lconcat [ "a" "b" "c" ]
|
||
=> "zabc"
|
||
# different types
|
||
lstrange = foldl (str: int: str + toString (int + 1)) ""
|
||
strange [ 1 2 3 4 ]
|
||
=> "a2345"
|
||
*/
|
||
foldl = op: nul: list:
|
||
let
|
||
foldl' = n:
|
||
if n == -1
|
||
then nul
|
||
else op (foldl' (n - 1)) (elemAt list n);
|
||
in foldl' (length list - 1);
|
||
|
||
/* Strict version of `foldl'.
|
||
|
||
The difference is that evaluation is forced upon access. Usually used
|
||
with small whole results (in contract with lazily-generated list or large
|
||
lists where only a part is consumed.)
|
||
*/
|
||
foldl' = builtins.foldl' or foldl;
|
||
|
||
/* Map with index starting from 0
|
||
|
||
Example:
|
||
imap0 (i: v: "${v}-${toString i}") ["a" "b"]
|
||
=> [ "a-0" "b-1" ]
|
||
*/
|
||
imap0 = f: list: genList (n: f n (elemAt list n)) (length list);
|
||
|
||
/* Map with index starting from 1
|
||
|
||
Example:
|
||
imap1 (i: v: "${v}-${toString i}") ["a" "b"]
|
||
=> [ "a-1" "b-2" ]
|
||
*/
|
||
imap1 = f: list: genList (n: f (n + 1) (elemAt list n)) (length list);
|
||
|
||
/* Map and concatenate the result.
|
||
|
||
Example:
|
||
concatMap (x: [x] ++ ["z"]) ["a" "b"]
|
||
=> [ "a" "z" "b" "z" ]
|
||
*/
|
||
concatMap = builtins.concatMap or (f: list: concatLists (map f list));
|
||
|
||
/* Flatten the argument into a single list; that is, nested lists are
|
||
spliced into the top-level lists.
|
||
|
||
Example:
|
||
flatten [1 [2 [3] 4] 5]
|
||
=> [1 2 3 4 5]
|
||
flatten 1
|
||
=> [1]
|
||
*/
|
||
flatten = x:
|
||
if isList x
|
||
then concatMap (y: flatten y) x
|
||
else [x];
|
||
|
||
/* Remove elements equal to 'e' from a list. Useful for buildInputs.
|
||
|
||
Example:
|
||
remove 3 [ 1 3 4 3 ]
|
||
=> [ 1 4 ]
|
||
*/
|
||
remove = e: filter (x: x != e);
|
||
|
||
/* Find the sole element in the list matching the specified
|
||
predicate, returns `default' if no such element exists, or
|
||
`multiple' if there are multiple matching elements.
|
||
|
||
Example:
|
||
findSingle (x: x == 3) "none" "multiple" [ 1 3 3 ]
|
||
=> "multiple"
|
||
findSingle (x: x == 3) "none" "multiple" [ 1 3 ]
|
||
=> 3
|
||
findSingle (x: x == 3) "none" "multiple" [ 1 9 ]
|
||
=> "none"
|
||
*/
|
||
findSingle = pred: default: multiple: list:
|
||
let found = filter pred list; len = length found;
|
||
in if len == 0 then default
|
||
else if len != 1 then multiple
|
||
else head found;
|
||
|
||
/* Find the first element in the list matching the specified
|
||
predicate or returns `default' if no such element exists.
|
||
|
||
Example:
|
||
findFirst (x: x > 3) 7 [ 1 6 4 ]
|
||
=> 6
|
||
findFirst (x: x > 9) 7 [ 1 6 4 ]
|
||
=> 7
|
||
*/
|
||
findFirst = pred: default: list:
|
||
let found = filter pred list;
|
||
in if found == [] then default else head found;
|
||
|
||
/* Return true iff function `pred' returns true for at least element
|
||
of `list'.
|
||
|
||
Example:
|
||
any isString [ 1 "a" { } ]
|
||
=> true
|
||
any isString [ 1 { } ]
|
||
=> false
|
||
*/
|
||
any = builtins.any or (pred: foldr (x: y: if pred x then true else y) false);
|
||
|
||
/* Return true iff function `pred' returns true for all elements of
|
||
`list'.
|
||
|
||
Example:
|
||
all (x: x < 3) [ 1 2 ]
|
||
=> true
|
||
all (x: x < 3) [ 1 2 3 ]
|
||
=> false
|
||
*/
|
||
all = builtins.all or (pred: foldr (x: y: if pred x then y else false) true);
|
||
|
||
/* Count how many times function `pred' returns true for the elements
|
||
of `list'.
|
||
|
||
Example:
|
||
count (x: x == 3) [ 3 2 3 4 6 ]
|
||
=> 2
|
||
*/
|
||
count = pred: foldl' (c: x: if pred x then c + 1 else c) 0;
|
||
|
||
/* Return a singleton list or an empty list, depending on a boolean
|
||
value. Useful when building lists with optional elements
|
||
(e.g. `++ optional (system == "i686-linux") flashplayer').
|
||
|
||
Example:
|
||
optional true "foo"
|
||
=> [ "foo" ]
|
||
optional false "foo"
|
||
=> [ ]
|
||
*/
|
||
optional = cond: elem: if cond then [elem] else [];
|
||
|
||
/* Return a list or an empty list, depending on a boolean value.
|
||
|
||
Example:
|
||
optionals true [ 2 3 ]
|
||
=> [ 2 3 ]
|
||
optionals false [ 2 3 ]
|
||
=> [ ]
|
||
*/
|
||
optionals = cond: elems: if cond then elems else [];
|
||
|
||
|
||
/* If argument is a list, return it; else, wrap it in a singleton
|
||
list. If you're using this, you should almost certainly
|
||
reconsider if there isn't a more "well-typed" approach.
|
||
|
||
Example:
|
||
toList [ 1 2 ]
|
||
=> [ 1 2 ]
|
||
toList "hi"
|
||
=> [ "hi "]
|
||
*/
|
||
toList = x: if isList x then x else [x];
|
||
|
||
/* Return a list of integers from `first' up to and including `last'.
|
||
|
||
Example:
|
||
range 2 4
|
||
=> [ 2 3 4 ]
|
||
range 3 2
|
||
=> [ ]
|
||
*/
|
||
range = first: last:
|
||
if first > last then
|
||
[]
|
||
else
|
||
genList (n: first + n) (last - first + 1);
|
||
|
||
/* Splits the elements of a list in two lists, `right' and
|
||
`wrong', depending on the evaluation of a predicate.
|
||
|
||
Example:
|
||
partition (x: x > 2) [ 5 1 2 3 4 ]
|
||
=> { right = [ 5 3 4 ]; wrong = [ 1 2 ]; }
|
||
*/
|
||
partition = builtins.partition or (pred:
|
||
foldr (h: t:
|
||
if pred h
|
||
then { right = [h] ++ t.right; wrong = t.wrong; }
|
||
else { right = t.right; wrong = [h] ++ t.wrong; }
|
||
) { right = []; wrong = []; });
|
||
|
||
/* Splits the elements of a list into many lists, using the return value of a predicate.
|
||
Predicate should return a string which becomes keys of attrset `groupBy' returns.
|
||
|
||
`groupBy'' allows to customise the combining function and initial value
|
||
|
||
Example:
|
||
groupBy (x: boolToString (x > 2)) [ 5 1 2 3 4 ]
|
||
=> { true = [ 5 3 4 ]; false = [ 1 2 ]; }
|
||
groupBy (x: x.name) [ {name = "icewm"; script = "icewm &";}
|
||
{name = "xfce"; script = "xfce4-session &";}
|
||
{name = "icewm"; script = "icewmbg &";}
|
||
{name = "mate"; script = "gnome-session &";}
|
||
]
|
||
=> { icewm = [ { name = "icewm"; script = "icewm &"; }
|
||
{ name = "icewm"; script = "icewmbg &"; } ];
|
||
mate = [ { name = "mate"; script = "gnome-session &"; } ];
|
||
xfce = [ { name = "xfce"; script = "xfce4-session &"; } ];
|
||
}
|
||
|
||
|
||
groupBy' allows to customise the combining function and initial value
|
||
|
||
Example:
|
||
groupBy' builtins.add 0 (x: boolToString (x > 2)) [ 5 1 2 3 4 ]
|
||
=> { true = 12; false = 3; }
|
||
*/
|
||
groupBy' = op: nul: pred: lst:
|
||
foldl' (r: e:
|
||
let
|
||
key = pred e;
|
||
in
|
||
r // { ${key} = op (r.${key} or nul) e; }
|
||
) {} lst;
|
||
|
||
groupBy = groupBy' (sum: e: sum ++ [e]) [];
|
||
|
||
/* Merges two lists of the same size together. If the sizes aren't the same
|
||
the merging stops at the shortest. How both lists are merged is defined
|
||
by the first argument.
|
||
|
||
Example:
|
||
zipListsWith (a: b: a + b) ["h" "l"] ["e" "o"]
|
||
=> ["he" "lo"]
|
||
*/
|
||
zipListsWith = f: fst: snd:
|
||
genList
|
||
(n: f (elemAt fst n) (elemAt snd n)) (min (length fst) (length snd));
|
||
|
||
/* Merges two lists of the same size together. If the sizes aren't the same
|
||
the merging stops at the shortest.
|
||
|
||
Example:
|
||
zipLists [ 1 2 ] [ "a" "b" ]
|
||
=> [ { fst = 1; snd = "a"; } { fst = 2; snd = "b"; } ]
|
||
*/
|
||
zipLists = zipListsWith (fst: snd: { inherit fst snd; });
|
||
|
||
/* Reverse the order of the elements of a list.
|
||
|
||
Example:
|
||
|
||
reverseList [ "b" "o" "j" ]
|
||
=> [ "j" "o" "b" ]
|
||
*/
|
||
reverseList = xs:
|
||
let l = length xs; in genList (n: elemAt xs (l - n - 1)) l;
|
||
|
||
/* Depth-First Search (DFS) for lists `list != []`.
|
||
|
||
`before a b == true` means that `b` depends on `a` (there's an
|
||
edge from `b` to `a`).
|
||
|
||
Examples:
|
||
|
||
listDfs true hasPrefix [ "/home/user" "other" "/" "/home" ]
|
||
== { minimal = "/"; # minimal element
|
||
visited = [ "/home/user" ]; # seen elements (in reverse order)
|
||
rest = [ "/home" "other" ]; # everything else
|
||
}
|
||
|
||
listDfs true hasPrefix [ "/home/user" "other" "/" "/home" "/" ]
|
||
== { cycle = "/"; # cycle encountered at this element
|
||
loops = [ "/" ]; # and continues to these elements
|
||
visited = [ "/" "/home/user" ]; # elements leading to the cycle (in reverse order)
|
||
rest = [ "/home" "other" ]; # everything else
|
||
|
||
*/
|
||
|
||
listDfs = stopOnCycles: before: list:
|
||
let
|
||
dfs' = us: visited: rest:
|
||
let
|
||
c = filter (x: before x us) visited;
|
||
b = partition (x: before x us) rest;
|
||
in if stopOnCycles && (length c > 0)
|
||
then { cycle = us; loops = c; inherit visited rest; }
|
||
else if length b.right == 0
|
||
then # nothing is before us
|
||
{ minimal = us; inherit visited rest; }
|
||
else # grab the first one before us and continue
|
||
dfs' (head b.right)
|
||
([ us ] ++ visited)
|
||
(tail b.right ++ b.wrong);
|
||
in dfs' (head list) [] (tail list);
|
||
|
||
/* Sort a list based on a partial ordering using DFS. This
|
||
implementation is O(N^2), if your ordering is linear, use `sort`
|
||
instead.
|
||
|
||
`before a b == true` means that `b` should be after `a`
|
||
in the result.
|
||
|
||
Examples:
|
||
|
||
toposort hasPrefix [ "/home/user" "other" "/" "/home" ]
|
||
== { result = [ "/" "/home" "/home/user" "other" ]; }
|
||
|
||
toposort hasPrefix [ "/home/user" "other" "/" "/home" "/" ]
|
||
== { cycle = [ "/home/user" "/" "/" ]; # path leading to a cycle
|
||
loops = [ "/" ]; } # loops back to these elements
|
||
|
||
toposort hasPrefix [ "other" "/home/user" "/home" "/" ]
|
||
== { result = [ "other" "/" "/home" "/home/user" ]; }
|
||
|
||
toposort (a: b: a < b) [ 3 2 1 ] == { result = [ 1 2 3 ]; }
|
||
|
||
*/
|
||
|
||
toposort = before: list:
|
||
let
|
||
dfsthis = listDfs true before list;
|
||
toporest = toposort before (dfsthis.visited ++ dfsthis.rest);
|
||
in
|
||
if length list < 2
|
||
then # finish
|
||
{ result = list; }
|
||
else if dfsthis ? "cycle"
|
||
then # there's a cycle, starting from the current vertex, return it
|
||
{ cycle = reverseList ([ dfsthis.cycle ] ++ dfsthis.visited);
|
||
inherit (dfsthis) loops; }
|
||
else if toporest ? "cycle"
|
||
then # there's a cycle somewhere else in the graph, return it
|
||
toporest
|
||
# Slow, but short. Can be made a bit faster with an explicit stack.
|
||
else # there are no cycles
|
||
{ result = [ dfsthis.minimal ] ++ toporest.result; };
|
||
|
||
/* Sort a list based on a comparator function which compares two
|
||
elements and returns true if the first argument is strictly below
|
||
the second argument. The returned list is sorted in an increasing
|
||
order. The implementation does a quick-sort.
|
||
|
||
Example:
|
||
sort (a: b: a < b) [ 5 3 7 ]
|
||
=> [ 3 5 7 ]
|
||
*/
|
||
sort = builtins.sort or (
|
||
strictLess: list:
|
||
let
|
||
len = length list;
|
||
first = head list;
|
||
pivot' = n: acc@{ left, right }: let el = elemAt list n; next = pivot' (n + 1); in
|
||
if n == len
|
||
then acc
|
||
else if strictLess first el
|
||
then next { inherit left; right = [ el ] ++ right; }
|
||
else
|
||
next { left = [ el ] ++ left; inherit right; };
|
||
pivot = pivot' 1 { left = []; right = []; };
|
||
in
|
||
if len < 2 then list
|
||
else (sort strictLess pivot.left) ++ [ first ] ++ (sort strictLess pivot.right));
|
||
|
||
/* Compare two lists element-by-element.
|
||
|
||
Example:
|
||
compareLists compare [] []
|
||
=> 0
|
||
compareLists compare [] [ "a" ]
|
||
=> -1
|
||
compareLists compare [ "a" ] []
|
||
=> 1
|
||
compareLists compare [ "a" "b" ] [ "a" "c" ]
|
||
=> 1
|
||
*/
|
||
compareLists = cmp: a: b:
|
||
if a == []
|
||
then if b == []
|
||
then 0
|
||
else -1
|
||
else if b == []
|
||
then 1
|
||
else let rel = cmp (head a) (head b); in
|
||
if rel == 0
|
||
then compareLists cmp (tail a) (tail b)
|
||
else rel;
|
||
|
||
/* Sort list using "Natural sorting".
|
||
Numeric portions of strings are sorted in numeric order.
|
||
|
||
Example:
|
||
naturalSort ["disk11" "disk8" "disk100" "disk9"]
|
||
=> ["disk8" "disk9" "disk11" "disk100"]
|
||
naturalSort ["10.46.133.149" "10.5.16.62" "10.54.16.25"]
|
||
=> ["10.5.16.62" "10.46.133.149" "10.54.16.25"]
|
||
naturalSort ["v0.2" "v0.15" "v0.0.9"]
|
||
=> [ "v0.0.9" "v0.2" "v0.15" ]
|
||
*/
|
||
naturalSort = lst:
|
||
let
|
||
vectorise = s: map (x: if isList x then toInt (head x) else x) (builtins.split "(0|[1-9][0-9]*)" s);
|
||
prepared = map (x: [ (vectorise x) x ]) lst; # remember vectorised version for O(n) regex splits
|
||
less = a: b: (compareLists compare (head a) (head b)) < 0;
|
||
in
|
||
map (x: elemAt x 1) (sort less prepared);
|
||
|
||
/* Return the first (at most) N elements of a list.
|
||
|
||
Example:
|
||
take 2 [ "a" "b" "c" "d" ]
|
||
=> [ "a" "b" ]
|
||
take 2 [ ]
|
||
=> [ ]
|
||
*/
|
||
take = count: sublist 0 count;
|
||
|
||
/* Remove the first (at most) N elements of a list.
|
||
|
||
Example:
|
||
drop 2 [ "a" "b" "c" "d" ]
|
||
=> [ "c" "d" ]
|
||
drop 2 [ ]
|
||
=> [ ]
|
||
*/
|
||
drop = count: list: sublist count (length list) list;
|
||
|
||
/* Return a list consisting of at most ‘count’ elements of ‘list’,
|
||
starting at index ‘start’.
|
||
|
||
Example:
|
||
sublist 1 3 [ "a" "b" "c" "d" "e" ]
|
||
=> [ "b" "c" "d" ]
|
||
sublist 1 3 [ ]
|
||
=> [ ]
|
||
*/
|
||
sublist = start: count: list:
|
||
let len = length list; in
|
||
genList
|
||
(n: elemAt list (n + start))
|
||
(if start >= len then 0
|
||
else if start + count > len then len - start
|
||
else count);
|
||
|
||
/* Return the last element of a list.
|
||
|
||
Example:
|
||
last [ 1 2 3 ]
|
||
=> 3
|
||
*/
|
||
last = list:
|
||
assert list != []; elemAt list (length list - 1);
|
||
|
||
/* Return all elements but the last
|
||
|
||
Example:
|
||
init [ 1 2 3 ]
|
||
=> [ 1 2 ]
|
||
*/
|
||
init = list: assert list != []; take (length list - 1) list;
|
||
|
||
|
||
/* return the image of the cross product of some lists by a function
|
||
|
||
Example:
|
||
crossLists (x:y: "${toString x}${toString y}") [[1 2] [3 4]]
|
||
=> [ "13" "14" "23" "24" ]
|
||
*/
|
||
crossLists = f: foldl (fs: args: concatMap (f: map f args) fs) [f];
|
||
|
||
|
||
/* Remove duplicate elements from the list. O(n^2) complexity.
|
||
|
||
Example:
|
||
|
||
unique [ 3 2 3 4 ]
|
||
=> [ 3 2 4 ]
|
||
*/
|
||
unique = list:
|
||
if list == [] then
|
||
[]
|
||
else
|
||
let
|
||
x = head list;
|
||
xs = unique (drop 1 list);
|
||
in [x] ++ remove x xs;
|
||
|
||
/* Intersects list 'e' and another list. O(nm) complexity.
|
||
|
||
Example:
|
||
intersectLists [ 1 2 3 ] [ 6 3 2 ]
|
||
=> [ 3 2 ]
|
||
*/
|
||
intersectLists = e: filter (x: elem x e);
|
||
|
||
/* Subtracts list 'e' from another list. O(nm) complexity.
|
||
|
||
Example:
|
||
subtractLists [ 3 2 ] [ 1 2 3 4 5 3 ]
|
||
=> [ 1 4 5 ]
|
||
*/
|
||
subtractLists = e: filter (x: !(elem x e));
|
||
|
||
/* Test if two lists have no common element.
|
||
It should be slightly more efficient than (intersectLists a b == [])
|
||
*/
|
||
mutuallyExclusive = a: b:
|
||
(builtins.length a) == 0 ||
|
||
(!(builtins.elem (builtins.head a) b) &&
|
||
mutuallyExclusive (builtins.tail a) b);
|
||
|
||
}
|