Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 181 additions & 1 deletion src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2999,6 +2999,13 @@ 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) best case (attribute set already sorted)
- O(n log n) worst case (requires sorting), where:
n = number of attributes in the set
)",
.fun = prim_attrNames,
});
Expand Down Expand Up @@ -3031,6 +3038,13 @@ 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) best case (attribute set already sorted)
- O(n log n) worst case (requires sorting), where:
Comment on lines +3044 to +3045
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no user-facing concept of attrset sortedness, and I don't think the best case is implemented? Usually that'd be a special case in the sorting algorithm which makes the average case worse.
Furthermore, I think pinning down the best case is too ambitious and not even useful to users.

n = number of attributes in the set
)",
.fun = prim_attrValues,
});
Expand All @@ -3056,6 +3070,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,
});
Expand Down Expand Up @@ -3144,6 +3162,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,
});
Expand Down Expand Up @@ -3203,6 +3225,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,
});
Expand Down Expand Up @@ -3290,6 +3319,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,
});
Expand Down Expand Up @@ -3366,7 +3399,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,
});
Expand Down Expand Up @@ -3406,6 +3444,13 @@ static RegisterPrimOp primop_catAttrs({
```
evaluates to `[1 2]`.
# Time Complexity
O(n * log m) where:
n = number of sets in input list
m = average number of attributes per set
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically not true for skewed distributions, I believe. We can keep it vague again; it's ok.

Suggested change
m = average number of attributes per set
m = number of attributes per set

)",
.fun = prim_catAttrs,
});
Expand Down Expand Up @@ -3449,6 +3494,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,
});
Expand Down Expand Up @@ -3481,6 +3530,13 @@ static RegisterPrimOp primop_mapAttrs({
```
evaluates to `{ a = 10; b = 20; }`.
# Time Complexity
O(n * T_f) where:
n = number of attributes
T_f = function evaluation time
Comment on lines +3533 to +3539
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See map

)",
.fun = prim_mapAttrs,
});
Expand Down Expand Up @@ -3568,6 +3624,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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't checked this one.

)",
.fun = prim_zipAttrsWith,
});
Expand Down Expand Up @@ -3633,6 +3698,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,
});
Expand Down Expand Up @@ -3664,6 +3733,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 (must copy n-1 elements)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
O(n) where n = list length (must copy n-1 elements)
O(n) where n = list length (copies n-1 elements)

