1
0
Fork 0
forked from auxolotl/labs
labs/lib/src/lists/default.nix
2024-07-09 01:49:44 -07:00

234 lines
7.2 KiB
Nix

lib: {
lists = {
from = {
## Convert a value to a list. If the value is already a list,
## it will be returned as-is. If the value is not a list, it
## will be wrapped in a list.
##
## @type a | (List a) -> List a
any = value: if builtins.isList value then value else [ value ];
};
sort = {
## Perform a natural sort on a list of strings.
##
## @type List String -> List String
natural =
list:
let
vectorize =
string:
let
serialize = part: if builtins.isList part then lib.strings.into.int (builtins.head part) else part;
parts = lib.strings.split "(0|[1-9][0-9]*)" string;
in
builtins.map serialize parts;
prepared = builtins.map (value: [
(vectorize value)
value
]) list;
isLess = a: b: (lib.lists.compare lib.numbers.compare (builtins.head a) (builtins.head b)) < 0;
in
builtins.map (x: builtins.elemAt x 1) (builtins.sort isLess prepared);
## Perform a topological sort on a list of items. The predicate function determines whether
## its first argument comes before the second argument.
##
## @type (a -> a -> Bool) -> List a -> List a
topological =
predicate: list:
let
searched = lib.lists.search.depthFirst true predicate list;
results = lib.lists.sort.topological predicate (searched.visited ++ searched.rest);
in
if builtins.length list < 2 then
{ result = list; }
else if searched ? cycle then
{
loops = searched.loops;
cycle = lib.lists.reverse ([ searched.cycle ] ++ searched.visited);
}
else if results ? cycle then
results
else
{ result = [ searched.minimal ] ++ results.result; };
};
search = {
## Perform a depth first search on a list. The supplied predicate function determines whether
## its first argument comes before the second argument.
##
## @type Bool -> (a -> a -> Bool) -> List a
depthFirst =
isAcyclical: predicate: list:
let
process =
current: visited: rest:
let
loops = builtins.filter (value: predicate value current) visited;
partitioned = builtins.partition (value: predicate value current) rest;
in
if isAcyclical && (builtins.length loops > 0) then
{
cycle = current;
inherit loops visited rest;
}
else if builtins.length partitioned.right == 0 then
{
minimal = current;
inherit visited rest;
}
else
process (builtins.head partitioned.right) ([ current ] ++ visited) (
builtins.tail partitioned.right ++ partitioned.wrong
);
in
process (builtins.head list) [ ] (builtins.tail list);
};
## Map a list using both the index and value of each item. The
## index starts at 0.
##
## @type (Int -> a -> b) -> List a -> List b
mapWithIndex = f: list: builtins.genList (i: f i (builtins.elemAt list i)) (builtins.length list);
## Map a list using both the index and value of each item. The
## index starts at 1.
##
## @type (Int -> a -> b) -> List a -> List b
mapWithIndex1 =
f: list: builtins.genList (i: f (i + 1) (builtins.elemAt list i)) (builtins.length list);
## Compare two lists using a custom compare function. The compare
## function is called for each element in the lists that need to
## be compared.
##
## @type (a -> b -> -1 | 0 | 1) -> List a -> List b -> Int
compare =
compare: a: b:
let
result = compare (builtins.head a) (builtins.head b);
in
if a == [ ] then
if b == [ ] then 0 else -1
else if b == [ ] then
1
else if result == 0 then
lib.lists.compare compare (builtins.tail a) (builtins.tail b)
else
result;
## Get the last element of a list.
##
## @type List a -> a
last =
list:
assert lib.errors.trace (list != [ ]) "List cannot be empty";
builtins.elemAt list (builtins.length list - 1);
## Slice part of a list to create a new list.
##
## @type Int -> Int -> List -> List
slice =
start: count: list:
let
listLength = builtins.length list;
resultLength =
if start >= listLength then
0
else if start + count > listLength then
listLength - start
else
count;
in
builtins.genList (i: builtins.elemAt list (start + i)) resultLength;
## Take the first n elements of a list.
##
## @type Int -> List -> List
take = lib.lists.slice 0;
## Drop the first n elements of a list.
##
## @type Int -> List -> List
drop =
count: list:
let
listLength = builtins.length list;
in
lib.lists.slice count listLength list;
## Take all but the last element of a list.
##
## @type List -> List
init =
list:
assert lib.errors.trace (builtins.length list != 0) "lib.lists.init: list must not be empty.";
lib.lists.take (builtins.length list - 1) list;
## Reverse a list.
##
## @type List -> List
reverse =
list:
let
length = builtins.length list;
create = i: builtins.elemAt list (length - i - 1);
in
builtins.genList create length;
## Interleave a list with a separator.
##
## @type Separator -> List -> List
intersperse =
separator: list:
let
length = builtins.length list;
in
if length < 2 then
list
else
builtins.tail (
builtins.concatMap (part: [
separator
part
]) list
);
## Create a list of integers from a starting number to an ending
## number. This *includes* the ending number as well.
##
## @type Int -> Int -> List
range = start: end: if start > end then [ ] else builtins.genList (i: start + i) (end - start + 1);
## Depending on a given condition, either use the given value (as
## a list) or an empty list.
##
## @type Attrs a b => Bool -> a -> a | b
when =
condition: value: if condition then if builtins.isList value then value else [ value ] else [ ];
## Count the number of items in a list that satisfy a given predicate.
##
## @type (a -> Bool) -> List a -> Int
count =
predicate: list:
builtins.foldl' (total: value: if predicate value then total + 1 else total) 0 list;
## Remove duplicate items from a list.
##
## @type List -> List
unique =
list:
let
filter = result: value: if builtins.elem value result then result else result ++ [ value ];
in
builtins.foldl' filter [ ] list;
## Flatten a list of lists into a single list.
##
## @type List (List a) -> List a
flatten =
value: if builtins.isList value then builtins.concatMap lib.lists.flatten value else [ value ];
};
}