From c4bf6ba8e17c7f873e88ef78056a38469a68c79b Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 13 Nov 2025 12:45:01 +0700 Subject: [PATCH] docs: add time complexity to relevant primops --- src/libexpr/primops.cc | 185 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 184 insertions(+), 1 deletion(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 98ed1b4509b..f6e6c1912d5 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2999,6 +2999,12 @@ static RegisterPrimOp primop_attrNames({ Return the names of the attributes in the set *set* in an alphabetically sorted list. For instance, `builtins.attrNames { y = 1; x = "foo"; }` evaluates to `[ "x" "y" ]`. + + # Time Complexity + + - O(n log n), where: + + n = number of attributes in the set )", .fun = prim_attrNames, }); @@ -3031,6 +3037,12 @@ static RegisterPrimOp primop_attrValues({ .doc = R"( Return the values of the attributes in the set *set* in the order corresponding to the sorted attribute names. + + # Time Complexity + + - O(n log n), where: + + n = number of attributes in the set )", .fun = prim_attrValues, }); @@ -3056,6 +3068,10 @@ static RegisterPrimOp primop_getAttr({ aborts if the attribute doesn’t exist. This is a dynamic version of the `.` operator, since *s* is an expression rather than an identifier. + + # Time Complexity + + O(log n) where n = number of attributes in the set )", .fun = prim_getAttr, }); @@ -3144,6 +3160,10 @@ static RegisterPrimOp primop_hasAttr({ `hasAttr` returns `true` if *set* has an attribute named *s*, and `false` otherwise. This is a dynamic version of the `?` operator, since *s* is an expression rather than an identifier. + + # Time Complexity + + O(log n) where n = number of attributes in the set )", .fun = prim_hasAttr, }); @@ -3203,6 +3223,13 @@ static RegisterPrimOp primop_removeAttrs({ ``` evaluates to `{ y = 2; }`. + + # Time Complexity + + O(n + k log k) where: + + n = number of attributes in input set + k = number of attribute names to remove )", .fun = prim_removeAttrs, }); @@ -3290,6 +3317,10 @@ static RegisterPrimOp primop_listToAttrs({ ```nix { foo = 123; bar = 456; } ``` + + # Time Complexity + + O(n log n) where n = number of list elements )", .fun = prim_listToAttrs, }); @@ -3366,7 +3397,12 @@ static RegisterPrimOp primop_intersectAttrs({ Return a set consisting of the attributes in the set *e2* which have the same name as some attribute in *e1*. - Performs in O(*n* log *m*) where *n* is the size of the smaller set and *m* the larger set's size. + # Time Complexity + + O(n * log m) where: + + n = number of attributes in the smaller set + m = number of attributes in the larger set )", .fun = prim_intersectAttrs, }); @@ -3406,6 +3442,13 @@ static RegisterPrimOp primop_catAttrs({ ``` evaluates to `[1 2]`. + + # Time Complexity + + O(n * log m) where: + + n = number of sets in input list + m = number of attributes per set )", .fun = prim_catAttrs, }); @@ -3449,6 +3492,10 @@ static RegisterPrimOp primop_functionArgs({ "Formal argument" here refers to the attributes pattern-matched by the function. Plain lambdas are not included, e.g. `functionArgs (x: ...) = { }`. + + # Time Complexity + + O(n) where n = number of formal arguments )", .fun = prim_functionArgs, }); @@ -3481,6 +3528,14 @@ static RegisterPrimOp primop_mapAttrs({ ``` evaluates to `{ a = 10; b = 20; }`. + + # Time Complexity + + O(n) where: + + n = number of attributes + + Calls to `f` are performed afterwards, when needed )", .fun = prim_mapAttrs, }); @@ -3568,6 +3623,15 @@ static RegisterPrimOp primop_zipAttrsWith({ b = { name = "b"; values = [ "z" ]; }; } ``` + + # Time Complexity + + O(n * k * log k) worst case, where: + + n = number of attribute sets in input list + k = number of unique keys across all sets + + More precisely: O(n * m * log k) where m ≤ k is average number of attributes per set )", .fun = prim_zipAttrsWith, }); @@ -3633,6 +3697,10 @@ static RegisterPrimOp primop_head({ Return the first element of a list; abort evaluation if the argument isn’t a list or is an empty list. You can test whether a list is empty by comparing it with `[]`. + + # Time Complexity + + O(1) )", .fun = prim_head, }); @@ -3664,6 +3732,10 @@ static RegisterPrimOp primop_tail({ > This function should generally be avoided since it's inefficient: > unlike Haskell's `tail`, it takes O(n) time, so recursing over a > list by repeatedly calling `tail` takes O(n^2) time. + + # Time Complexity + + O(n) where n = list length (copies n-1 elements) )", .fun = prim_tail, }); @@ -3698,6 +3770,14 @@ static RegisterPrimOp primop_map({ ``` evaluates to `[ "foobar" "foobla" "fooabc" ]`. + + # Time Complexity + + O(n) where: + + n = list length + + Calls to `f` are performed afterwards when needed. )", .fun = prim_map, }); @@ -3747,6 +3827,13 @@ static RegisterPrimOp primop_filter({ .doc = R"( Return a list consisting of the elements of *list* for which the function *f* returns `true`. + + # Time Complexity + + O(n * T_f) where: + + n = list length + T_f = predicate evaluation time )", .fun = prim_filter, }); @@ -3770,6 +3857,15 @@ static RegisterPrimOp primop_elem({ .doc = R"( Return `true` if a value equal to *x* occurs in the list *xs*, and `false` otherwise. + + # Time Complexity + + O(n * T) (worst case) where: + + n = list length + T = time to compare two elements + + returns early if the elements is found )", .fun = prim_elem, }); @@ -3792,6 +3888,12 @@ static RegisterPrimOp primop_concatLists({ .args = {"lists"}, .doc = R"( Concatenate a list of lists into a single list. + + # Time Complexity + + O(N) where: + + N = total number of elements across all lists )", .fun = prim_concatLists, }); @@ -3808,6 +3910,10 @@ static RegisterPrimOp primop_length({ .args = {"e"}, .doc = R"( Return the length of the list *e*. + + # Time Complexity + + O(1) )", .fun = prim_length, }); @@ -3851,6 +3957,17 @@ static RegisterPrimOp primop_foldlStrict({ argument is the current element being processed. The return value of each application of `op` is evaluated immediately, even for intermediate values. + + # Time Complexity + + O(n * T_op) where: + + n = list length + T_op = `op` call evaluation time + + Note: Because foldl' evaluates results only to [WHNF](@docroot@/language/evaluation.md#values), + deeper parts of values returned by op may be evaluated later. + Their cost (and errors) therefore may not clearly belong to T_op or to foldl' itself. )", .fun = prim_foldlStrict, }); @@ -3889,6 +4006,15 @@ static RegisterPrimOp primop_any({ .doc = R"( Return `true` if the function *pred* returns `true` for at least one element of *list*, and `false` otherwise. + + # Time Complexity + + O(n * T_pred) where: + + - n = `list` length + - T_pred = `pred` call evaluation time + + returns early when `pred` returns `true` )", .fun = prim_any, }); @@ -3904,6 +4030,13 @@ static RegisterPrimOp primop_all({ .doc = R"( Return `true` if the function *pred* returns `true` for all elements of *list*, and `false` otherwise. + + # Time Complexity + + O(n * T_f) where: + + - n = list length + - T_f = predicate evaluation time )", .fun = prim_all, }); @@ -3942,6 +4075,14 @@ static RegisterPrimOp primop_genList({ ``` returns the list `[ 0 1 4 9 16 ]`. + + # Time Complexity + + O(n) where: + + n = requested length. + + Calls to `generator` are performed afterwards, when needed. )", .fun = prim_genList, }); @@ -4036,6 +4177,13 @@ static RegisterPrimOp primop_sort({ If the *comparator* violates any of these properties, then `builtins.sort` reorders elements in an unspecified manner. + + # Time Complexity + + O(n log n * T_cmp), where: + + n = `list` length + T_cmp = `comparator` call evaluation time )", .fun = prim_sort, }); @@ -4097,6 +4245,13 @@ static RegisterPrimOp primop_partition({ ```nix { right = [ 23 42 ]; wrong = [ 1 9 3 ]; } ``` + + # Time Complexity + + O(n * T_pred) where: + + n = list length + T_pred = `pred` call evaluation time )", .fun = prim_partition, }); @@ -4150,6 +4305,14 @@ static RegisterPrimOp primop_groupBy({ ```nix { b = [ "bar" "baz" ]; f = [ "foo" ]; } ``` + + # Time Complexity + + O(N * T_f + N * log k) where: + + N = number of `list` elements + T_f = `f` call evaluation time + k = number of unique groups )", .fun = prim_groupBy, }); @@ -4192,6 +4355,14 @@ static RegisterPrimOp primop_concatMap({ .doc = R"( This function is equivalent to `builtins.concatLists (map f list)` but is more efficient. + + # Time Complexity + + O(k * T_f + N) where: + + k = length of input list + T_f = time to call `f` on an element + N = total number of elements returned by `f` calls )", .fun = prim_concatMap, }); @@ -4888,6 +5059,10 @@ static RegisterPrimOp primop_concatStringsSep({ Concatenate a list of strings with a separator between each element, e.g. `concatStringsSep "/" ["usr" "local" "bin"] == "usr/local/bin"`. + + # Time Complexity + + O(n) where n = total length of output string )", .fun = prim_concatStringsSep, }); @@ -4972,6 +5147,14 @@ static RegisterPrimOp primop_replaceStrings({ ``` evaluates to `"fabir"`. + + # Time Complexity + + O(n * k * c) where: + + n = length of input string + k = number of replacement patterns + c = average length of patterns in 'from' list )", .fun = prim_replaceStrings, });