Current section

Files

Jump to
gens src gens.gleam
Raw

src/gens.gleam

import gleam/int
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/order.{type Order}
pub type Generator(a, s) {
Generator(state: s, next: fn(s) -> Option(#(a, s)))
}
/// Returns the next element of a generator and the updated gen
/// ```gleam
/// let counter =
/// Generator(state: 0, next: fn(c) { Some(#(c, c + 1)) })
/// case get(counter).0 {
/// None -> Nil
/// Some(x) -> echo x // -> 0
/// }
/// ```
pub fn get(g: Generator(a, s)) -> #(Option(a), Generator(a, s)) {
case g.next(g.state) {
None -> #(None, g)
Some(#(x, s)) -> #(Some(x), Generator(state: s, next: g.next))
}
}
/// Tail recursive function for gen
fn gen_acc(
g: Generator(a, s),
n: Int,
ls: List(a),
) -> #(List(a), Generator(a, s)) {
case n > 0 {
False -> #(list.reverse(ls), g)
True ->
case get(g) {
#(None, _) -> #(list.reverse(ls), g)
#(Some(x), g2) -> gen_acc(g2, n - 1, [x, ..ls])
}
}
}
/// Generates at most n elements and returns the updated gen
/// ```gleam
/// let counter =
/// Generator(state: 0, next: fn(c) { Some(#(c, c + 1)) })
/// let #(nums, _) = gen(counter, 5)
/// echo nums // -> [0, 1, 2, 3, 4]
/// ```
pub fn gen(g: Generator(a, s), n: Int) -> #(List(a), Generator(a, s)) {
gen_acc(g, n, [])
}
/// Combines two generators into one, advancing them separately
/// ```gleam
/// let two_powers =
/// Generator(state: 1, next: fn(p) { Some(#(p, p * 2)) })
/// let bellow_three =
/// Generator(state: 0, next: fn(n) { Some(#(n < 3, n + 1)) })
///
/// let z = combine(two_powers, bellow_three)
/// let #(res, _) = gen(z, 5)
/// echo res
/// // -> [#(1, True), #(2, True), #(4, True), #(8, False), #(16, False)]
/// ```
pub fn combine(
g1: Generator(a, s1),
g2: Generator(b, s2),
) -> Generator(#(a, b), #(s1, s2)) {
Generator(state: #(g1.state, g2.state), next: fn(state) {
let #(state1, state2) = state
let res1 = g1.next(state1)
let res2 = g2.next(state2)
case res1, res2 {
Some(#(x, state3)), Some(#(y, state4)) ->
Some(#(#(x, y), #(state3, state4)))
_, _ -> None
}
})
}
/// Generates the lists elements
/// ```gleam
/// let gen_fruit = from_list(["apple", "banana", "orange"])
/// let #(fruit1, gen_fruit2) = get(gen_fruit)
/// echo fruit1
/// // -> Some("apple")
/// ```
/// ```gleam
/// let #(fruit2, gen_fruit3) = get(gen_fruit2)
/// echo fruit2
/// // -> Some("banana")
/// ```
/// ```gleam
/// let #(fruit3, gen_fruit4) = get(gen_fruit3)
/// echo fruit3
/// // -> Some("orange")
/// ```
/// ```gleam
/// let #(fruit4, _) = get(gen_fruit4)
/// echo fruit4
/// // -> None
/// ```
pub fn from_list(l: List(a)) -> Generator(a, List(a)) {
Generator(state: l, next: fn(ls) {
case ls {
[] -> None
[x, ..rest] -> Some(#(x, rest))
}
})
}
/// Generates the lists elements on repeat
/// ```gleam
/// let gen_fruit = list_repeat(["apple", "banana", "orange"])
/// let #(fruits, _) = gen(gen_fruit, 5)
/// echo fruits
/// // -> ["apple", "banana", "orange", "apple", "banana"]
/// ```
pub fn list_repeat(l: List(a)) -> Generator(a, #(List(a), List(a))) {
Generator(state: #(l, l), next: fn(list_pair) {
let #(current, original) = list_pair
case current {
[] -> None
[x, ..rest] ->
case rest {
[] -> Some(#(x, #(original, original)))
_ -> Some(#(x, #(rest, original)))
}
}
})
}
/// Generates elements from the lazy list
/// ```gleam
/// let infinite_list = new() |> drop(3) |> map(fn(x) { x * 10 })
/// let ten_gen = from_lazy_list(infinite_list)
/// let #(res, _) = gen(ten_gen, 10)
/// echo res
/// // -> [30, 40, 50, 60, 70, 80, 90, 100, 110, 120]
/// ```
pub fn from_lazy_list(l: LazyList(a)) -> Generator(a, LazyList(a)) {
Generator(state: l, next: fn(ls) {
case take(ls, 1) {
[] -> None
[x, ..] -> Some(#(x, ls |> drop(1)))
}
})
}
/// Merges two `sorted` generators into one
/// ```gleam
/// let counter1 = Generator(0, fn(c) { Some(#(c, c + 1)) })
/// let counter2 = Generator(0, fn(c) { Some(#(c, c + 2)) })
/// let merged = merge(counter1, counter2, int.compare)
/// merged
/// |> gen(8)
/// |> echo
/// // -> #([0, 0, 1, 2, 2, 3, 4, 4], Generator(#(5, 6), fn() { ... }))
/// ```
pub fn merge(
g1: Generator(a, s1),
g2: Generator(a, s2),
comp: fn(a, a) -> Order,
) -> Generator(a, #(s1, s2)) {
Generator(state: #(g1.state, g2.state), next: fn(s) {
let #(state1, state2) = s
case g1.next(state1), g2.next(state2) {
Some(#(x1, st1)), Some(#(x2, st2)) ->
case comp(x1, x2) {
order.Gt -> Some(#(x2, #(state1, st2)))
_ -> Some(#(x1, #(st1, state2)))
}
Some(#(x1, st1)), None -> Some(#(x1, #(st1, state2)))
None, Some(#(x2, st2)) -> Some(#(x2, #(state1, st2)))
None, None -> None
}
})
}
pub opaque type LazyList(a) {
LazyList(Int, fn(Int) -> a, fn(Int) -> Bool)
}
/// Default LazyList for the list of `natural numbers` [0..]
/// ```gleam
/// new() |> take(5)
/// // -> [0, 1, 2, 3, 4]
/// ```
pub fn new() -> LazyList(Int) {
LazyList(0, fn(x) { x }, fn(_) { True })
}
/// `Tail recursive` function for **take**
fn take_acc(
index: Int,
step: Int,
total: Int,
fmap: fn(Int) -> a,
filt: fn(Int) -> Bool,
acc: List(a),
) {
case step < total {
False -> acc |> list.reverse
True ->
case filt(index) {
False -> take_acc(index + 1, step, total, fmap, filt, acc)
True ->
case step >= 0 {
False -> take_acc(index + 1, step + 1, total, fmap, filt, acc)
True ->
take_acc(index + 1, step + 1, total, fmap, filt, [
fmap(index),
..acc
])
}
}
}
}
/// **Takes** a `finite` number of elements from a LazyList
/// ```gleam
/// take(new(), 5)
/// // -> [0, 1, 2, 3, 4]
/// ```
pub fn take(ga: LazyList(a), n: Int) -> List(a) {
case ga {
LazyList(index, amap, afilt) -> take_acc(index, 0, n, amap, afilt, [])
}
}
/// **Maps** each element of the takeerated list
/// ```gleam
/// new()
/// |> map(fn(x) { x + 3 })
/// |> map(int.to_string)
/// |> take(5)
/// // -> ["3", "4", "5", "6", "7"]
/// ```
pub fn map(ga: LazyList(a), f: fn(a) -> b) -> LazyList(b) {
case ga {
LazyList(index, amap, afilt) -> LazyList(index, fn(n) { f(amap(n)) }, afilt)
}
}
/// **Filters** elements from the takeerated list
/// ```gleam
/// new()
/// |> filter(fn(x) { x % 2 == 0 })
/// |> filter(fn(x) { x != 4 })
/// |> take(5)
/// // -> [0, 2, 6, 8, 10]
/// ```
pub fn filter(ga: LazyList(a), f: fn(a) -> Bool) -> LazyList(a) {
case ga {
LazyList(index, amap, afilt) ->
LazyList(index, amap, fn(n) { afilt(n) && f(amap(n)) })
}
}
// `Tail recursive` function used for skipping takeerated elements
fn advance(index: Int, steps: Int, filt: fn(Int) -> Bool) -> Int {
case steps >= 0 {
False -> index - 1
True ->
case filt(index) {
False -> advance(index + 1, steps, filt)
True -> advance(index + 1, steps - 1, filt)
}
}
}
/// **Drops** the first n elements of a LazyList
/// ```gleam
/// new() // [0, 1, 2, 3, 4..]
/// |> drop(4) // [4, 5, 6, 7..]
/// |> filter(int.is_even) // [4, 6, 8..]
/// |> take(5)
/// // -> [4, 6, 8, 10, 12]
/// ```
/// ```gleam
/// new() // [0, 1, 2, 3, 4..]
/// |> filter(int.is_even) // [0, 2, 4, 6, 8..]
/// |> drop(4) // [8, 10, 12..]
/// |> take(5)
/// // -> [8, 10, 12, 14, 16]
/// ```
pub fn drop(ga: LazyList(a), steps: Int) -> LazyList(a) {
case steps >= 0 {
False -> ga
True ->
case ga {
LazyList(index, amap, afilt) ->
LazyList(advance(index, steps, afilt), amap, afilt)
}
}
}
/// **Zips** two LazyLists into one
/// - The resulting index is the maximum of the two takes
/// - The filters get combined
/// - For separate indexes, do `list.zip(take(g1, n), take(g2, n))`
/// ```gleam
/// let g1 = new() |> map(fn(x) { x + 2 })
/// let g2 = new() |> filter(int.is_even)
/// zip(g1, g2)
/// |> take(3)
/// // -> [#(2, 0), #(4, 2), #(6, 4)]
/// ```
pub fn zip(ga: LazyList(a), gb: LazyList(b)) -> LazyList(#(a, b)) {
let LazyList(aindex, amap, afilt) = ga
let LazyList(bindex, bmap, bfilt) = gb
LazyList(int.max(aindex, bindex), fn(n) { #(amap(n), bmap(n)) }, fn(n) {
afilt(n) && bfilt(n)
})
}
/// **Zips** a list with an infinite list
/// ```gleam
/// ["a", "b", "c"]
/// |> list_zip(new())
/// // -> [#("a", 0), #("b", 1), #("c", 2)]
/// ```
pub fn list_zip(la: List(a), gb: LazyList(b)) -> List(#(a, b)) {
list.zip(la, take(gb, list.length(la)))
}