Reason (not to be included): Could be changed perhaps if testing shows that a different data structure for large lists results in a speedup in practice.

)",
.fun = prim_tail,
});
Expand Down Expand Up @@ -3698,6 +3771,13 @@ static RegisterPrimOp primop_map({
```
evaluates to `[ "foobar" "foobla" "fooabc" ]`.
# Time Complexity
O(n * T_f) where:
n = list length
T_f = function evaluation time
Comment on lines +3777 to +3780
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
O(n * T_f) where:
n = list length
T_f = function evaluation time
O(n) where:
n = list length
Calls to `f` are performed afterwards, when needed.

)",
.fun = prim_map,
});
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this one's actually strict in terms of callbacks.

)",
.fun = prim_filter,
});
Expand All @@ -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 average element
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O() expresses an upper bound, so this should be the upper bound too.
Let's not get into average time.
We can use vagueness to our advantage though:

Suggested change
T = time to compare average element
T = time to compare two elements

This is by ==, right?

returns early if the elements is found
)",
.fun = prim_elem,
});
Expand All @@ -3792,6 +3888,13 @@ static RegisterPrimOp primop_concatLists({
.args = {"lists"},
.doc = R"(
Concatenate a list of lists into a single list.
# Time Complexity
O(k + N) where:
k = number of input lists
N = total number of elements across all lists
Comment on lines +3894 to +3897
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k is proportional to N, so can be omitted in a +

Suggested change
O(k + N) where:
k = number of input lists
N = total number of elements across all lists
O(N) where:
N = total number of elements across all lists

)",
.fun = prim_concatLists,
});
Expand All @@ -3808,6 +3911,10 @@ static RegisterPrimOp primop_length({
.args = {"e"},
.doc = R"(
Return the length of the list *e*.
# Time Complexity
O(1)
)",
.fun = prim_length,
});
Expand Down Expand Up @@ -3851,6 +3958,13 @@ 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_f) where:
n = list length
T_f = fold function evaluation time
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
T_f = fold function evaluation time
T_op = `op` call evaluation time
Note that the result of `op` is only evaluated to [WHNF](@docroot@/language/evaluation.md#values) or perhaps more depending on subsequent `op` calls. Returned items such as attribute values may or may not be part of `T_op` and `foldl'`'s error behavior.

)",
.fun = prim_foldlStrict,
});
Expand Down Expand Up @@ -3889,6 +4003,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_f) where:
- n = list length
- T_f = predicate evaluation time
returns early when predicate returns true
Comment on lines +4009 to +4014
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
O(n * T_f) where:
- n = list length
- T_f = predicate evaluation time
returns early when predicate returns true
O(n * T_pred) where:
- n = `list` length
- T_pred = `pred` call evaluation time
returns early when `pred` returns `true`

)",
.fun = prim_any,
});
Expand All @@ -3904,6 +4027,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
Comment on lines +4033 to +4036
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
O(n * T_f) where:
- n = list length
- T_f = predicate evaluation time
O(n * T_pred) where:
- n = `list` length
- T_pred = `pred` call evaluation time
returns early when `pred` returns `true`

)",
.fun = prim_all,
});
Expand Down Expand Up @@ -3942,6 +4072,13 @@ static RegisterPrimOp primop_genList({
```
returns the list `[ 0 1 4 9 16 ]`.
# Time Complexity
O(n * T_f) where:
n = requested length
T_f = generator function evaluation time
Comment on lines +4076 to +4081
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't call them, so time complexity is O(n)

nix-repl> builtins.length (builtins.genList (x: throw "nope") 1000) 
1000

Comment on lines +4078 to +4081
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
O(n * T_f) where:
n = requested length
T_f = generator function evaluation time
O(n) where:
n = requested length
Calls to `generator` are performed afterwards when needed.

)",
.fun = prim_genList,
});
Expand Down Expand Up @@ -4036,6 +4173,14 @@ 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) worst case
O(n * T_cmp) best case (input already sorted), where:
n = list length
T_cmp = comparator evaluation time
Comment on lines +4179 to +4183
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best case is doubtful and noisy.

Suggested change
O(n log n * T_cmp) worst case
O(n * T_cmp) best case (input already sorted), where:
n = list length
T_cmp = comparator evaluation time
O(n log n * T_cmp)
n = `list` length
T_cmp = `comparator` call evaluation time

)",
.fun = prim_sort,
});
Expand Down Expand Up @@ -4097,6 +4242,13 @@ static RegisterPrimOp primop_partition({
```nix
{ right = [ 23 42 ]; wrong = [ 1 9 3 ]; }
```
# Time Complexity
O(n * T_f) where:
n = list length
T_f = predicate evaluation time
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
T_f = predicate evaluation time
T_f = `pred` call evaluation time

)",
.fun = prim_partition,
});
Expand Down Expand Up @@ -4150,6 +4302,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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
N = number of list elements
N = number of `list` elements

T_f = grouping function evaluation time
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
T_f = grouping function evaluation time
T_f = `f` call evaluation time

k = number of unique groups
)",
.fun = prim_groupBy,
});
Expand Down Expand Up @@ -4192,6 +4352,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 evaluate function on each element
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
T_f = time to evaluate function on each element
T_f = time to call `f` on an element

N = total elements in all output lists
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
N = total elements in all output lists
N = total number of elements returned by `f` calls

)",
.fun = prim_concatMap,
});
Expand Down Expand Up @@ -4888,6 +5056,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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically there's also the evaluation of each element, as this function is currently strict in those.
(I'm not sure that it will remain so! I've heard talk about lazy ropes)

)",
.fun = prim_concatStringsSep,
});
Expand Down Expand Up @@ -4972,6 +5144,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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Average actually works out ok here, because of the * k.

)",
.fun = prim_replaceStrings,
});
Expand Down
Loading