/// Purely-functional, singly-linked lists. /// A list of type `List` is either `null` or an optional pair of a value of type `T` and a tail, itself of type `List`. /// /// To use this library, import it using: /// /// ```motoko name=initialize /// import List "mo:base/List"; /// ``` import Array "Array"; import Iter "IterType"; import Option "Option"; import Order "Order"; import Result "Result"; module { // A singly-linked list consists of zero or more _cons cells_, wherein // each cell contains a single list element (the cell's _head_), and a pointer to the // remainder of the list (the cell's _tail_). public type List = ?(T, List); /// Create an empty list. /// /// Example: /// ```motoko include=initialize /// List.nil() // => null /// ``` /// /// Runtime: O(1) /// /// Space: O(1) public func nil() : List = null; /// Check whether a list is empty and return true if the list is empty. /// /// Example: /// ```motoko include=initialize /// List.isNil(null) // => true /// ``` /// /// Runtime: O(1) /// /// Space: O(1) public func isNil(l : List) : Bool { switch l { case null { true }; case _ { false } } }; /// Add `x` to the head of `list`, and return the new list. /// /// Example: /// ```motoko include=initialize /// List.push(0, null) // => ?(0, null); /// ``` /// /// Runtime: O(1) /// /// Space: O(1) public func push(x : T, l : List) : List = ?(x, l); /// Return the last element of the list, if present. /// Example: /// ```motoko include=initialize /// List.last(?(0, ?(1, null))) // => ?1 /// ``` /// /// Runtime: O(size) /// /// Space: O(1) public func last(l : List) : ?T { switch l { case null { null }; case (?(x, null)) { ?x }; case (?(_, t)) { last(t) } } }; /// Remove the head of the list, returning the optioned head and the tail of the list in a pair. /// Returns `(null, null)` if the list is empty. /// /// Example: /// ```motoko include=initialize /// List.pop(?(0, ?(1, null))) // => (?0, ?(1, null)) /// ``` /// /// Runtime: O(1) /// /// Space: O(1) public func pop(l : List) : (?T, List) { switch l { case null { (null, null) }; case (?(h, t)) { (?h, t) } } }; /// Return the length of the list. /// /// Example: /// ```motoko include=initialize /// List.size(?(0, ?(1, null))) // => 2 /// ``` /// /// Runtime: O(size) /// /// Space: O(1) public func size(l : List) : Nat { func rec(l : List, n : Nat) : Nat { switch l { case null { n }; case (?(_, t)) { rec(t, n + 1) } } }; rec(l, 0) }; /// Access any item in a list, zero-based. /// /// NOTE: Indexing into a list is a linear operation, and usually an /// indication that a list might not be the best data structure /// to use. /// /// Example: /// ```motoko include=initialize /// List.get(?(0, ?(1, null)), 1) // => ?1 /// ``` /// /// Runtime: O(size) /// /// Space: O(1) public func get(l : List, n : Nat) : ?T { switch (n, l) { case (_, null) { null }; case (0, (?(h, _))) { ?h }; case (_, (?(_, t))) { get(t, n - 1) } } }; /// Reverses the list. /// /// Example: /// ```motoko include=initialize /// List.reverse(?(0, ?(1, ?(2, null)))) // => ?(2, ?(1, ?(0, null))) /// ``` /// /// Runtime: O(size) /// /// Space: O(size) public func reverse(l : List) : List { func rec(l : List, r : List) : List { switch l { case null { r }; case (?(h, t)) { rec(t, ?(h, r)) } } }; rec(l, null) }; /// Call the given function for its side effect, with each list element in turn. /// /// Example: /// ```motoko include=initialize /// var sum = 0; /// List.iterate(?(0, ?(1, ?(2, null))), func n { sum += n }); /// sum // => 3 /// ``` /// /// Runtime: O(size) /// /// Space: O(size) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func iterate(l : List, f : T -> ()) { switch l { case null { () }; case (?(h, t)) { f(h); iterate(t, f) } } }; /// Call the given function `f` on each list element and collect the results /// in a new list. /// /// Example: /// ```motoko include=initialize /// import Nat = "mo:base/Nat" /// List.map(?(0, ?(1, ?(2, null))), Nat.toText) // => ?("0", ?("1", ?("2", null)) /// ``` /// /// Runtime: O(size) /// /// Space: O(size) /// *Runtime and space assumes that `f` runs in O(1) time and space. public func map(l : List, f : T -> U) : List { switch l { case null { null }; case (?(h, t)) { ?(f(h), map(t, f)) } } }; /// Create a new list with only those elements of the original list for which /// the given function (often called the _predicate_) returns true. /// /// Example: /// ```motoko include=initialize /// List.filter(?(0, ?(1, ?(2, null))), func n { n != 1 }) // => ?(0, ?(2, null)) /// ``` /// /// Runtime: O(size) /// /// Space: O(size) public func filter(l : List, f : T -> Bool) : List { switch l { case null { null }; case (?(h, t)) { if (f(h)) { ?(h, filter(t, f)) } else { filter(t, f) } } } }; /// Create two new lists from the results of a given function (`f`). /// The first list only includes the elements for which the given /// function `f` returns true and the second list only includes /// the elements for which the function returns false. /// /// Example: /// ```motoko include=initialize /// List.partition(?(0, ?(1, ?(2, null))), func n { n != 1 }) // => (?(0, ?(2, null)), ?(1, null)) /// ``` /// /// Runtime: O(size) /// /// Space: O(size) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func partition(l : List, f : T -> Bool) : (List, List) { switch l { case null { (null, null) }; case (?(h, t)) { if (f(h)) { // call f in-order let (l, r) = partition(t, f); (?(h, l), r) } else { let (l, r) = partition(t, f); (l, ?(h, r)) } } } }; /// Call the given function on each list element, and collect the non-null results /// in a new list. /// /// Example: /// ```motoko include=initialize /// List.mapFilter( /// ?(1, ?(2, ?(3, null))), /// func n { /// if (n > 1) { /// ?(n * 2); /// } else { /// null /// } /// } /// ) // => ?(4, ?(6, null)) /// ``` /// /// Runtime: O(size) /// /// Space: O(size) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func mapFilter(l : List, f : T -> ?U) : List { switch l { case null { null }; case (?(h, t)) { switch (f(h)) { case null { mapFilter(t, f) }; case (?h_) { ?(h_, mapFilter(t, f)) } } } } }; /// Maps a Result-returning function `f` over a List and returns either /// the first error or a list of successful values. /// /// Example: /// ```motoko include=initialize /// List.mapResult( /// ?(1, ?(2, ?(3, null))), /// func n { /// if (n > 0) { /// #ok(n * 2); /// } else { /// #err("Some element is zero") /// } /// } /// ); // => #ok ?(2, ?(4, ?(6, null)) /// ``` /// /// Runtime: O(size) /// /// Space: O(size) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func mapResult(xs : List, f : T -> Result.Result) : Result.Result, E> { func go(xs : List, acc : List) : Result.Result, E> { switch xs { case null { #ok(acc) }; case (?(head, tail)) { switch (f(head)) { case (#err(err)) { #err(err) }; case (#ok(ok)) { go(tail, ?(ok, acc)) } } } } }; Result.mapOk(go(xs, null), func(xs : List) : List = reverse(xs)) }; /// Append the elements from the reverse of one list, 'l', to another list, 'm'. /// /// Example: /// ```motoko include=initialize /// List.revAppend( /// ?(2, ?(1, ?(0, null))), /// ?(3, ?(4, ?(5, null))) /// ); // => ?(0, ?(1, ?(2, ?(3, ?(4, ?(5, null)))))) /// ``` /// /// Runtime: O(size(l)) /// /// Space: O(size(l)) func revAppend(l : List, m : List) : List { switch l { case null { m }; case (?(h, t)) { revAppend(t, ?(h, m)) } } }; /// Append the elements from one list to another list. /// /// Example: /// ```motoko include=initialize /// List.append( /// ?(0, ?(1, ?(2, null))), /// ?(3, ?(4, ?(5, null))) /// ) // => ?(0, ?(1, ?(2, ?(3, ?(4, ?(5, null)))))) /// ``` /// /// Runtime: O(size(l)) /// /// Space: O(size(l)) public func append(l : List, m : List) : List { revAppend(reverse(l), m) }; /// Flatten, or concatenate, a list of lists as a list. /// /// Example: /// ```motoko include=initialize /// List.flatten( /// ?(?(0, ?(1, ?(2, null))), /// ?(?(3, ?(4, ?(5, null))), /// null)) /// ); // => ?(0, ?(1, ?(2, ?(3, ?(4, ?(5, null)))))) /// ``` /// /// Runtime: O(size*size) /// /// Space: O(size*size) public func flatten(l : List>) : List { //FIXME: this is quadratic, not linear https://github.com/dfinity/motoko-base/issues/459 foldLeft, List>(l, null, func(a, b) { append(a, b) }) }; /// Returns the first `n` elements of the given list. /// If the given list has fewer than `n` elements, this function returns /// a copy of the full input list. /// /// Example: /// ```motoko include=initialize /// List.take( /// ?(0, ?(1, ?(2, null))), /// 2 /// ); // => ?(0, ?(1, null)) /// ``` /// /// Runtime: O(n) /// /// Space: O(n) public func take(l : List, n : Nat) : List { switch (l, n) { case (_, 0) { null }; case (null, _) { null }; case (?(h, t), m) { ?(h, take(t, m - 1)) } } }; /// Drop the first `n` elements from the given list. /// /// Example: /// ```motoko include=initialize /// List.drop( /// ?(0, ?(1, ?(2, null))), /// 2 /// ); // => ?(2, null) /// ``` /// /// Runtime: O(n) /// /// Space: O(1) public func drop(l : List, n : Nat) : List { switch (l, n) { case (l_, 0) { l_ }; case (null, _) { null }; case ((?(_, t)), m) { drop(t, m - 1) } } }; /// Collapses the elements in `list` into a single value by starting with `base` /// and progessively combining elements into `base` with `combine`. Iteration runs /// left to right. /// /// Example: /// ```motoko include=initialize /// import Nat "mo:base/Nat"; /// /// List.foldLeft( /// ?(1, ?(2, ?(3, null))), /// "", /// func (acc, x) { acc # Nat.toText(x)} /// ) // => "123" /// ``` /// /// Runtime: O(size(list)) /// /// Space: O(1) heap, O(1) stack /// /// *Runtime and space assumes that `combine` runs in O(1) time and space. public func foldLeft(list : List, base : S, combine : (S, T) -> S) : S { switch list { case null { base }; case (?(h, t)) { foldLeft(t, combine(base, h), combine) } } }; /// Collapses the elements in `buffer` into a single value by starting with `base` /// and progessively combining elements into `base` with `combine`. Iteration runs /// right to left. /// /// Example: /// ```motoko include=initialize /// import Nat "mo:base/Nat"; /// /// List.foldRight( /// ?(1, ?(2, ?(3, null))), /// "", /// func (x, acc) { Nat.toText(x) # acc} /// ) // => "123" /// ``` /// /// Runtime: O(size(list)) /// /// Space: O(1) heap, O(size(list)) stack /// /// *Runtime and space assumes that `combine` runs in O(1) time and space. public func foldRight(list : List, base : S, combine : (T, S) -> S) : S { switch list { case null { base }; case (?(h, t)) { combine(h, foldRight(t, base, combine)) } } }; /// Return the first element for which the given predicate `f` is true, /// if such an element exists. /// /// Example: /// ```motoko include=initialize /// /// List.find( /// ?(1, ?(2, ?(3, null))), /// func n { n > 1 } /// ); // => ?2 /// ``` /// /// Runtime: O(size) /// /// Space: O(1) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func find(l : List, f : T -> Bool) : ?T { switch l { case null { null }; case (?(h, t)) { if (f(h)) { ?h } else { find(t, f) } } } }; /// Return true if there exists a list element for which /// the given predicate `f` is true. /// /// Example: /// ```motoko include=initialize /// /// List.some( /// ?(1, ?(2, ?(3, null))), /// func n { n > 1 } /// ) // => true /// ``` /// /// Runtime: O(size(list)) /// /// Space: O(1) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func some(l : List, f : T -> Bool) : Bool { switch l { case null { false }; case (?(h, t)) { f(h) or some(t, f) } } }; /// Return true if the given predicate `f` is true for all list /// elements. /// /// Example: /// ```motoko include=initialize /// /// List.all( /// ?(1, ?(2, ?(3, null))), /// func n { n > 1 } /// ); // => false /// ``` /// /// Runtime: O(size) /// /// Space: O(1) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func all(l : List, f : T -> Bool) : Bool { switch l { case null { true }; case (?(h, t)) { f(h) and all(t, f) } } }; /// Merge two ordered lists into a single ordered list. /// This function requires both list to be ordered as specified /// by the given relation `lessThanOrEqual`. /// /// Example: /// ```motoko include=initialize /// /// List.merge( /// ?(1, ?(2, ?(4, null))), /// ?(2, ?(4, ?(6, null))), /// func (n1, n2) { n1 <= n2 } /// ); // => ?(1, ?(2, ?(2, ?(4, ?(4, ?(6, null))))))), /// ``` /// /// Runtime: O(size(l1) + size(l2)) /// /// Space: O(size(l1) + size(l2)) /// /// *Runtime and space assumes that `lessThanOrEqual` runs in O(1) time and space. // TODO: replace by merge taking a compare : (T, T) -> Order.Order function? public func merge(l1 : List, l2 : List, lessThanOrEqual : (T, T) -> Bool) : List { switch (l1, l2) { case (null, _) { l2 }; case (_, null) { l1 }; case (?(h1, t1), ?(h2, t2)) { if (lessThanOrEqual(h1, h2)) { ?(h1, merge(t1, l2, lessThanOrEqual)) } else { ?(h2, merge(l1, t2, lessThanOrEqual)) } } } }; private func compareAux(l1 : List, l2 : List, compare : (T, T) -> Order.Order) : Order.Order { switch (l1, l2) { case (null, null) { #equal }; case (null, _) { #less }; case (_, null) { #greater }; case (?(h1, t1), ?(h2, t2)) { switch (compare(h1, h2)) { case (#equal) { compareAux(t1, t2, compare) }; case other { other } } } } }; /// Compare two lists using lexicographic ordering specified by argument function `compare`. /// /// Example: /// ```motoko include=initialize /// import Nat "mo:base/Nat"; /// /// List.compare( /// ?(1, ?(2, null)), /// ?(3, ?(4, null)), /// Nat.compare /// ) // => #less /// ``` /// /// Runtime: O(size(l1)) /// /// Space: O(1) /// /// *Runtime and space assumes that argument `compare` runs in O(1) time and space. public func compare(l1 : List, l2 : List, compare : (T, T) -> Order.Order) : Order.Order { compareAux(l1, l2, compare); }; private func equalAux(l1 : List, l2 : List, equal : (T, T) -> Bool) : Bool { switch (l1, l2) { case (?(h1, t1), ?(h2, t2)) { equal(h1, h2) and equalAux(t1, t2, equal) }; case (null, null) { true }; case _ { false }; } }; /// Compare two lists for equality using the argument function `equal` to determine equality of their elements. /// /// Example: /// ```motoko include=initialize /// import Nat "mo:base/Nat"; /// /// List.equal( /// ?(1, ?(2, null)), /// ?(3, ?(4, null)), /// Nat.equal /// ); // => false /// ``` /// /// Runtime: O(size(l1)) /// /// Space: O(1) /// /// *Runtime and space assumes that argument `equal` runs in O(1) time and space. public func equal(l1 : List, l2 : List, equal : (T, T) -> Bool) : Bool { equalAux(l1, l2, equal); }; /// Generate a list based on a length and a function that maps from /// a list index to a list element. /// /// Example: /// ```motoko include=initialize /// List.tabulate( /// 3, /// func n { n * 2 } /// ) // => ?(0, ?(2, (?4, null))) /// ``` /// /// Runtime: O(n) /// /// Space: O(n) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func tabulate(n : Nat, f : Nat -> T) : List { var i = 0; var l : List = null; while (i < n) { l := ?(f(i), l); i += 1 }; reverse(l) }; /// Create a list with exactly one element. /// /// Example: /// ```motoko include=initialize /// List.make( /// 0 /// ) // => ?(0, null) /// ``` /// /// Runtime: O(1) /// /// Space: O(1) public func make(x : T) : List = ?(x, null); /// Create a list of the given length with the same value in each position. /// /// Example: /// ```motoko include=initialize /// List.replicate( /// 3, /// 0 /// ) // => ?(0, ?(0, ?(0, null))) /// ``` /// /// Runtime: O(n) /// /// Space: O(n) public func replicate(n : Nat, x : T) : List { var i = 0; var l : List = null; while (i < n) { l := ?(x, l); i += 1 }; l }; /// Create a list of pairs from a pair of lists. /// /// If the given lists have different lengths, then the created list will have a /// length equal to the length of the smaller list. /// /// Example: /// ```motoko include=initialize /// List.zip( /// ?(0, ?(1, ?(2, null))), /// ?("0", ?("1", null)), /// ) // => ?((0, "0"), ?((1, "1"), null)) /// ``` /// /// Runtime: O(min(size(xs), size(ys))) /// /// Space: O(min(size(xs), size(ys))) public func zip(xs : List, ys : List) : List<(T, U)> = zipWith(xs, ys, func(x, y) { (x, y) }); /// Create a list in which elements are created by applying function `f` to each pair `(x, y)` of elements /// occuring at the same position in list `xs` and list `ys`. /// /// If the given lists have different lengths, then the created list will have a /// length equal to the length of the smaller list. /// /// Example: /// ```motoko include=initialize /// import Nat = "mo:base/Nat"; /// import Char = "mo:base/Char"; /// /// List.zipWith( /// ?(0, ?(1, ?(2, null))), /// ?('a', ?('b', null)), /// func (n, c) { Nat.toText(n) # Char.toText(c) } /// ) // => ?("0a", ?("1b", null)) /// ``` /// /// Runtime: O(min(size(xs), size(ys))) /// /// Space: O(min(size(xs), size(ys))) /// /// *Runtime and space assumes that `f` runs in O(1) time and space. public func zipWith( xs : List, ys : List, f : (T, U) -> V ) : List { switch (pop(xs)) { case (null, _) { null }; case (?x, xt) { switch (pop(ys)) { case (null, _) { null }; case (?y, yt) { push(f(x, y), zipWith(xt, yt, f)) } } } } }; /// Split the given list at the given zero-based index. /// /// Example: /// ```motoko include=initialize /// List.split( /// 2, /// ?(0, ?(1, ?(2, null))) /// ) // => (?(0, ?(1, null)), ?(2, null)) /// ``` /// /// Runtime: O(n) /// /// Space: O(n) public func split(n : Nat, xs : List) : (List, List) { if (n == 0) { (null, xs) } else { func rec(n : Nat, xs : List) : (List, List) { switch (pop(xs)) { case (null, _) { (null, null) }; case (?h, t) { if (n == 1) { (make(h), t) } else { let (l, r) = rec(n - 1, t); (push(h, l), r) } } } }; rec(n, xs) } }; /// Split the given list into chunks of length `n`. /// The last chunk will be shorter if the length of the given list /// does not divide by `n` evenly. /// /// Example: /// ```motoko include=initialize /// List.chunks( /// 2, /// ?(0, ?(1, ?(2, ?(3, ?(4, null))))) /// ) /// /* => ?(?(0, ?(1, null)), /// ?(?(2, ?(3, null)), /// ?(?(4, null), /// null))) /// */ /// ``` /// /// Runtime: O(size) /// /// Space: O(size) public func chunks(n : Nat, xs : List) : List> { let (l, r) = split(n, xs); if (isNil(l)) { null } else { push>(l, chunks(n, r)) } }; /// Convert an array into a list. /// /// Example: /// ```motoko include=initialize /// List.fromArray([ 0, 1, 2, 3, 4]) /// // => ?(0, ?(1, ?(2, ?(3, ?(4, null))))) /// ``` /// /// Runtime: O(size) /// /// Space: O(size) public func fromArray(xs : [T]) : List { Array.foldRight>( xs, null, func(x : T, ys : List) : List { push(x, ys) } ) }; /// Convert a mutable array into a list. /// /// Example: /// ```motoko include=initialize /// List.fromVarArray([var 0, 1, 2, 3, 4]) /// // => ?(0, ?(1, ?(2, ?(3, ?(4, null))))) /// ``` /// /// Runtime: O(size) /// /// Space: O(size) public func fromVarArray(xs : [var T]) : List = fromArray(Array.freeze(xs)); /// Create an array from a list. /// Example: /// ```motoko include=initialize /// List.toArray(?(0, ?(1, ?(2, ?(3, ?(4, null)))))) /// // => [0, 1, 2, 3, 4] /// ``` /// /// Runtime: O(size) /// /// Space: O(size) public func toArray(xs : List) : [T] { let length = size(xs); var list = xs; Array.tabulate( length, func(i) { let popped = pop(list); list := popped.1; switch (popped.0) { case null { loop { assert false } }; case (?x) x } } ) }; /// Create a mutable array from a list. /// Example: /// ```motoko include=initialize /// List.toVarArray(?(0, ?(1, ?(2, ?(3, ?(4, null)))))) /// // => [var 0, 1, 2, 3, 4] /// ``` /// /// Runtime: O(size) /// /// Space: O(size) public func toVarArray(xs : List) : [var T] = Array.thaw(toArray(xs)); /// Create an iterator from a list. /// Example: /// ```motoko include=initialize /// var sum = 0; /// for (n in List.toIter(?(0, ?(1, ?(2, ?(3, ?(4, null))))))) { /// sum += n; /// }; /// sum /// // => 10 /// ``` /// /// Runtime: O(1) /// /// Space: O(1) public func toIter(xs : List) : Iter.Iter { var state = xs; object { public func next() : ?T = switch state { case (?(hd, tl)) { state := tl; ?hd }; case _ null } } } }