1 # General list operations.
5 inherit (lib.strings) toInt;
6 inherit (lib.trivial) compare min;
7 inherit (lib.attrsets) mapAttrs;
11 inherit (builtins) head tail length isList elemAt concatLists filter elem genList map;
13 /* Create a list consisting of a single element. `singleton x` is
14 sometimes more convenient with respect to indentation than `[x]`
15 when x spans multiple lines.
17 Type: singleton :: a -> [a]
25 /* Apply the function to each element in the list. Same as `map`, but arguments
28 Type: forEach :: [a] -> (a -> b) -> [b]
36 forEach = xs: f: map f xs;
38 /* “right fold” a binary function `op` between successive elements of
39 `list` with `nul` as the starting value, i.e.,
40 `foldr op nul [x_1 x_2 ... x_n] == op x_1 (op x_2 ... (op x_n nul))`.
42 Type: foldr :: (a -> b -> b) -> b -> [a] -> b
45 concat = foldr (a: b: a + b) "z"
46 concat [ "a" "b" "c" ]
49 strange = foldr (int: str: toString (int + 1) + str) "a"
53 foldr = op: nul: list:
59 else op (elemAt list n) (fold' (n + 1));
62 /* `fold` is an alias of `foldr` for historic reasons */
63 # FIXME(Profpatsch): deprecate?
67 /* “left fold”, like `foldr`, but from the left:
68 `foldl op nul [x_1 x_2 ... x_n] == op (... (op (op nul x_1) x_2) ... x_n)`.
70 Type: foldl :: (b -> a -> b) -> b -> [a] -> b
73 lconcat = foldl (a: b: a + b) "z"
74 lconcat [ "a" "b" "c" ]
77 lstrange = foldl (str: int: str + toString (int + 1)) "a"
81 foldl = op: nul: list:
86 else op (foldl' (n - 1)) (elemAt list n);
87 in foldl' (length list - 1);
89 /* Strict version of `foldl`.
91 The difference is that evaluation is forced upon access. Usually used
92 with small whole results (in contrast with lazily-generated list or large
93 lists where only a part is consumed.)
95 Type: foldl' :: (b -> a -> b) -> b -> [a] -> b
97 foldl' = builtins.foldl' or foldl;
99 /* Map with index starting from 0
101 Type: imap0 :: (int -> a -> b) -> [a] -> [b]
104 imap0 (i: v: "${v}-${toString i}") ["a" "b"]
107 imap0 = f: list: genList (n: f n (elemAt list n)) (length list);
109 /* Map with index starting from 1
111 Type: imap1 :: (int -> a -> b) -> [a] -> [b]
114 imap1 (i: v: "${v}-${toString i}") ["a" "b"]
117 imap1 = f: list: genList (n: f (n + 1) (elemAt list n)) (length list);
119 /* Map and concatenate the result.
121 Type: concatMap :: (a -> [b]) -> [a] -> [b]
124 concatMap (x: [x] ++ ["z"]) ["a" "b"]
125 => [ "a" "z" "b" "z" ]
127 concatMap = builtins.concatMap or (f: list: concatLists (map f list));
129 /* Flatten the argument into a single list; that is, nested lists are
130 spliced into the top-level lists.
133 flatten [1 [2 [3] 4] 5]
140 then concatMap (y: flatten y) x
143 /* Remove elements equal to 'e' from a list. Useful for buildInputs.
145 Type: remove :: a -> [a] -> [a]
152 # Element to remove from the list
153 e: filter (x: x != e);
155 /* Find the sole element in the list matching the specified
156 predicate, returns `default` if no such element exists, or
157 `multiple` if there are multiple matching elements.
159 Type: findSingle :: (a -> bool) -> a -> a -> [a] -> a
162 findSingle (x: x == 3) "none" "multiple" [ 1 3 3 ]
164 findSingle (x: x == 3) "none" "multiple" [ 1 3 ]
166 findSingle (x: x == 3) "none" "multiple" [ 1 9 ]
172 # Default value to return if element was not found.
174 # Default value to return if more than one element was found
178 let found = filter pred list; len = length found;
179 in if len == 0 then default
180 else if len != 1 then multiple
183 /* Find the first element in the list matching the specified
184 predicate or return `default` if no such element exists.
186 Type: findFirst :: (a -> bool) -> a -> [a] -> a
189 findFirst (x: x > 3) 7 [ 1 6 4 ]
191 findFirst (x: x > 9) 7 [ 1 6 4 ]
197 # Default value to return
201 let found = filter pred list;
202 in if found == [] then default else head found;
204 /* Return true if function `pred` returns true for at least one
207 Type: any :: (a -> bool) -> [a] -> bool
210 any isString [ 1 "a" { } ]
212 any isString [ 1 { } ]
215 any = builtins.any or (pred: foldr (x: y: if pred x then true else y) false);
217 /* Return true if function `pred` returns true for all elements of
220 Type: all :: (a -> bool) -> [a] -> bool
223 all (x: x < 3) [ 1 2 ]
225 all (x: x < 3) [ 1 2 3 ]
228 all = builtins.all or (pred: foldr (x: y: if pred x then y else false) true);
230 /* Count how many elements of `list` match the supplied predicate
233 Type: count :: (a -> bool) -> [a] -> int
236 count (x: x == 3) [ 3 2 3 4 6 ]
241 pred: foldl' (c: x: if pred x then c + 1 else c) 0;
243 /* Return a singleton list or an empty list, depending on a boolean
244 value. Useful when building lists with optional elements
245 (e.g. `++ optional (system == "i686-linux") firefox').
247 Type: optional :: bool -> a -> [a]
255 optional = cond: elem: if cond then [elem] else [];
257 /* Return a list or an empty list, depending on a boolean value.
259 Type: optionals :: bool -> [a] -> [a]
262 optionals true [ 2 3 ]
264 optionals false [ 2 3 ]
270 # List to return if condition is true
271 elems: if cond then elems else [];
274 /* If argument is a list, return it; else, wrap it in a singleton
275 list. If you're using this, you should almost certainly
276 reconsider if there isn't a more "well-typed" approach.
284 toList = x: if isList x then x else [x];
286 /* Return a list of integers from `first' up to and including `last'.
288 Type: range :: int -> int -> [int]
297 # First integer in the range
299 # Last integer in the range
304 genList (n: first + n) (last - first + 1);
306 /* Splits the elements of a list in two lists, `right` and
307 `wrong`, depending on the evaluation of a predicate.
309 Type: (a -> bool) -> [a] -> { right :: [a], wrong :: [a] }
312 partition (x: x > 2) [ 5 1 2 3 4 ]
313 => { right = [ 5 3 4 ]; wrong = [ 1 2 ]; }
315 partition = builtins.partition or (pred:
318 then { right = [h] ++ t.right; wrong = t.wrong; }
319 else { right = t.right; wrong = [h] ++ t.wrong; }
320 ) { right = []; wrong = []; });
322 /* Splits the elements of a list into many lists, using the return value of a predicate.
323 Predicate should return a string which becomes keys of attrset `groupBy' returns.
325 `groupBy'` allows to customise the combining function and initial value
328 groupBy (x: boolToString (x > 2)) [ 5 1 2 3 4 ]
329 => { true = [ 5 3 4 ]; false = [ 1 2 ]; }
330 groupBy (x: x.name) [ {name = "icewm"; script = "icewm &";}
331 {name = "xfce"; script = "xfce4-session &";}
332 {name = "icewm"; script = "icewmbg &";}
333 {name = "mate"; script = "gnome-session &";}
335 => { icewm = [ { name = "icewm"; script = "icewm &"; }
336 { name = "icewm"; script = "icewmbg &"; } ];
337 mate = [ { name = "mate"; script = "gnome-session &"; } ];
338 xfce = [ { name = "xfce"; script = "xfce4-session &"; } ];
341 groupBy' builtins.add 0 (x: boolToString (x > 2)) [ 5 1 2 3 4 ]
342 => { true = 12; false = 3; }
344 groupBy' = op: nul: pred: lst: mapAttrs (name: foldl op nul) (groupBy pred lst);
346 groupBy = builtins.groupBy or (
351 r // { ${key} = (r.${key} or []) ++ [e]; }
354 /* Merges two lists of the same size together. If the sizes aren't the same
355 the merging stops at the shortest. How both lists are merged is defined
356 by the first argument.
358 Type: zipListsWith :: (a -> b -> c) -> [a] -> [b] -> [c]
361 zipListsWith (a: b: a + b) ["h" "l"] ["e" "o"]
365 # Function to zip elements of both lists
372 (n: f (elemAt fst n) (elemAt snd n)) (min (length fst) (length snd));
374 /* Merges two lists of the same size together. If the sizes aren't the same
375 the merging stops at the shortest.
377 Type: zipLists :: [a] -> [b] -> [{ fst :: a, snd :: b}]
380 zipLists [ 1 2 ] [ "a" "b" ]
381 => [ { fst = 1; snd = "a"; } { fst = 2; snd = "b"; } ]
383 zipLists = zipListsWith (fst: snd: { inherit fst snd; });
385 /* Reverse the order of the elements of a list.
387 Type: reverseList :: [a] -> [a]
391 reverseList [ "b" "o" "j" ]
395 let l = length xs; in genList (n: elemAt xs (l - n - 1)) l;
397 /* Depth-First Search (DFS) for lists `list != []`.
399 `before a b == true` means that `b` depends on `a` (there's an
400 edge from `b` to `a`).
403 listDfs true hasPrefix [ "/home/user" "other" "/" "/home" ]
404 == { minimal = "/"; # minimal element
405 visited = [ "/home/user" ]; # seen elements (in reverse order)
406 rest = [ "/home" "other" ]; # everything else
409 listDfs true hasPrefix [ "/home/user" "other" "/" "/home" "/" ]
410 == { cycle = "/"; # cycle encountered at this element
411 loops = [ "/" ]; # and continues to these elements
412 visited = [ "/" "/home/user" ]; # elements leading to the cycle (in reverse order)
413 rest = [ "/home" "other" ]; # everything else
416 listDfs = stopOnCycles: before: list:
418 dfs' = us: visited: rest:
420 c = filter (x: before x us) visited;
421 b = partition (x: before x us) rest;
422 in if stopOnCycles && (length c > 0)
423 then { cycle = us; loops = c; inherit visited rest; }
424 else if length b.right == 0
425 then # nothing is before us
426 { minimal = us; inherit visited rest; }
427 else # grab the first one before us and continue
430 (tail b.right ++ b.wrong);
431 in dfs' (head list) [] (tail list);
433 /* Sort a list based on a partial ordering using DFS. This
434 implementation is O(N^2), if your ordering is linear, use `sort`
437 `before a b == true` means that `b` should be after `a`
442 toposort hasPrefix [ "/home/user" "other" "/" "/home" ]
443 == { result = [ "/" "/home" "/home/user" "other" ]; }
445 toposort hasPrefix [ "/home/user" "other" "/" "/home" "/" ]
446 == { cycle = [ "/home/user" "/" "/" ]; # path leading to a cycle
447 loops = [ "/" ]; } # loops back to these elements
449 toposort hasPrefix [ "other" "/home/user" "/home" "/" ]
450 == { result = [ "other" "/" "/home" "/home/user" ]; }
452 toposort (a: b: a < b) [ 3 2 1 ] == { result = [ 1 2 3 ]; }
455 toposort = before: list:
457 dfsthis = listDfs true before list;
458 toporest = toposort before (dfsthis.visited ++ dfsthis.rest);
463 else if dfsthis ? cycle
464 then # there's a cycle, starting from the current vertex, return it
465 { cycle = reverseList ([ dfsthis.cycle ] ++ dfsthis.visited);
466 inherit (dfsthis) loops; }
467 else if toporest ? cycle
468 then # there's a cycle somewhere else in the graph, return it
470 # Slow, but short. Can be made a bit faster with an explicit stack.
471 else # there are no cycles
472 { result = [ dfsthis.minimal ] ++ toporest.result; };
474 /* Sort a list based on a comparator function which compares two
475 elements and returns true if the first argument is strictly below
476 the second argument. The returned list is sorted in an increasing
477 order. The implementation does a quick-sort.
480 sort (a: b: a < b) [ 5 3 7 ]
483 sort = builtins.sort or (
488 pivot' = n: acc@{ left, right }: let el = elemAt list n; next = pivot' (n + 1); in
491 else if strictLess first el
492 then next { inherit left; right = [ el ] ++ right; }
494 next { left = [ el ] ++ left; inherit right; };
495 pivot = pivot' 1 { left = []; right = []; };
498 else (sort strictLess pivot.left) ++ [ first ] ++ (sort strictLess pivot.right));
500 /* Compare two lists element-by-element.
503 compareLists compare [] []
505 compareLists compare [] [ "a" ]
507 compareLists compare [ "a" ] []
509 compareLists compare [ "a" "b" ] [ "a" "c" ]
512 compareLists = cmp: a: b:
519 else let rel = cmp (head a) (head b); in
521 then compareLists cmp (tail a) (tail b)
524 /* Sort list using "Natural sorting".
525 Numeric portions of strings are sorted in numeric order.
528 naturalSort ["disk11" "disk8" "disk100" "disk9"]
529 => ["disk8" "disk9" "disk11" "disk100"]
530 naturalSort ["10.46.133.149" "10.5.16.62" "10.54.16.25"]
531 => ["10.5.16.62" "10.46.133.149" "10.54.16.25"]
532 naturalSort ["v0.2" "v0.15" "v0.0.9"]
533 => [ "v0.0.9" "v0.2" "v0.15" ]
537 vectorise = s: map (x: if isList x then toInt (head x) else x) (builtins.split "(0|[1-9][0-9]*)" s);
538 prepared = map (x: [ (vectorise x) x ]) lst; # remember vectorised version for O(n) regex splits
539 less = a: b: (compareLists compare (head a) (head b)) < 0;
541 map (x: elemAt x 1) (sort less prepared);
543 /* Return the first (at most) N elements of a list.
545 Type: take :: int -> [a] -> [a]
548 take 2 [ "a" "b" "c" "d" ]
554 # Number of elements to take
555 count: sublist 0 count;
557 /* Remove the first (at most) N elements of a list.
559 Type: drop :: int -> [a] -> [a]
562 drop 2 [ "a" "b" "c" "d" ]
568 # Number of elements to drop
571 list: sublist count (length list) list;
573 /* Return a list consisting of at most `count` elements of `list`,
574 starting at index `start`.
576 Type: sublist :: int -> int -> [a] -> [a]
579 sublist 1 3 [ "a" "b" "c" "d" "e" ]
585 # Index at which to start the sublist
587 # Number of elements to take
591 let len = length list; in
593 (n: elemAt list (n + start))
594 (if start >= len then 0
595 else if start + count > len then len - start
598 /* Return the last element of a list.
600 This function throws an error if the list is empty.
602 Type: last :: [a] -> a
609 assert lib.assertMsg (list != []) "lists.last: list must not be empty!";
610 elemAt list (length list - 1);
612 /* Return all elements but the last.
614 This function throws an error if the list is empty.
616 Type: init :: [a] -> [a]
623 assert lib.assertMsg (list != []) "lists.init: list must not be empty!";
624 take (length list - 1) list;
627 /* Return the image of the cross product of some lists by a function.
630 crossLists (x:y: "${toString x}${toString y}") [[1 2] [3 4]]
631 => [ "13" "14" "23" "24" ]
633 crossLists = builtins.trace
634 "lib.crossLists is deprecated, use lib.cartesianProductOfSets instead"
635 (f: foldl (fs: args: concatMap (f: map f args) fs) [f]);
638 /* Remove duplicate elements from the list. O(n^2) complexity.
640 Type: unique :: [a] -> [a]
646 unique = foldl' (acc: e: if elem e acc then acc else acc ++ [ e ]) [];
648 /* Intersects list 'e' and another list. O(nm) complexity.
651 intersectLists [ 1 2 3 ] [ 6 3 2 ]
654 intersectLists = e: filter (x: elem x e);
656 /* Subtracts list 'e' from another list. O(nm) complexity.
659 subtractLists [ 3 2 ] [ 1 2 3 4 5 3 ]
662 subtractLists = e: filter (x: !(elem x e));
664 /* Test if two lists have no common element.
665 It should be slightly more efficient than (intersectLists a b == [])
667 mutuallyExclusive = a: b: length a == 0 || !(any (x: elem x a) b);