Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

std.collections

Functional operations on arrays. All higher-order functions take closures as arguments.

import std.collections

Types

Pair<A, B>

A generic two-element tuple used by zip and enumerate.

pub class Pair<A, B> {
    first: A
    second: B
}

Transforms

map

collections.map<T, U>(arr: [T], f: fn(T) U) [U]

Applies f to each element, returns a new array of results.

let doubled = collections.map([1, 2, 3], (x: int) => x * 2)
// [2, 4, 6]

flat_map

collections.flat_map<T, U>(arr: [T], f: fn(T) [U]) [U]

Maps each element to an array, then flattens the result.

let expanded = collections.flat_map([1, 2, 3], (x: int) => [x, x * 10])
// [1, 10, 2, 20, 3, 30]

filter

collections.filter<T>(arr: [T], f: fn(T) bool) [T]

Returns elements for which f returns true.

let evens = collections.filter([1, 2, 3, 4], (x: int) => x % 2 == 0)
// [2, 4]

Reductions

fold

collections.fold<T, U>(arr: [T], initial: U, f: fn(U, T) U) U

Left fold with an initial accumulator value.

let sum = collections.fold([1, 2, 3], 0, (acc: int, x: int) => acc + x)
// 6

reduce

collections.reduce<T>(arr: [T], f: fn(T, T) T) T

Like fold, but uses the first element as the initial value. Array must be non-empty.

let product = collections.reduce([2, 3, 4], (a: int, b: int) => a * b)
// 24

sum / sum_float

collections.sum(arr: [int]) int
collections.sum_float(arr: [float]) float
collections.sum([10, 20, 30])          // 60
collections.sum_float([1.5, 2.5])      // 4.0

Predicates

any / all

collections.any<T>(arr: [T], f: fn(T) bool) bool
collections.all<T>(arr: [T], f: fn(T) bool) bool
collections.any([1, 2, 3], (x: int) => x > 2)    // true
collections.all([1, 2, 3], (x: int) => x > 0)     // true

count

collections.count<T>(arr: [T], f: fn(T) bool) int

Returns the number of elements satisfying f.

collections.count([1, 2, 3, 4, 5], (x: int) => x > 3)    // 2

Slicing and Reordering

reverse / take / drop

collections.reverse<T>(arr: [T]) [T]
collections.take<T>(arr: [T], n: int) [T]
collections.drop<T>(arr: [T], n: int) [T]
collections.reverse([1, 2, 3])     // [3, 2, 1]
collections.take([1, 2, 3, 4], 2)  // [1, 2]
collections.drop([1, 2, 3, 4], 2)  // [3, 4]

flatten

collections.flatten<T>(arr: [[T]]) [T]

Flattens one level of nesting.

collections.flatten([[1, 2], [3], [4, 5]])    // [1, 2, 3, 4, 5]

Combining

zip

collections.zip<A, B>(a: [A], b: [B]) [Pair<A, B>]

Pairs elements by index. Result length equals the shorter array.

let names = ["alice", "bob"]
let scores = [95, 87]
let pairs = collections.zip(names, scores)
// pairs[0].first == "alice", pairs[0].second == 95

enumerate

collections.enumerate<T>(arr: [T]) [Pair<int, T>]

Pairs each element with its index.

let indexed = collections.enumerate(["a", "b", "c"])
// Pair { first: 0, second: "a" }, Pair { first: 1, second: "b" }, ...

Side Effects

for_each

collections.for_each<T>(arr: [T], f: fn(T))

Calls f on each element. Returns void.

collections.for_each(["alice", "bob"], (name: string) => {
    print(f"hello {name}")
})

Sorting

All sorts return a new array and leave the input untouched. They are stable (equal elements keep their order) and run in O(n log n).

sort_by

collections.sort_by<T>(arr: [T], less: fn(T, T) bool) [T]

Sorts with less(a, b), which returns true when a should come strictly before b.

let desc = collections.sort_by([1, 3, 2], (a: int, b: int) => a > b)    // [3, 2, 1]

sort / sort_floats / sort_strings

collections.sort(arr: [int]) [int]
collections.sort_floats(arr: [float]) [float]
collections.sort_strings(arr: [string]) [string]

Ascending order. Strings sort byte-wise, as strings.compare does.

collections.sort([3, 1, 2])                          // [1, 2, 3]
collections.sort_strings(["pear", "apple", "fig"])   // ["apple", "fig", "pear"]

Fallible Callbacks

collections.try_map<T, U>(arr: [T], f: fn(T) U!) [U]
collections.try_filter<T>(arr: [T], f: fn(T) bool!) [T]
collections.try_fold<T, U>(arr: [T], initial: U, f: fn(U, T) U!) U
collections.try_for_each<T>(arr: [T], f: fn(T)!)

Variants of map, filter, fold and for_each whose callback may raise. The first error stops the walk and propagates to the caller, so a call must be handled with ! or catch. Infallible callbacks are accepted too.

let halves = collections.try_map([4, 8], halve)!    // [2, 4]
let safe = collections.try_map(inputs, halve) catch err {
    let none_left: [int] = []
    none_left
}

Example: Pipeline

import std.collections

fn main() {
    let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    let even_data = collections.filter(data, (x: int) => x % 2 == 0)
    let squared = collections.map(even_data, (x: int) => x * x)
    let total = collections.fold(squared, 0, (acc: int, x: int) => acc + x)
    print(f"sum of even squares: {total}")
    // sum of even squares: 220
}