import gleam/result import gleam/int import gleam/list import gleam/dict.{type Dict} import gleam/option.{type Option, None, Some} import gleam/order // Internal private representation of an Iterator type Action(element) { // Dedicated to Electric Six // https://youtu.be/_30t2dzEgiw?t=162 Stop Continue(element, fn() -> Action(element)) } /// An iterator is a lazily evaluated sequence of element. /// /// Iterators are useful when working with collections that are too large to /// fit in memory (or those that are infinite in size) as they only require the /// elements currently being processed to be in memory. /// /// As a lazy data structure no work is done when an iterator is filtered, /// mapped, etc, instead a new iterator is returned with these transformations /// applied to the stream. Once the stream has all the required transformations /// applied it can be evaluated using functions such as `fold` and `to_list`. /// pub opaque type Iterator(element) { Iterator(continuation: fn() -> Action(element)) } // Public API for iteration pub type Step(element, accumulator) { Next(element: element, accumulator: accumulator) Done } // Shortcut for an empty iterator. fn stop() -> Action(element) { Stop } // Creating Iterators fn do_unfold( initial: acc, f: fn(acc) -> Step(element, acc), ) -> fn() -> Action(element) { fn() { case f(initial) { Next(x, acc) -> Continue(x, do_unfold(acc, f)) Done -> Stop } } } /// Creates an iterator from a given function and accumulator. /// /// The function is called on the accumulator and returns either `Done`, /// indicating the iterator has no more elements, or `Next` which contains a /// new element and accumulator. The element is yielded by the iterator and the /// new accumulator is used with the function to compute the next element in /// the sequence. /// /// ## Examples /// /// ```gleam /// unfold(from: 5, with: fn(n) { /// case n { /// 0 -> Done /// n -> Next(element: n, accumulator: n - 1) /// } /// }) /// |> to_list /// // -> [5, 4, 3, 2, 1] /// ``` /// pub fn unfold( from initial: acc, with f: fn(acc) -> Step(element, acc), ) -> Iterator(element) { initial |> do_unfold(f) |> Iterator } // TODO: test /// Creates an iterator that yields values created by calling a given function /// repeatedly. /// pub fn repeatedly(f: fn() -> element) -> Iterator(element) { unfold(Nil, fn(_) { Next(f(), Nil) }) } /// Creates an iterator that returns the same value infinitely. /// /// ## Examples /// /// ```gleam /// repeat(10) /// |> take(4) /// |> to_list /// // -> [10, 10, 10, 10] /// ``` /// pub fn repeat(x: element) -> Iterator(element) { repeatedly(fn() { x }) } /// Creates an iterator that yields each element from the given list. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3, 4]) /// |> to_list /// // -> [1, 2, 3, 4] /// ``` /// pub fn from_list(list: List(element)) -> Iterator(element) { let yield = fn(acc) { case acc { [] -> Done [head, ..tail] -> Next(head, tail) } } unfold(list, yield) } // Consuming Iterators fn do_transform( continuation: fn() -> Action(a), state: acc, f: fn(acc, a) -> Step(b, acc), ) -> fn() -> Action(b) { fn() { case continuation() { Stop -> Stop Continue(el, next) -> case f(state, el) { Done -> Stop Next(yield, next_state) -> Continue(yield, do_transform(next, next_state, f)) } } } } /// Creates an iterator from an existing iterator /// and a stateful function that may short-circuit. /// /// `f` takes arguments `acc` for current state and `el` for current element from underlying iterator, /// and returns either `Next` with yielded element and new state value, or `Done` to halt the iterator. /// /// ## Examples /// /// Approximate implementation of `index` in terms of `transform`: /// /// ```gleam /// from_list(["a", "b", "c"]) /// |> transform(0, fn(i, el) { Next(#(i, el), i + 1) }) /// |> to_list /// // -> [#(0, "a"), #(1, "b"), #(2, "c")] /// ``` /// pub fn transform( over iterator: Iterator(a), from initial: acc, with f: fn(acc, a) -> Step(b, acc), ) -> Iterator(b) { do_transform(iterator.continuation, initial, f) |> Iterator } fn do_fold( continuation: fn() -> Action(e), f: fn(acc, e) -> acc, accumulator: acc, ) -> acc { case continuation() { Continue(elem, next) -> do_fold(next, f, f(accumulator, elem)) Stop -> accumulator } } /// Reduces an iterator of elements into a single value by calling a given /// function on each element in turn. /// /// If called on an iterator of infinite length then this function will never /// return. /// /// If you do not care about the end value and only wish to evaluate the /// iterator for side effects consider using the `run` function instead. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3, 4]) /// |> fold(from: 0, with: fn(acc, element) { element + acc }) /// // -> 10 /// ``` /// pub fn fold( over iterator: Iterator(e), from initial: acc, with f: fn(acc, e) -> acc, ) -> acc { iterator.continuation |> do_fold(f, initial) } // TODO: test /// Evaluates all elements emitted by the given iterator. This function is useful for when /// you wish to trigger any side effects that would occur when evaluating /// the iterator. /// pub fn run(iterator: Iterator(e)) -> Nil { fold(iterator, Nil, fn(_, _) { Nil }) } /// Evaluates an iterator and returns all the elements as a list. /// /// If called on an iterator of infinite length then this function will never /// return. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3]) /// |> map(fn(x) { x * 2 }) /// |> to_list /// // -> [2, 4, 6] /// ``` /// pub fn to_list(iterator: Iterator(element)) -> List(element) { iterator |> fold([], fn(acc, e) { [e, ..acc] }) |> list.reverse } /// Eagerly accesses the first value of an iterator, returning a `Next` /// that contains the first value and the rest of the iterator. /// /// If called on an empty iterator, `Done` is returned. /// /// ## Examples /// /// ```gleam /// let assert Next(first, rest) = from_list([1, 2, 3, 4]) |> step /// /// first /// // -> 1 /// /// rest |> to_list /// // -> [2, 3, 4] /// ``` /// /// ```gleam /// empty() |> step /// // -> Done /// ``` /// pub fn step(iterator: Iterator(e)) -> Step(e, Iterator(e)) { case iterator.continuation() { Stop -> Done Continue(e, a) -> Next(e, Iterator(a)) } } fn do_take(continuation: fn() -> Action(e), desired: Int) -> fn() -> Action(e) { fn() { case desired > 0 { False -> Stop True -> case continuation() { Stop -> Stop Continue(e, next) -> Continue(e, do_take(next, desired - 1)) } } } } /// Creates an iterator that only yields the first `desired` elements. /// /// If the iterator does not have enough elements all of them are yielded. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3, 4, 5]) /// |> take(up_to: 3) /// |> to_list /// // -> [1, 2, 3] /// ``` /// /// ```gleam /// from_list([1, 2]) /// |> take(up_to: 3) /// |> to_list /// // -> [1, 2] /// ``` /// pub fn take(from iterator: Iterator(e), up_to desired: Int) -> Iterator(e) { iterator.continuation |> do_take(desired) |> Iterator } fn do_drop(continuation: fn() -> Action(e), desired: Int) -> Action(e) { case continuation() { Stop -> Stop Continue(e, next) -> case desired > 0 { True -> do_drop(next, desired - 1) False -> Continue(e, next) } } } /// Evaluates and discards the first N elements in an iterator, returning a new /// iterator. /// /// If the iterator does not have enough elements an empty iterator is /// returned. /// /// This function does not evaluate the elements of the iterator, the /// computation is performed when the iterator is later run. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3, 4, 5]) /// |> drop(up_to: 3) /// |> to_list /// // -> [4, 5] /// ``` /// /// ```gleam /// from_list([1, 2]) /// |> drop(up_to: 3) /// |> to_list /// // -> [] /// ``` /// pub fn drop(from iterator: Iterator(e), up_to desired: Int) -> Iterator(e) { fn() { do_drop(iterator.continuation, desired) } |> Iterator } fn do_map(continuation: fn() -> Action(a), f: fn(a) -> b) -> fn() -> Action(b) { fn() { case continuation() { Stop -> Stop Continue(e, continuation) -> Continue(f(e), do_map(continuation, f)) } } } /// Creates an iterator from an existing iterator and a transformation function. /// /// Each element in the new iterator will be the result of calling the given /// function on the elements in the given iterator. /// /// This function does not evaluate the elements of the iterator, the /// computation is performed when the iterator is later run. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3]) /// |> map(fn(x) { x * 2 }) /// |> to_list /// // -> [2, 4, 6] /// ``` /// pub fn map(over iterator: Iterator(a), with f: fn(a) -> b) -> Iterator(b) { iterator.continuation |> do_map(f) |> Iterator } fn do_map2( continuation1: fn() -> Action(a), continuation2: fn() -> Action(b), with fun: fn(a, b) -> c, ) -> fn() -> Action(c) { fn() { case continuation1() { Stop -> Stop Continue(a, next_a) -> case continuation2() { Stop -> Stop Continue(b, next_b) -> Continue(fun(a, b), do_map2(next_a, next_b, fun)) } } } } /// Combines two interators into a single one using the given function. /// /// If an iterator is longer than the other the extra elements are dropped. /// /// This function does not evaluate the elements of the two iterators, the /// computation is performed when the resulting iterator is later run. /// /// ## Examples /// /// ```gleam /// let first = from_list([1, 2, 3]) /// let second = from_list([4, 5, 6]) /// map2(first, second, fn(x, y) { x + y }) |> to_list /// // -> [5, 7, 9] /// ``` /// /// ```gleam /// let first = from_list([1, 2]) /// let second = from_list(["a", "b", "c"]) /// map2(first, second, fn(i, x) { #(i, x) }) |> to_list /// // -> [#(1, "a"), #(2, "b")] /// ``` /// pub fn map2( iterator1: Iterator(a), iterator2: Iterator(b), with fun: fn(a, b) -> c, ) -> Iterator(c) { do_map2(iterator1.continuation, iterator2.continuation, fun) |> Iterator } fn do_append(first: fn() -> Action(a), second: fn() -> Action(a)) -> Action(a) { case first() { Continue(e, first) -> Continue(e, fn() { do_append(first, second) }) Stop -> second() } } /// Appends two iterators, producing a new iterator. /// /// This function does not evaluate the elements of the iterators, the /// computation is performed when the resulting iterator is later run. /// /// ## Examples /// /// ```gleam /// from_list([1, 2]) /// |> append(from_list([3, 4])) /// |> to_list /// // -> [1, 2, 3, 4] /// ``` /// pub fn append( to first: Iterator(a), suffix second: Iterator(a), ) -> Iterator(a) { fn() { do_append(first.continuation, second.continuation) } |> Iterator } fn do_flatten(flattened: fn() -> Action(Iterator(a))) -> Action(a) { case flattened() { Stop -> Stop Continue(it, next_iterator) -> do_append(it.continuation, fn() { do_flatten(next_iterator) }) } } /// Flattens an iterator of iterators, creating a new iterator. /// /// This function does not evaluate the elements of the iterator, the /// computation is performed when the iterator is later run. /// /// ## Examples /// /// ```gleam /// from_list([[1, 2], [3, 4]]) /// |> map(from_list) /// |> flatten /// |> to_list /// // -> [1, 2, 3, 4] /// ``` /// pub fn flatten(iterator: Iterator(Iterator(a))) -> Iterator(a) { fn() { do_flatten(iterator.continuation) } |> Iterator } /// Joins a list of iterators into a single iterator. /// /// This function does not evaluate the elements of the iterator, the /// computation is performed when the iterator is later run. /// /// ## Examples /// /// ```gleam /// [[1, 2], [3, 4]] /// |> map(from_list) /// |> concat /// |> to_list /// // -> [1, 2, 3, 4] /// ``` /// pub fn concat(iterators: List(Iterator(a))) -> Iterator(a) { flatten(from_list(iterators)) } /// Creates an iterator from an existing iterator and a transformation function. /// /// Each element in the new iterator will be the result of calling the given /// function on the elements in the given iterator and then flattening the /// results. /// /// This function does not evaluate the elements of the iterator, the /// computation is performed when the iterator is later run. /// /// ## Examples /// /// ```gleam /// from_list([1, 2]) /// |> flat_map(fn(x) { from_list([x, x + 1]) }) /// |> to_list /// // -> [1, 2, 2, 3] /// ``` /// pub fn flat_map( over iterator: Iterator(a), with f: fn(a) -> Iterator(b), ) -> Iterator(b) { iterator |> map(f) |> flatten } fn do_filter( continuation: fn() -> Action(e), predicate: fn(e) -> Bool, ) -> Action(e) { case continuation() { Stop -> Stop Continue(e, iterator) -> case predicate(e) { True -> Continue(e, fn() { do_filter(iterator, predicate) }) False -> do_filter(iterator, predicate) } } } /// Creates an iterator from an existing iterator and a predicate function. /// /// The new iterator will contain elements from the first iterator for which /// the given function returns `True`. /// /// This function does not evaluate the elements of the iterator, the /// computation is performed when the iterator is later run. /// /// ## Examples /// /// ```gleam /// import gleam/int /// from_list([1, 2, 3, 4]) /// |> filter(int.is_even) /// |> to_list /// // -> [2, 4] /// ``` /// pub fn filter( iterator: Iterator(a), keeping predicate: fn(a) -> Bool, ) -> Iterator(a) { fn() { do_filter(iterator.continuation, predicate) } |> Iterator } /// Creates an iterator that repeats a given iterator infinitely. /// /// ## Examples /// /// ```gleam /// from_list([1, 2]) /// |> cycle /// |> take(6) /// |> to_list /// // -> [1, 2, 1, 2, 1, 2] /// ``` /// pub fn cycle(iterator: Iterator(a)) -> Iterator(a) { repeat(iterator) |> flatten } /// Creates an iterator of ints, starting at a given start int and stepping by /// one to a given end int. /// /// ## Examples /// /// ```gleam /// range(from: 1, to: 5) |> to_list /// // -> [1, 2, 3, 4, 5] /// ``` /// /// ```gleam /// range(from: 1, to: -2) |> to_list /// // -> [1, 0, -1, -2] /// ``` /// /// ```gleam /// range(from: 0, to: 0) |> to_list /// // -> [0] /// ``` /// pub fn range(from start: Int, to stop: Int) -> Iterator(Int) { case int.compare(start, stop) { order.Eq -> once(fn() { start }) order.Gt -> unfold(from: start, with: fn(current) { case current < stop { False -> Next(current, current - 1) True -> Done } }) order.Lt -> unfold(from: start, with: fn(current) { case current > stop { False -> Next(current, current + 1) True -> Done } }) } } fn do_find( continuation: fn() -> Action(a), f: fn(a) -> Bool, ) -> Result(a, Nil) { case continuation() { Stop -> Error(Nil) Continue(e, next) -> case f(e) { True -> Ok(e) False -> do_find(next, f) } } } /// Finds the first element in a given iterator for which the given function returns /// `True`. /// /// Returns `Error(Nil)` if the function does not return `True` for any of the /// elements. /// /// ## Examples /// /// ```gleam /// find(from_list([1, 2, 3]), fn(x) { x > 2 }) /// // -> Ok(3) /// ``` /// /// ```gleam /// find(from_list([1, 2, 3]), fn(x) { x > 4 }) /// // -> Error(Nil) /// ``` /// /// ```gleam /// find(empty(), fn(_) { True }) /// // -> Error(Nil) /// ``` /// pub fn find( in haystack: Iterator(a), one_that is_desired: fn(a) -> Bool, ) -> Result(a, Nil) { haystack.continuation |> do_find(is_desired) } fn do_index( continuation: fn() -> Action(element), next: Int, ) -> fn() -> Action(#(element, Int)) { fn() { case continuation() { Stop -> Stop Continue(e, continuation) -> Continue(#(e, next), do_index(continuation, next + 1)) } } } /// Wraps values yielded from an iterator with indices, starting from 0. /// /// ## Examples /// /// ```gleam /// from_list(["a", "b", "c"]) |> index |> to_list /// // -> [#("a", 0), #("b", 1), #("c", 2)] /// ``` /// pub fn index(over iterator: Iterator(element)) -> Iterator(#(element, Int)) { iterator.continuation |> do_index(0) |> Iterator } /// Creates an iterator that inifinitely applies a function to a value. /// /// ## Examples /// /// ```gleam /// iterate(1, fn(n) { n * 3 }) |> take(5) |> to_list /// // -> [1, 3, 9, 27, 81] /// ``` /// pub fn iterate( from initial: element, with f: fn(element) -> element, ) -> Iterator(element) { unfold(initial, fn(element) { Next(element, f(element)) }) } fn do_take_while( continuation: fn() -> Action(element), predicate: fn(element) -> Bool, ) -> fn() -> Action(element) { fn() { case continuation() { Stop -> Stop Continue(e, next) -> case predicate(e) { False -> Stop True -> Continue(e, do_take_while(next, predicate)) } } } } /// Creates an iterator that yields elements while the predicate returns `True`. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3, 2, 4]) /// |> take_while(satisfying: fn(x) { x < 3 }) /// |> to_list /// // -> [1, 2] /// ``` /// pub fn take_while( in iterator: Iterator(element), satisfying predicate: fn(element) -> Bool, ) -> Iterator(element) { iterator.continuation |> do_take_while(predicate) |> Iterator } fn do_drop_while( continuation: fn() -> Action(element), predicate: fn(element) -> Bool, ) -> Action(element) { case continuation() { Stop -> Stop Continue(e, next) -> case predicate(e) { False -> Continue(e, next) True -> do_drop_while(next, predicate) } } } /// Creates an iterator that drops elements while the predicate returns `True`, /// and then yields the remaining elements. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3, 4, 2, 5]) /// |> drop_while(satisfying: fn(x) { x < 4 }) /// |> to_list /// // -> [4, 2, 5] /// ``` /// pub fn drop_while( in iterator: Iterator(element), satisfying predicate: fn(element) -> Bool, ) -> Iterator(element) { fn() { do_drop_while(iterator.continuation, predicate) } |> Iterator } fn do_scan( continuation: fn() -> Action(element), f: fn(acc, element) -> acc, accumulator: acc, ) -> fn() -> Action(acc) { fn() { case continuation() { Stop -> Stop Continue(el, next) -> { let accumulated = f(accumulator, el) Continue(accumulated, do_scan(next, f, accumulated)) } } } } /// Creates an iterator from an existing iterator and a stateful function. /// /// Specifically, this behaves like `fold`, but yields intermediate results. /// /// ## Examples /// /// ```gleam /// // Generate a sequence of partial sums /// from_list([1, 2, 3, 4, 5]) /// |> scan(from: 0, with: fn(acc, el) { acc + el }) /// |> to_list /// // -> [1, 3, 6, 10, 15] /// ``` /// pub fn scan( over iterator: Iterator(element), from initial: acc, with f: fn(acc, element) -> acc, ) -> Iterator(acc) { iterator.continuation |> do_scan(f, initial) |> Iterator } fn do_zip( left: fn() -> Action(a), right: fn() -> Action(b), ) -> fn() -> Action(#(a, b)) { fn() { case left() { Stop -> Stop Continue(el_left, next_left) -> case right() { Stop -> Stop Continue(el_right, next_right) -> Continue(#(el_left, el_right), do_zip(next_left, next_right)) } } } } /// Zips two iterators together, emitting values from both /// until the shorter one runs out. /// /// ## Examples /// /// ```gleam /// from_list(["a", "b", "c"]) /// |> zip(range(20, 30)) /// |> to_list /// // -> [#("a", 20), #("b", 21), #("c", 22)] /// ``` /// pub fn zip(left: Iterator(a), right: Iterator(b)) -> Iterator(#(a, b)) { do_zip(left.continuation, right.continuation) |> Iterator } // Result of collecting a single chunk by key type Chunk(element, key) { AnotherBy(List(element), key, element, fn() -> Action(element)) LastBy(List(element)) } fn next_chunk( continuation: fn() -> Action(element), f: fn(element) -> key, previous_key: key, current_chunk: List(element), ) -> Chunk(element, key) { case continuation() { Stop -> LastBy(list.reverse(current_chunk)) Continue(e, next) -> { let key = f(e) case key == previous_key { True -> next_chunk(next, f, key, [e, ..current_chunk]) False -> AnotherBy(list.reverse(current_chunk), key, e, next) } } } } fn do_chunk( continuation: fn() -> Action(element), f: fn(element) -> key, previous_key: key, previous_element: element, ) -> Action(List(element)) { case next_chunk(continuation, f, previous_key, [previous_element]) { LastBy(chunk) -> Continue(chunk, stop) AnotherBy(chunk, key, el, next) -> Continue(chunk, fn() { do_chunk(next, f, key, el) }) } } /// Creates an iterator that emits chunks of elements /// for which `f` returns the same value. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 2, 3, 4, 4, 6, 7, 7]) /// |> chunk(by: fn(n) { n % 2 }) /// |> to_list /// // -> [[1], [2, 2], [3], [4, 4, 6], [7, 7]] /// ``` /// pub fn chunk( over iterator: Iterator(element), by f: fn(element) -> key, ) -> Iterator(List(element)) { fn() { case iterator.continuation() { Stop -> Stop Continue(e, next) -> do_chunk(next, f, f(e), e) } } |> Iterator } // Result of collecting a single sized chunk type SizedChunk(element) { Another(List(element), fn() -> Action(element)) Last(List(element)) NoMore } fn next_sized_chunk( continuation: fn() -> Action(element), left: Int, current_chunk: List(element), ) -> SizedChunk(element) { case continuation() { Stop -> case current_chunk { [] -> NoMore remaining -> Last(list.reverse(remaining)) } Continue(e, next) -> { let chunk = [e, ..current_chunk] case left > 1 { False -> Another(list.reverse(chunk), next) True -> next_sized_chunk(next, left - 1, chunk) } } } } fn do_sized_chunk( continuation: fn() -> Action(element), count: Int, ) -> fn() -> Action(List(element)) { fn() { case next_sized_chunk(continuation, count, []) { NoMore -> Stop Last(chunk) -> Continue(chunk, stop) Another(chunk, next_element) -> Continue(chunk, do_sized_chunk(next_element, count)) } } } /// Creates an iterator that emits chunks of given size. /// /// If the last chunk does not have `count` elements, it is yielded /// as a partial chunk, with less than `count` elements. /// /// For any `count` less than 1 this function behaves as if it was set to 1. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3, 4, 5, 6]) /// |> sized_chunk(into: 2) /// |> to_list /// // -> [[1, 2], [3, 4], [5, 6]] /// ``` /// /// ```gleam /// from_list([1, 2, 3, 4, 5, 6, 7, 8]) /// |> sized_chunk(into: 3) /// |> to_list /// // -> [[1, 2, 3], [4, 5, 6], [7, 8]] /// ``` /// pub fn sized_chunk( over iterator: Iterator(element), into count: Int, ) -> Iterator(List(element)) { iterator.continuation |> do_sized_chunk(count) |> Iterator } fn do_intersperse( continuation: fn() -> Action(element), separator: element, ) -> Action(element) { case continuation() { Stop -> Stop Continue(e, next) -> { let next_interspersed = fn() { do_intersperse(next, separator) } Continue(separator, fn() { Continue(e, next_interspersed) }) } } } /// Creates an iterator that yields the given `elem` element /// between elements emitted by the underlying iterator. /// /// ## Examples /// /// ```gleam /// empty() /// |> intersperse(with: 0) /// |> to_list /// // -> [] /// ``` /// /// ```gleam /// from_list([1]) /// |> intersperse(with: 0) /// |> to_list /// // -> [1] /// ``` /// /// ```gleam /// from_list([1, 2, 3, 4, 5]) /// |> intersperse(with: 0) /// |> to_list /// // -> [1, 0, 2, 0, 3, 0, 4, 0, 5] /// ``` /// pub fn intersperse( over iterator: Iterator(element), with elem: element, ) -> Iterator(element) { fn() { case iterator.continuation() { Stop -> Stop Continue(e, next) -> Continue(e, fn() { do_intersperse(next, elem) }) } } |> Iterator } fn do_any( continuation: fn() -> Action(element), predicate: fn(element) -> Bool, ) -> Bool { case continuation() { Stop -> False Continue(e, next) -> case predicate(e) { True -> True False -> do_any(next, predicate) } } } /// Returns `True` if any element emitted by the iterator satisfies the given predicate, /// `False` otherwise. /// /// This function short-circuits once it finds a satisfying element. /// /// An empty iterator results in `False`. /// /// ## Examples /// /// ```gleam /// empty() /// |> any(fn(n) { n % 2 == 0 }) /// // -> False /// ``` /// /// ```gleam /// from_list([1, 2, 5, 7, 9]) /// |> any(fn(n) { n % 2 == 0 }) /// // -> True /// ``` /// /// ```gleam /// from_list([1, 3, 5, 7, 9]) /// |> any(fn(n) { n % 2 == 0 }) /// // -> False /// ``` /// pub fn any( in iterator: Iterator(element), satisfying predicate: fn(element) -> Bool, ) -> Bool { iterator.continuation |> do_any(predicate) } fn do_all( continuation: fn() -> Action(element), predicate: fn(element) -> Bool, ) -> Bool { case continuation() { Stop -> True Continue(e, next) -> case predicate(e) { True -> do_all(next, predicate) False -> False } } } /// Returns `True` if all elements emitted by the iterator satisfy the given predicate, /// `False` otherwise. /// /// This function short-circuits once it finds a non-satisfying element. /// /// An empty iterator results in `True`. /// /// ## Examples /// /// ```gleam /// empty() /// |> all(fn(n) { n % 2 == 0 }) /// // -> True /// ``` /// /// ```gleam /// from_list([2, 4, 6, 8]) /// |> all(fn(n) { n % 2 == 0 }) /// // -> True /// ``` /// /// ```gleam /// from_list([2, 4, 5, 8]) /// |> all(fn(n) { n % 2 == 0 }) /// // -> False /// ``` /// pub fn all( in iterator: Iterator(element), satisfying predicate: fn(element) -> Bool, ) -> Bool { iterator.continuation |> do_all(predicate) } fn update_group_with( el: element, ) -> fn(Option(List(element))) -> List(element) { fn(maybe_group) { case maybe_group { Some(group) -> [el, ..group] None -> [el] } } } fn group_updater( f: fn(element) -> key, ) -> fn(Dict(key, List(element)), element) -> Dict(key, List(element)) { fn(groups, elem) { groups |> dict.update(f(elem), update_group_with(elem)) } } /// Returns a `Dict(k, List(element))` of elements from the given iterator /// grouped with the given key function. /// /// The order within each group is preserved from the iterator. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3, 4, 5, 6]) /// |> group(by: fn(n) { n % 3 }) /// // -> dict.from_list([#(0, [3, 6]), #(1, [1, 4]), #(2, [2, 5])]) /// ``` /// pub fn group( in iterator: Iterator(element), by key: fn(element) -> key, ) -> Dict(key, List(element)) { iterator |> fold(dict.new(), group_updater(key)) |> dict.map_values(fn(_, group) { list.reverse(group) }) } /// This function acts similar to fold, but does not take an initial state. /// Instead, it starts from the first yielded element /// and combines it with each subsequent element in turn using the given function. /// The function is called as `f(accumulator, current_element)`. /// /// Returns `Ok` to indicate a successful run, and `Error` if called on an empty iterator. /// /// ## Examples /// /// ```gleam /// from_list([]) /// |> reduce(fn(acc, x) { acc + x }) /// // -> Error(Nil) /// ``` /// /// ```gleam /// from_list([1, 2, 3, 4, 5]) /// |> reduce(fn(acc, x) { acc + x }) /// // -> Ok(15) /// ``` /// pub fn reduce( over iterator: Iterator(e), with f: fn(e, e) -> e, ) -> Result(e, Nil) { case iterator.continuation() { Stop -> Error(Nil) Continue(e, next) -> do_fold(next, f, e) |> Ok } } /// Returns the last element in the given iterator. /// /// Returns `Error(Nil)` if the iterator is empty. /// /// This function runs in linear time. /// /// ## Examples /// /// ```gleam /// empty() |> last /// // -> Error(Nil) /// ``` /// /// ```gleam /// range(1, 10) |> last /// // -> Ok(9) /// ``` /// pub fn last(iterator: Iterator(element)) -> Result(element, Nil) { iterator |> reduce(fn(_, elem) { elem }) } /// Creates an iterator that yields no elements. /// /// ## Examples /// /// ```gleam /// empty() |> to_list /// // -> [] /// ``` /// pub fn empty() -> Iterator(element) { Iterator(stop) } /// Creates an iterator that yields exactly one element provided by calling the given function. /// /// ## Examples /// /// ```gleam /// once(fn() { 1 }) |> to_list /// // -> [1] /// ``` /// pub fn once(f: fn() -> element) -> Iterator(element) { fn() { Continue(f(), stop) } |> Iterator } /// Creates an iterator that yields the given element exactly once. /// /// ## Examples /// /// ```gleam /// single(1) |> to_list /// // -> [1] /// ``` /// pub fn single(elem: element) -> Iterator(element) { once(fn() { elem }) } fn do_interleave( current: fn() -> Action(element), next: fn() -> Action(element), ) -> Action(element) { case current() { Stop -> next() Continue(e, next_other) -> Continue(e, fn() { do_interleave(next, next_other) }) } } /// Creates an iterator that alternates between the two given iterators /// until both have run out. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3, 4]) /// |> interleave(from_list([11, 12, 13, 14])) /// |> to_list /// // -> [1, 11, 2, 12, 3, 13, 4, 14] /// ``` /// /// ```gleam /// from_list([1, 2, 3, 4]) /// |> interleave(from_list([100])) /// |> to_list /// // -> [1, 100, 2, 3, 4] /// ``` /// pub fn interleave( left: Iterator(element), with right: Iterator(element), ) -> Iterator(element) { fn() { do_interleave(left.continuation, right.continuation) } |> Iterator } fn do_fold_until( continuation: fn() -> Action(e), f: fn(acc, e) -> list.ContinueOrStop(acc), accumulator: acc, ) -> acc { case continuation() { Stop -> accumulator Continue(elem, next) -> case f(accumulator, elem) { list.Continue(accumulator) -> do_fold_until(next, f, accumulator) list.Stop(accumulator) -> accumulator } } } /// Like `fold`, `fold_until` reduces an iterator of elements into a single value by calling a given /// function on each element in turn, but uses `list.ContinueOrStop` to determine /// whether or not to keep iterating. /// /// If called on an iterator of infinite length then this function will only ever /// return if the function returns `list.Stop`. /// /// ## Examples /// /// ```gleam /// import gleam/list /// /// let f = fn(acc, e) { /// case e { /// _ if e < 4 -> list.Continue(e + acc) /// _ -> list.Stop(acc) /// } /// } /// /// from_list([1, 2, 3, 4]) /// |> fold_until(from: acc, with: f) /// // -> 6 /// ``` /// pub fn fold_until( over iterator: Iterator(e), from initial: acc, with f: fn(acc, e) -> list.ContinueOrStop(acc), ) -> acc { iterator.continuation |> do_fold_until(f, initial) } fn do_try_fold( over continuation: fn() -> Action(a), with f: fn(acc, a) -> Result(acc, err), from accumulator: acc, ) -> Result(acc, err) { case continuation() { Stop -> Ok(accumulator) Continue(elem, next) -> { use accumulator <- result.try(f(accumulator, elem)) do_try_fold(next, f, accumulator) } } } /// A variant of fold that might fail. /// /// The folding function should return `Result(accumulator, error)`. /// If the returned value is `Ok(accumulator)` try_fold will try the next value in the iterator. /// If the returned value is `Error(error)` try_fold will stop and return that error. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3, 4]) /// |> try_fold(0, fn(acc, i) { /// case i < 3 { /// True -> Ok(acc + i) /// False -> Error(Nil) /// } /// }) /// // -> Error(Nil) /// ``` /// pub fn try_fold( over iterator: Iterator(e), from initial: acc, with f: fn(acc, e) -> Result(acc, err), ) -> Result(acc, err) { iterator.continuation |> do_try_fold(f, initial) } /// Returns the first element yielded by the given iterator, if it exists, /// or `Error(Nil)` otherwise. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3]) |> first /// // -> Ok(1) /// ``` /// /// ```gleam /// empty() |> first /// // -> Error(Nil) /// ``` pub fn first(from iterator: Iterator(e)) -> Result(e, Nil) { case iterator.continuation() { Stop -> Error(Nil) Continue(e, _) -> Ok(e) } } /// Returns nth element yielded by the given iterator, where `0` means the first element. /// /// If there are not enough elements in the iterator, `Error(Nil)` is returned. /// /// For any `index` less than `0` this function behaves as if it was set to `0`. /// /// ## Examples /// /// ```gleam /// from_list([1, 2, 3, 4]) |> at(2) /// // -> Ok(3) /// ``` /// /// ```gleam /// from_list([1, 2, 3, 4]) |> at(4) /// // -> Error(Nil) /// ``` /// /// ```gleam /// empty() |> at(0) /// // -> Error(Nil) /// ``` /// pub fn at(in iterator: Iterator(e), get index: Int) -> Result(e, Nil) { iterator |> drop(index) |> first } fn do_length(over continuation: fn() -> Action(e), with length: Int) -> Int { case continuation() { Stop -> length Continue(_, next) -> do_length(next, length + 1) } } /// Counts the number of elements in the given iterator. /// /// This function has to traverse the entire iterator to count its elements, /// so it runs in linear time. /// /// ## Examples /// /// ```gleam /// empty() |> length /// // -> 0 /// ``` /// /// ```gleam /// from_list([1, 2, 3, 4]) |> length /// // -> 4 /// ``` /// pub fn length(over iterator: Iterator(e)) -> Int { iterator.continuation |> do_length(0) } /// Traverse an iterator, calling a function on each element. /// /// ## Examples /// /// ```gleam /// empty() |> each(io.println) /// // -> Nil /// ``` /// /// ```gleam /// from_list(["Tom", "Malory", "Louis"]) |> each(io.println) /// // -> Nil /// // Tom /// // Malory /// // Louis /// ``` /// pub fn each(over iterator: Iterator(a), with f: fn(a) -> b) -> Nil { iterator |> map(f) |> run } /// Add a new element to the start of an iterator. /// /// This function is for use with `use` expressions, to replicate the behaviour /// of the `yield` keyword found in other languages. /// /// ## Examples /// /// ```gleam /// let iterator = { /// use <- yield(1) /// use <- yield(2) /// use <- yield(3) /// empty() /// } /// iterator |> to_list /// // -> [1, 2, 3] /// ``` /// pub fn yield(element: a, next: fn() -> Iterator(a)) -> Iterator(a) { Iterator(fn() { Continue(element, next().continuation) }) }