list
stdlib/list.hk: List<T> as methods on impl<T> List<T>.
Storage is a runtime-side persistent List, an RRB vector, opaque to user code. Four intrinsics bottom out in Rust: empty / append : fresh persistent lists; append is O(1)-amortized and leaves the original unchanged length : returns i32 length get : bounds-checked indexed read, returns Option<T>
The surface is method-style: xs.append(x), xs.length, xs.get(i), plus List<T>.empty() as an associated function. The C-ABI symbols are hanki_intrinsic_list_list_* (per intrinsic_c_abi_symbol's qualification of the new list.List.NAME qualified name).
option.Option, option.Some, option.None are reached bare through this open option directive: get returns Option<T> and find / map / each! pattern-match on Some/None.
List
type List<T>
end
impl<T> List<T>
empty
def empty() -> List<T>
The empty list. The element type is fixed by the binding it flows into.
xs: List<i32> = List.empty()
xs.length => 0
append
def append(self, x: T) -> List<T>
Returns a new list with x appended; the original is unchanged (persistent).
xs: List<i32> = List.empty().append(7i32)
xs.get(0) => Some(7i32)
length
prop length(self) -> int
The number of elements.
xs: List<i32> = List.empty().append(1i32).append(2i32)
xs.length => 2
empty?
prop empty?(self) -> bool
True when the list has no elements. xs.empty? is xs.length == 0.
xs: List<i32> = List.empty()
xs.empty? => true
xs.append(1i32).empty? => false
get
def get(self, i: int) -> Option<T>
The element at index i, or None when i is out of bounds (including a negative index).
xs: List<i32> = List.empty().append(7i32)
xs.get(0) => Some(7i32)
xs.get(9) => None
get_or
def get_or(self, i: int, fallback: T) -> T
The element at index i, or fallback when i is out of bounds.
xs.get_or(i, d) is xs.get(i).unwrap_or(d) without the Option. That is why it exists: get allocates a Some on the heap for every read, and a caller that immediately unwraps it has paid for a value it discards. In a scan over a table or a buffer that allocation is most of the cost.
xs: List<i32> = List.empty().append(7i32)
xs.get_or(0, 0i32) => 7i32
xs.get_or(9, 0i32) => 0i32
first
prop first(self) -> Option<T>
The first element, or None for the empty list. xs.first is xs.get(0).
xs: List<i32> = List.empty().append(7i32).append(8i32)
xs.first => Some(7i32)
empty: List<i32> = List.empty()
empty.first => None
last
prop last(self) -> Option<T>
The last element, or None for the empty list. A thin get(length - 1): the empty list's -1 index is out of bounds, and it is therefore None.
xs: List<i32> = List.empty().append(7i32).append(8i32)
xs.last => Some(8i32)
empty: List<i32> = List.empty()
empty.last => None
concat
def concat(self, other: List<T>) -> List<T>
The concatenation of self and other, in order; both inputs are unchanged (persistent). O(log n).
xs: List<i32> = List.empty().append(1i32).append(2i32)
ys: List<i32> = List.empty().append(3i32)
xs.concat(ys).get(2) => Some(3i32)
slice
def slice(self, lo: int, hi: int) -> List<T>
The sub-list [lo, hi), clamped to the list bounds (a negative or past-the-end endpoint is pulled into range). The source is unchanged. O(log n).
xs: List<i32> = List.empty().append(10i32).append(20i32).append(30i32)
xs.slice(1, 3).get(0) => Some(20i32)
xs.slice(1, 3).length => 2
update
def update(self, i: int, x: T) -> List<T>
A new list with index i replaced by x; an out-of-bounds i yields the list unchanged. The original is untouched (persistent). O(log n).
xs: List<i32> = List.empty().append(1i32).append(2i32)
xs.update(1, 9i32).get(1) => Some(9i32)
xs.get(1) => Some(2i32)
prepend
def prepend(self, x: T) -> List<T>
A new list with x at the front, followed by every element of self.
xs: List<i32> = List.empty().append(2i32).append(3i32)
xs.prepend(1i32).get(0) => Some(1i32)
xs.prepend(1i32).length => 3
find
def find(self, pred: (T) -> bool) -> Option<T>
The first element satisfying pred, or None if none does.
xs: List<i32> = List.empty().append(1i32).append(5i32).append(9i32)
xs.find(|x: i32| x > 3i32) => Some(5i32)
A counting loop, like every linear walk below: per-element recursion would consume call stack proportional to the length: a native stack overflow on the AOT tier for ordinary long lists.
find!
def find!(self, pred: (T) -> bool [e]) -> Option<T> [e]
find with an effectful predicate: the first element for which pred accepts, or None. It short-circuits, and pred runs only up to the match.
xs: List<i32> = List.empty().append(1i32).append(5i32).append(9i32)
xs.find!(|x: i32| x > 3i32) => Some(5i32)
find_map
def find_map(self, f: (T) -> Option<U>) -> Option<U>
The first Some that f produces, or None when f yields None for every element: find and map fused. It short-circuits: f is applied to each element only until the first Some. O(n) worst case.
xs: List<i32> = List.empty().append(2i32).append(3i32)
xs.find_map(|x: i32| Some(x * 10i32)) => Some(20i32)
os: List<Option<i32>> = List.empty().append(None).append(Some(5i32))
os.find_map(|o: Option<i32>| o) => Some(5i32)
find_map!
def find_map!(self, f: (T) -> Option<U> [e]) -> Option<U> [e]
find_map with an effectful f: the first Some it produces, or None. It short-circuits, f is applied only up to the first Some, and the remaining elements' effects never happen.
xs: List<i32> = List.empty().append(2i32).append(3i32)
xs.find_map!(|x: i32| Some(x * 10i32)) => Some(20i32)
map
def map(self, f: (T) -> U) -> List<U>
A new list with f applied to every element, in order; the source is unchanged.
xs: List<i32> = List.empty().append(1i32).append(2i32)
ys = xs.map(|x: i32| x * 10i32)
ys.get(0) => Some(10i32)
ys.get(1) => Some(20i32)
map!
def map!(self, f: (T) -> U [e]) -> List<U> [e]
map with an effectful transform: the results of f over every element, in order. f runs once per element, left to right, and a transform that also prints or writes does so in that order.
xs: List<i32> = List.empty().append(1i32).append(2i32)
ys = xs.map!(|x: i32| x * 10i32)
ys.get(0) => Some(10i32)
ys.get(1) => Some(20i32)
each!
def each!(self, f: (T) -> () [e]) -> () [e]
Applies the effectful f to every element in order, for its effects. @no-doctest: effectful iteration returning unit; no value to assert; see io for effectful-callback examples
eachwithindex!
def each_with_index!(self, f: (T, int) -> () [e]) -> () [e]
Applies the effectful f to each element with its index, in order. @no-doctest: effectful iteration returning unit; no value to assert; see io for effectful-callback examples
fold
def fold(self, init: U, f: (U, T) -> U) -> U
The left fold of f over the elements: f(…f(f(init, x₀), x₁)…, xₙ). An empty list folds to init.
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.fold(0i32, |acc: i32, x: i32| acc + x) => 6i32
fold!
def fold!(self, init: U, f: (U, T) -> U [e]) -> U [e]
fold with an effectful step. This is the combinator to reach for when an accumulator has to survive an effectful traversal: a closure captures by value (HANKI.md §13), and accumulating into a var from inside an each! block cannot work and is rejected (H0209); fold! threads the accumulator through instead and returns it.
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.fold!(0i32, |acc: i32, x: i32| acc + x) => 6i32
try_fold
def try_fold(self, init: U, f: (U, T) -> Result<U, E>) -> Result<U, E>
fold that can stop: the step returns Result, and the first Err ends the traversal as the result; later elements are not visited. An empty list gives Ok(init). The composition this improves on - fold with the Result threaded through and_then - answers the same value but cannot stop: and_then passes the first Err untouched to the end, and the step still runs once per element. Here a long list that fails early costs only the elements before the failure.
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.try_fold(0i32, |acc: i32, x: i32| Ok(acc + x)) => Ok(6i32)
xs.try_fold(0i32, |acc: i32, x: i32| if x == 2i32 then Err("two") else Ok(acc + x)) => Err("two")
ys: List<i32> = List.empty()
ys.try_fold(7i32, |acc: i32, x: i32| Err("never")) => Ok(7i32)
try_each
def try_each(self, f: (T) -> Result<(), E>) -> Result<(), E>
A validation sweep that can stop: applies the pure f in order and answers the first Err, or Ok(()) when every element passes. As with try_fold, the and_then composition answers the same value but runs f once per element regardless; this stops at the failure.
xs: List<i32> = List.empty().append(2i32).append(4i32)
xs.try_each(|x: i32| if x % 2i32 == 0i32 then Ok(()) else Err(x)) => Ok(())
xs.try_each(|x: i32| if x < 3i32 then Ok(()) else Err(x)) => Err(4i32)
try_fold!
def try_fold!(self, init: U, f: (U, T) -> Result<U, E> [e]) -> Result<U, E> [e]
fold! that can stop: the step returns Result, and the first Err ends the traversal as the result. Later elements are not visited, and their effects never happen. An empty list gives Ok(init). This is the combinator for a fallible effectful pass; threading failure through a plain fold! means a poisoned accumulator every later step must re-check, and accumulating into a var from inside each! is rejected (H0209).
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.try_fold!(0i32, |acc: i32, x: i32| Ok(acc + x)) => Ok(6i32)
xs.try_fold!(0i32, |acc: i32, x: i32| if x == 2i32 then Err("two") else Ok(acc + x)) => Err("two")
ys: List<i32> = List.empty()
ys.try_fold!(7i32, |acc: i32, x: i32| Err("never")) => Ok(7i32)
try_each!
def try_each!(self, f: (T) -> Result<(), E> [e]) -> Result<(), E> [e]
each! that can stop: f returns Result<(), E>, and the first Err ends the traversal as the result; later elements are not visited. The unit-accumulator convenience over try_fold! for the majority case: perform each element's effect, stop at the first failure.
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.try_each!(|x: i32| if x == 2i32 then Err("two") else Ok(())) => Err("two")
xs.try_each!(|x: i32| if x > 0i32 then Ok(()) else Err("nonpositive")) => Ok(())
fold_right
def fold_right(self, init: U, f: (T, U) -> U) -> U
A right-associative fold: f(x0, f(x1, … f(xn, init))). f takes the element first and the accumulator second, and elements are visited right-to-left. On an associative f this matches fold; on a non-associative one (e.g. subtraction) it differs. O(n).
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.fold_right(0i32, |x: i32, acc: i32| x - acc) => 2
scan
def scan(self, init: U, f: (U, T) -> U) -> List<U>
The successive accumulator values of a left fold: element i is f applied across elements 0..=i starting from init (an inclusive running fold; fold returns the final value alone). The result has the same length as self; the empty list yields the empty list. O(n).
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
ys: List<i32> = xs.scan(0i32, |acc: i32, x: i32| acc + x)
ys.length => 3
ys.get(0) => Some(1i32)
ys.get(2) => Some(6i32)
filter
def filter(self, pred: (T) -> bool) -> List<T>
The elements satisfying pred, in their original order.
xs: List<i32> = List.empty().append(1i32).append(8i32).append(2i32)
xs.filter(|x: i32| x < 5i32).length => 2
xs.filter(|x: i32| x < 5i32).get(1) => Some(2i32)
filter!
def filter!(self, pred: (T) -> bool [e]) -> List<T> [e]
filter with an effectful predicate: the elements pred accepts, in their original order. pred is evaluated once per element, and one that reads the filesystem or the clock is called length times.
xs: List<i32> = List.empty().append(1i32).append(8i32).append(2i32)
xs.filter!(|x: i32| x < 5i32).length => 2
xs.filter!(|x: i32| x < 5i32).get(1) => Some(2i32)
filter_map
def filter_map(self, f: (T) -> Option<U>) -> List<U>
Map and retain the Somes, in order: filter and map fused, and the shape of every parse-a-row-or-skip-it loop. The alternative is a flat_map whose f answers a one- or zero-element list, which allocates a list per element in order to discard most of them.
ns: List<i32> = List.empty().append(10i32).append(0i32).append(4i32)
ns.filter_map(|n: i32| 100i32.checked_div(n)).length => 2
ns.filter_map(|n: i32| 100i32.checked_div(n)).get(1) => Some(25i32)
os: List<Option<i32>> = List.empty().append(Some(1i32)).append(None)
os.filter_map(|o: Option<i32>| o).length => 1
filter_map!
def filter_map!(self, f: (T) -> Option<U> [e]) -> List<U> [e]
filter_map with an effectful transform: f's Some results, in order. f is applied once per element, and one that reads the filesystem or the clock is called length times.
ns: List<i32> = List.empty().append(10i32).append(0i32).append(4i32)
ns.filter_map!(|n: i32| 100i32.checked_div(n)).length => 2
ns.filter_map!(|n: i32| 100i32.checked_div(n)).get(0) => Some(10i32)
reverse
def reverse(self) -> List<T>
The elements in reverse order.
xs: List<i32> = List.empty().append(1i32).append(2i32)
xs.reverse().get(0) => Some(2i32)
xs.reverse().get(1) => Some(1i32)
take
def take(self, n: int) -> List<T>
The first n elements. A thin slice(0, n), and n is therefore clamped: a negative n gives the empty list, an n past the end gives the whole list. The source is unchanged. O(log n).
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.take(2).length => 2
xs.take(2).get(1) => Some(2i32)
xs.take(9).length => 3
drop
def drop(self, n: int) -> List<T>
Every element after the first n. A thin slice(n, length()), and n is clamped: a negative n gives the whole list, an n past the end gives the empty list. The source is unchanged. O(log n).
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.drop(1).length => 2
xs.drop(1).get(0) => Some(2i32)
xs.drop(9).length => 0
take_last
def take_last(self, n: int) -> List<T>
The last n elements. A thin slice(length() - n, length()), and n is clamped: a non-positive n gives the empty list, an n past the end gives the whole list. The source is unchanged. O(log n).
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.take_last(2).length => 2
xs.take_last(2).get(0) => Some(2i32)
xs.take_last(9).length => 3
drop_last
def drop_last(self, n: int) -> List<T>
Every element except the last n. A thin slice(0, length() - n), and n is clamped: a non-positive n gives the whole list, an n past the end gives the empty list. The source is unchanged. O(log n).
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.drop_last(1).length => 2
xs.drop_last(1).get(1) => Some(2i32)
xs.drop_last(9).length => 0
any?
def any?(self, pred: (T) -> bool) -> bool
True when some element satisfies pred. Short-circuits on the first match (via find); false for the empty list.
xs: List<i32> = List.empty().append(1i32).append(5i32).append(9i32)
xs.any?(|x: i32| x > 8i32) => true
xs.any?(|x: i32| x > 9i32) => false
any!
def any!(self, pred: (T) -> bool [e]) -> bool [e]
any? with an effectful predicate. Short-circuits on the first element that satisfies pred, and the effects of the remaining elements never happen.
xs: List<i32> = List.empty().append(1i32).append(5i32).append(9i32)
xs.any!(|x: i32| x > 8i32) => true
xs.any!(|x: i32| x > 9i32) => false
all?
def all?(self, pred: (T) -> bool) -> bool
True where every element satisfies pred, and vacuously true for the empty list. Short-circuits on the first element that fails.
xs: List<i32> = List.empty().append(2i32).append(4i32).append(6i32)
xs.all?(|x: i32| x > 1i32) => true
xs.all?(|x: i32| x > 3i32) => false
all!
def all!(self, pred: (T) -> bool [e]) -> bool [e]
all with an effectful predicate, vacuously true for the empty list. It short-circuits on the first element that fails, and the effects of the remaining elements never happen.
xs: List<i32> = List.empty().append(2i32).append(4i32).append(6i32)
xs.all!(|x: i32| x > 1i32) => true
xs.all!(|x: i32| x > 3i32) => false
count
def count(self, pred: (T) -> bool) -> int
The number of elements satisfying pred, and 0 for the empty list. Like filter(pred).length but without building the intermediate list.
xs: List<i32> = List.empty().append(1i32).append(5i32).append(9i32)
xs.count(|x: i32| x > 4i32) => 2
xs.count(|x: i32| x > 9i32) => 0
count!
def count!(self, pred: (T) -> bool [e]) -> int [e]
count with an effectful predicate. It visits every element, and pred's effect happens length times; there is nothing to short-circuit.
xs: List<i32> = List.empty().append(1i32).append(5i32).append(9i32)
xs.count!(|x: i32| x > 4i32) => 2
xs.count!(|x: i32| x > 9i32) => 0
partition
def partition(self, pred: (T) -> bool) -> Pair<List<T>, List<T>>
Split into the elements satisfying pred (first) and those that do not (second), each in input order: filter and its complement in one O(n) walk. The predicate counterpart to no single existing method.
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32).append(4i32)
p: Pair<List<i32>, List<i32>> = xs.partition(|x: i32| x % 2i32 == 0i32)
p.first.length => 2
p.first.get(0) => Some(2i32)
p.second.length => 2
p.second.get(0) => Some(1i32)
partition!
def partition!(self, pred: (T) -> bool [e]) -> Pair<List<T>, List<T>> [e]
partition with an effectful predicate: the elements pred accepts (first) and those for which it does not (second), each in input order. pred is evaluated once per element, in one walk.
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32).append(4i32)
p: Pair<List<i32>, List<i32>> = xs.partition!(|x: i32| x % 2i32 == 0i32)
p.first.length => 2
p.first.get(0) => Some(2i32)
p.second.length => 2
p.second.get(0) => Some(1i32)
position
def position(self, pred: (T) -> bool) -> Option<int>
The index of the first element satisfying pred, or None where none do: bound-free, predicate counterpart to index_of. Short-circuits on the first match.
xs: List<i32> = List.empty().append(3i32).append(5i32).append(8i32)
xs.position(|x: i32| x > 4i32) => Some(1)
xs.position(|x: i32| x > 9i32) => None
take_while
def take_while(self, pred: (T) -> bool) -> List<T>
The longest prefix whose every element satisfies pred; stops before the first element that fails, or at the end. The complement of drop_while: position locates the first failing element, and take then takes the prefix before it. O(n) worst case (the prefix scan).
xs: List<i32> = List.empty().append(2i32).append(4i32).append(5i32).append(6i32)
xs.take_while(|x: i32| x < 5i32).length => 2
xs.take_while(|x: i32| x > 9i32).length => 0
take_while!
def take_while!(self, pred: (T) -> bool [e]) -> List<T> [e]
take_while with an effectful predicate: the longest prefix whose elements all satisfy pred. It stops at the first element that fails, and the rest of the list's effects never happen.
xs: List<i32> = List.empty().append(2i32).append(4i32).append(5i32).append(6i32)
xs.take_while!(|x: i32| x < 5i32).length => 2
xs.take_while!(|x: i32| x > 9i32).length => 0
A direct walk and no position plus take as the pure form uses: position takes a pure predicate, and the effectful one cannot be handed to it.
drop_while
def drop_while(self, pred: (T) -> bool) -> List<T>
The suffix left after dropping the longest prefix whose elements satisfy pred, the complement of take_while. Every element is dropped where they all satisfy pred; none are when the first already fails.
xs: List<i32> = List.empty().append(2i32).append(4i32).append(5i32).append(6i32)
xs.drop_while(|x: i32| x < 5i32).length => 2
xs.drop_while(|x: i32| x > 9i32).length => 4
min_by
def min_by(self, less: (T, T) -> bool) -> Option<T>
The minimum element by the less strict-before comparator (the one no element less-precedes), or None for the empty list. On a tie the earliest such element wins. The bound-free counterpart to min.
xs: List<i32> = List.empty().append(3i32).append(1i32).append(2i32)
xs.min_by(|a, b| a < b) => Some(1i32)
empty: List<i32> = List.empty()
empty.min_by(|a, b| a < b) => None
max_by
def max_by(self, less: (T, T) -> bool) -> Option<T>
The maximum element by the less strict-before comparator, or None for the empty list; on a tie the earliest wins. The maximum under less is the minimum under the reversed comparator, and this delegates to min_by.
xs: List<i32> = List.empty().append(1i32).append(3i32).append(2i32)
xs.max_by(|a, b| a < b) => Some(3i32)
empty: List<i32> = List.empty()
empty.max_by(|a, b| a < b) => None
enumerate
def enumerate(self) -> List<Pair<int, T>>
Each element paired with its index, in order: the pure indexed view. each_with_index! is an action and cannot take an index into a pure fold or map chain; this can.
xs: List<string> = List.empty().append("a").append("b")
xs.enumerate().get(1).map(|p| p.first) => Some(1)
xs.enumerate().get(1).map(|p| p.second) => Some("b")
flat_map
def flat_map(self, f: (T) -> List<U>) -> List<U>
The concatenation of f's result lists, in element order.
xs: List<i32> = List.empty().append(1i32).append(2i32)
xs.flat_map(|x: i32| List.empty().append(x).append(x * 10i32)).length => 4
flat_map!
def flat_map!(self, f: (T) -> List<U> [e]) -> List<U> [e]
flat_map with an effectful transform: the concatenation of f's result lists, in element order.
xs: List<i32> = List.empty().append(1i32).append(2i32)
xs.flat_map!(|x: i32| List.empty().append(x).append(x * 10i32)).length => 4
zip_with
def zip_with(self, other: List<U>, f: (T, U) -> V) -> List<V>
Combine two lists element-wise with f, stopping at the shorter length: element i of the result is f(self[i], other[i]). This is zip without the Pair allocation: f consumes both elements at once. O(min of the two lengths).
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
ys: List<i32> = List.empty().append(10i32).append(20i32)
zs: List<i32> = xs.zip_with(ys, |a: i32, b: i32| a + b)
zs.length => 2
zs.get(0) => Some(11i32)
zip
def zip(self, other: List<U>) -> List<Pair<T, U>>
Pair up two lists element-wise, stopping at the shorter length: element i of the result is Pair(first=self[i], second=other[i]). To combine the sides without allocating a Pair, use zip_with.
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
ys: List<string> = List.empty().append("a").append("b")
zs = xs.zip(ys)
zs.length => 2
zs.get(0).map(|p| p.first) => Some(1i32)
zs.get(1).map(|p| p.second) => Some("b")
appendall
def _append_all(self, src: List<U>, acc: List<U>) -> List<U>
Append every element of src onto acc, in order. append is O(1)-amortized, and this O(|src|) walk therefore beats concat's larger constant for the small per-element lists flat_map typically produces.
intersperse
def intersperse(self, sep: T) -> List<T>
sep inserted between every pair of adjacent elements; the empty and single-element lists are returned unchanged. O(n).
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.intersperse(0i32).length => 5
xs.intersperse(0i32).get(1) => Some(0i32)
xs.intersperse(0i32).get(2) => Some(2i32)
chunk
def chunk(self, size: int) -> List<List<T>>
Consecutive sublists of at most size elements, in order; the final chunk is shorter when the length is not a multiple of size. A size of zero or less yields the empty list (no valid chunking). O(n).
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32).append(4i32).append(5i32)
xs.chunk(2).length => 3
xs.chunk(2).first.unwrap_or(List.empty()).length => 2
xs.chunk(2).last.unwrap_or(List.empty()).length => 1
windows
def windows(self, size: int) -> List<List<T>>
Every contiguous sublist of size elements, sliding one element at a time ([1, 2, 3].windows(2) is [[1, 2], [2, 3]]). A size past the length, or zero or less, yields the empty list. O(n log n): n slices, each an O(log n) structural view.
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32).append(4i32)
xs.windows(2).length => 3
xs.windows(3).length => 2
xs.windows(9).length => 0
sort_by
def sort_by(self, less: (T, T) -> bool) -> List<T>
A new list with the same elements ordered by less, a strict-before predicate, true when its first argument should sort ahead of its second. A top-down merge sort: O(n log n) calls to less, stable (elements less ranks equal keep their input order), source unchanged (persistent). The bound-free counterpart to sort, for ordering by a projected key or a custom order without a T: Ord impl.
xs: List<i32> = List.empty().append(1i32).append(3i32).append(2i32)
ys = xs.sort_by(|a, b| a > b)
ys.get(0) => Some(3i32)
ys.get(2) => Some(1i32)
mergeby
def _merge_by(self, other: List<T>, less: (T, T) -> bool) -> List<T>
Merge two less-sorted lists. On a tie (neither side less-precedes the other) the self (left) element wins, keeping a full sort_by stable. The exhausted side's remainder attaches via O(log n) concat (one of the two tails is empty).
impl<T: Eq> List<T>
contains? needs T: Eq; like sort's Ord block below, the bound gets its own inherent block, which leaves the rest of List unbounded.
contains?
def contains?(self, x: T) -> bool
True where some element equals x under Eq<T>: any? with an equality test. Short-circuits on the first match; false for the empty list.
xs: List<i32> = List.empty().append(1i32).append(2i32)
xs.contains?(2i32) => true
xs.contains?(9i32) => false
dedup
def dedup(self) -> List<T>
Consecutive runs of equal elements collapsed to one: [1, 1, 2, 3, 3, 1] becomes [1, 2, 3, 1]; non-adjacent duplicates are kept. Compares under Eq<T> (§10), like contains?. O(n).
xs: List<i32> = List.empty().append(1i32).append(1i32).append(2i32).append(3i32).append(3i32)
xs.dedup().length => 3
xs.dedup().get(0) => Some(1i32)
xs.dedup().get(2) => Some(3i32)
unique
def unique(self) -> List<T>
Every element with all later duplicates removed, keeping the first occurrence of each in order: [1, 2, 1, 3, 2] becomes [1, 2, 3]. Compares under Eq<T> (§10), like contains?. Unlike dedup (which only collapses consecutive runs) this drops non-adjacent duplicates too, at O(n²): each element is checked against those already taken; for a large input a Set is the O(n) alternative.
xs: List<i32> = List.empty().append(1i32).append(2i32).append(1i32).append(3i32).append(2i32)
xs.unique().length => 3
xs.unique().get(0) => Some(1i32)
xs.unique().get(2) => Some(3i32)
index_of
def index_of(self, x: T) -> Option<int>
The index of the first element equal to x under Eq<T>, or None when no element matches. The positional counterpart to contains?: a position with an equality test.
xs: List<i32> = List.empty().append(5i32).append(7i32).append(7i32)
xs.index_of(7i32) => Some(1)
xs.index_of(9i32) => None
impl<T: Ord> List<T>
sort needs T: Ord and the rest of List does not, and it therefore sits in its own bounded inherent block (mixing bounded + unbounded inherent impls on one type is not allowed). A free function would also work, but xs.sort() is the method spelling the surface wants.
min
def min(self) -> Option<T>
The minimum element by Ord, or None for the empty list; on a tie the earliest wins. The bound counterpart to min_by.
xs: List<i32> = List.empty().append(3i32).append(1i32).append(2i32)
xs.min() => Some(1i32)
max
def max(self) -> Option<T>
The maximum element by Ord, or None for the empty list; on a tie the earliest wins. The bound counterpart to max_by.
xs: List<i32> = List.empty().append(3i32).append(1i32).append(2i32)
xs.max() => Some(3i32)
sort
def sort(self) -> List<T>
A new list with the same elements in non-decreasing Ord<T> order, by a top-down merge sort, O(n log n) comparisons. Stable: equal elements keep their input order. The source list is unchanged (persistent).
xs: List<i32> = List.empty().append(3i32).append(1i32).append(2i32)
ys = xs.sort()
ys.get(0) => Some(1i32)
ys.get(2) => Some(3i32)
_merge
def _merge(self, other: List<T>) -> List<T>
Merge two Ord-sorted lists into one sorted list. On a tie the element from self, the left half, whose elements came first, wins, and a full sort therefore remains stable. The exhausted side's remainder attaches through O(log n) concat (one of the two tails is empty).
impl<T: Numeric> List<T>
sum
def sum(self) -> T
The sum of the elements, a fold from T.zero() through add, and the empty list sums to zero. Each step is the element type's native +: fixed widths wrap like the operator, and the lowercase tier's in-band inf/undefined sentinels propagate unchanged.
xs: List<int> = List.empty().append(1).append(2).append(3)
xs.sum() => 6
empty: List<int> = List.empty()
empty.sum() => 0
product
def product(self) -> T
The product of the elements, a fold from T.one() through mul, so the empty list multiplies to one.
xs: List<int> = List.empty().append(2).append(3).append(4)
xs.product() => 24
empty: List<int> = List.empty()
empty.product() => 1
impl<T: Display> Display<List<T>>
to_string
def to_string(self) -> string
[e1, e2, e3]: each element through its own Display, comma-separated; the empty list is []. Written by hand because List is an opaque intrinsic with no variants, and auto-derived Display therefore has nothing to match and traps at runtime.
xs: List<i32> = List.empty().append(1i32).append(2i32).append(3i32)
xs.to_string() => "[1, 2, 3]"