/// A ZipList is a list with a cursor that can be moved forwards and backwards. import gleam/bool import gleam/list pub opaque type ZipList(a) { ZipList(previous: List(a), current: a, next: List(a)) } // Constructors /// Create a new ZipList /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// current(numbers) /// // => 3 /// ``` pub fn new( previous previous: List(a), current current: a, next next: List(a), ) -> ZipList(a) { ZipList(previous, current, next) } /// Create a new ZipList with a single element /// /// ## Examples /// ```gleam /// singleton(1) == new([], 1, []) /// ``` pub fn singleton(current: a) -> ZipList(a) { ZipList(previous: [], current: current, next: []) } /// Create a new ZipList from a list /// Returns `Error(Nil)` if the list is empty /// /// ## Examples /// ```gleam /// from_list([1, 2, 3, 4, 5]) == Ok(new([], 1, [2, 3, 4, 5])) /// from_list([]) == Error(Nil) /// ``` pub fn from_list(list: List(a)) -> Result(ZipList(a), Nil) { case list { [] -> Error(Nil) [head, ..tail] -> Ok(ZipList(previous: [], current: head, next: tail)) } } /// Create a new ZipList from a list, setting the current element to the first element that satisfies the predicate/// /// ## Examples /// ```gleam /// from_list_by([1, 2, 3, 4, 5], fn(x) { x == 3 }) == Ok(new([1, 2], 3, [4, 5])) /// from_list_by([1, 2, 3, 4, 5], fn(x) { x == 6 }) == Error(Nil) /// ``` pub fn from_list_by( list: List(a), predicate: fn(a) -> Bool, ) -> Result(ZipList(a), Nil) { from_list_by_helper([], predicate, list) } fn from_list_by_helper( previous: List(a), predicate: fn(a) -> Bool, remaining: List(a), ) -> Result(ZipList(a), Nil) { case remaining { [] -> Error(Nil) [head, ..tail] -> case predicate(head) { True -> Ok(ZipList(previous: previous, current: head, next: tail)) False -> from_list_by_helper(list.append(previous, [head]), predicate, tail) } } } // Transformations /// Convert a ZipList to a list /// /// ## Examples /// ```gleam /// to_list(new([1, 2], 3, [4, 5])) /// // => [1, 2, 3, 4, 5] /// ``` pub fn to_list(zip_list: ZipList(a)) -> List(a) { list.append(zip_list.previous, [zip_list.current, ..zip_list.next]) } // Cursor operations /// Move the cursor N elements backwards /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// backwards(numbers, 2) == new([], 1, [2, 3, 4, 5]) /// ``` /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// backwards(numbers, 5) == new([], 1, [2, 3, 4, 5]) /// ``` pub fn backwards(zip_list: ZipList(a), times: Int) -> ZipList(a) { use <- bool.guard(when: zip_list.previous == [], return: zip_list) case times { 0 -> zip_list _ -> backwards(previous(zip_list), times - 1) } } /// Move the cursor to the first element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// first(numbers) == new([], 1, [2, 3, 4, 5]) /// ``` /// ```gleam /// let numbers = new([], 1, [2, 3, 4, 5]) /// first(numbers) == new([], 1, [2, 3, 4, 5]) /// ``` pub fn first(zip_list: ZipList(a)) -> ZipList(a) { let #(new_current, previous_tail) = case zip_list.previous { [] -> #(zip_list.current, []) [head, ..tail] -> #(head, tail) } ZipList( previous: [], current: new_current, next: list.append(previous_tail, [zip_list.current, ..zip_list.next]), ) } /// Move the cursor N elements forward /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// forward(numbers, 2) == new([1, 2, 3, 4], 5, []) /// ``` /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// forward(numbers, 5) == new([1, 2, 3, 4, 5], 6, []) /// ``` pub fn forward(zip_list: ZipList(a), times: Int) -> ZipList(a) { use <- bool.guard(when: zip_list.next == [], return: zip_list) case times { 0 -> zip_list _ -> forward(next(zip_list), times - 1) } } /// Move the cursor to the Nth element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// jump(numbers, 1) == new([1], 2, [3, 4, 5]) /// ``` /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// jump(numbers, 5) == new([1, 2, 3, 4], 5, []) /// ``` pub fn jump(zip_list: ZipList(a), index: Int) -> ZipList(a) { let #(first, second) = zip_list |> to_list |> list.split(index) let #(previous, current, next) = case list.reverse(first), second { [], [] -> #(zip_list.previous, zip_list.current, zip_list.next) _, [head, ..tail] -> #(first, head, tail) [head, ..tail], [] -> #(list.reverse(tail), head, second) } ZipList(previous: previous, current: current, next: next) } /// Move the cursor to the last element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// last(numbers) == new([1, 2, 3, 4], 5, []) /// ``` /// ```gleam /// let numbers = new([1, 2, 3, 4], 5, []) /// last(numbers) == new([1, 2, 3, 4], 5, []) /// ``` pub fn last(zip_list: ZipList(a)) -> ZipList(a) { let #(new_current, next_tail) = case list.reverse(zip_list.next) { [] -> #(zip_list.current, []) [head, ..tail] -> #(head, list.reverse(tail)) } ZipList( previous: list.append(zip_list.previous, [zip_list.current, ..next_tail]), current: new_current, next: [], ) } /// Move the cursor to the next element /// Returns the same ZipList if there is no next element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// next(numbers) == new([1, 2, 3], 4, [5]) /// ``` /// ```gleam /// let numbers = new([1, 2, 3, 4, 5], 6, []) /// next(numbers) == new([1, 2, 3, 4, 5], 6, []) /// ``` pub fn next(zip_list: ZipList(a)) -> ZipList(a) { case next_try(zip_list) { Ok(new_zip_list) -> new_zip_list Error(_) -> zip_list } } /// Move the cursor to the next element /// Returns `Error(Nil)` if there is no next element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// next_try(numbers) == Ok(new([1, 2, 3], 4, [5])) /// ``` /// ```gleam /// let numbers = new([1, 2, 3, 4, 5], 6, []) /// next_try(numbers) == Error(Nil) /// ``` pub fn next_try(zip_list: ZipList(a)) -> Result(ZipList(a), Nil) { case zip_list.next { [] -> Error(Nil) [head, ..tail] -> Ok(ZipList( previous: list.append(zip_list.previous, [zip_list.current]), current: head, next: tail, )) } } /// Move the cursor to the next element, wrapping around to the first element if there is no next element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// next_warp(numbers) == new([1, 2, 3], 4, [5]) /// ``` /// ```gleam /// let numbers = new([1, 2, 3, 4, 5], 6, []) /// next_warp(numbers) == new([], 1, [2, 3, 4, 5, 6]) /// ``` pub fn next_warp(zip_list: ZipList(a)) -> ZipList(a) { case next_try(zip_list) { Ok(new_zip_list) -> new_zip_list Error(_) -> first(zip_list) } } /// Move the cursor to the previous element /// Returns the same ZipList if there is no previous element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// previous(numbers) == new([1], 2, [3, 4, 5]) /// ``` /// ```gleam /// let numbers = new([], 1, [2, 3, 4, 5]) /// previous(numbers) == new([], 1, [2, 3, 4, 5]) /// ``` pub fn previous(zip_list: ZipList(a)) -> ZipList(a) { case previous_try(zip_list) { Ok(new_zip_list) -> new_zip_list Error(_) -> zip_list } } /// Move the cursor to the previous element /// Returns `Error(Nil)` if there is no previous element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// previous_try(numbers) == Ok(new([1], 2, [3, 4, 5])) /// ``` /// ```gleam /// let numbers = new([], 1, [2, 3, 4, 5]) /// previous_try(numbers) == Error(Nil) /// ``` pub fn previous_try(zip_list: ZipList(a)) -> Result(ZipList(a), Nil) { let previous_reversed = list.reverse(zip_list.previous) case previous_reversed { [] -> Error(Nil) [head, ..tail] -> Ok( ZipList(previous: list.reverse(tail), current: head, next: [ zip_list.current, ..zip_list.next ]), ) } } /// Move the cursor to the previous element, wrapping around to the last element if there is no previous element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// previous_warp(numbers) == new([1, 2, 3], 4, [5]) /// ``` /// ```gleam /// let numbers = new([], 1, [2, 3, 4, 5]) /// previous_warp(numbers) == new([1, 2, 3, 4], 5, []) /// ``` pub fn previous_warp(zip_list: ZipList(a)) -> ZipList(a) { case previous_try(zip_list) { Ok(new_zip_list) -> new_zip_list Error(_) -> last(zip_list) } } // Modification /// Append a list to the next elements of the ZipList /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// append_next(numbers, [6, 7]) == new([1, 2], 3, [4, 5, 6, 7]) /// ``` pub fn append_next(zip_list: ZipList(a), next: List(a)) -> ZipList(a) { ZipList( previous: zip_list.previous, current: zip_list.current, next: list.append(zip_list.next, next), ) } /// Append a list to the previous elements of the ZipList /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// append_previous(numbers, [-1, 0]) == new([1, 2, -1, 0], 3, [4, 5]) /// ``` pub fn append_previous(zip_list: ZipList(a), previous: List(a)) -> ZipList(a) { ZipList( previous: list.append(zip_list.previous, previous), current: zip_list.current, next: zip_list.next, ) } /// Map a function over the elements of the ZipList, passing a flag indicating if the element is the current element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// current_map(numbers, fn(x, is_current) { /// case is_current { /// True -> x * 10 /// False -> x * 2 /// } /// }) == new([2, 4], 30, [8, 10]) /// ``` pub fn current_map(zip_list: ZipList(a), f: fn(a, Bool) -> a) -> ZipList(a) { let f_with_flag = fn(element) { f(element, False) } ZipList( previous: list.map(zip_list.previous, f_with_flag), current: f(zip_list.current, True), next: list.map(zip_list.next, f_with_flag), ) } /// Filter the elements of the ZipList /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// filter(numbers, int.is_even) == new([2], 4, []) /// ``` /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// filter(numbers, int.is_odd) == new([1], 3, [5]) /// ``` /// ```gleam /// let numbers = new([], 1, []) /// filter(numbers, int.is_even) == Error(Nil) /// ``` pub fn filter(zip_list: ZipList(a), f: fn(a) -> Bool) -> Result(ZipList(a), Nil) { let filtered_previous = list.filter(zip_list.previous, f) let filtered_next = list.filter(zip_list.next, f) let filtered_current = case f(zip_list.current) { True -> Ok(zip_list.current) False -> Error(Nil) } case filtered_current { Ok(current) -> Ok(ZipList( previous: filtered_previous, current: current, next: filtered_next, )) Error(_) -> case list.reverse(filtered_previous), filtered_next { [], [] -> Error(Nil) _, [head, ..tail] -> Ok(ZipList(previous: filtered_previous, current: head, next: tail)) [head, ..tail], [] -> Ok(ZipList( previous: list.reverse(tail), current: head, next: filtered_next, )) } } } /// Map a function over the elements of the ZipList, passing the index of each element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// index_map(numbers, fn(x, i) { x * i }) == new([0, 2], 6, [12, 20]) /// ``` pub fn index_map(zip_list: ZipList(a), f: fn(a, Int) -> b) -> ZipList(b) { let previous_length = list.length(zip_list.previous) ZipList( previous: list.index_map(zip_list.previous, f), current: f(zip_list.current, previous_length), next: list.index_map(zip_list.next, fn(element, index) { f(element, index + previous_length + 1) }), ) } /// Map a function over the elements of the ZipList /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// map(numbers, fn(x) { x * 2 }) == new([2, 4], 6, [8, 10]) /// ``` pub fn map(zip_list: ZipList(a), f: fn(a) -> b) -> ZipList(b) { ZipList( previous: list.map(zip_list.previous, f), current: f(zip_list.current), next: list.map(zip_list.next, f), ) } /// Prepend a list to the next elements of the ZipList /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// prepend_next(numbers, [6, 7]) == new([1, 2], 3, [6, 7, 4, 5]) /// ``` pub fn prepend_next(zip_list: ZipList(a), next: List(a)) -> ZipList(a) { ZipList( previous: zip_list.previous, current: zip_list.current, next: list.append(next, zip_list.next), ) } /// Prepend a list to the previous elements of the ZipList /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// prepend_previous(numbers, [-1, 0]) == new([-1, 0, 1, 2], 3, [4, 5]) /// ``` pub fn prepend_previous(zip_list: ZipList(a), previous: List(a)) -> ZipList(a) { ZipList( previous: list.append(previous, zip_list.previous), current: zip_list.current, next: zip_list.next, ) } /// Remove the current element from the ZipList /// The cursor is moved to the next element if there is one, otherwise to the previous element /// Returns the same ZipList if there is no next or previous element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// remove(numbers) == new([1, 2], 4, [5]) /// ``` /// ```gleam /// let numbers = new([1, 2], 3, []) /// remove(numbers) == new([1], 2, []) /// ``` /// ```gleam /// let numbers = new([], 1, []) /// remove(numbers) == new([], 1, []) /// ``` pub fn remove(zip_list: ZipList(a)) -> ZipList(a) { case remove_try(zip_list) { Ok(new_zip_list) -> new_zip_list Error(_) -> zip_list } } /// Remove the current element from the ZipList /// The cursor is moved to the previous element if there is one, otherwise to the next element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// remove_backwards(numbers) == new([1], 2, [3, 4, 5]) /// ``` /// ```gleam /// let numbers = new([], 1, [2, 3]) /// remove_backwards(numbers) == new([], 2, [3]) /// ``` /// ```gleam /// let numbers = new([], 1, []) /// remove(numbers) == new([], 1, []) /// ``` pub fn remove_backwards(zip_list: ZipList(a)) -> ZipList(a) { case remove_try(zip_list) { Ok(new_zip_list) -> previous(new_zip_list) Error(_) -> zip_list } } /// Remove the current element from the ZipList /// The cursor is moved to the next element if there is one, otherwise to the previous element /// Returns `Error(Nil)` if there is no next or previous element /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// remove_try(numbers) == Ok(new([1, 2], 4, [5])) /// ``` /// ```gleam /// let numbers = new([1, 2], 3, []) /// remove_try(numbers) == Ok(new([1], 2, [])) /// ``` /// ```gleam /// let numbers = new([], 1, []) /// remove_try(numbers) == Error(Nil) /// ``` pub fn remove_try(zip_list: ZipList(a)) -> Result(ZipList(a), Nil) { case zip_list.next, list.reverse(zip_list.previous) { [], [] -> Error(Nil) [head, ..tail], _ -> Ok(ZipList(..zip_list, current: head, next: tail)) _, [head, ..tail] -> Ok(ZipList(..zip_list, current: head, previous: list.reverse(tail))) } } /// Replace the current element of the ZipList /// /// ## Examples /// ```gleam /// let numbers = new([1, 2], 3, [4, 5]) /// replace(numbers, 6) == new([1, 2], 6, [4, 5]) /// ``` pub fn replace(zip_list: ZipList(a), new_current: a) -> ZipList(a) { ZipList(..zip_list, current: new_current) } // Acessors /// Get the next elements of the ZipList /// /// ## Examples /// ```gleam /// new([1, 2], 3, [4, 5]) /// |> get_all_next /// // => [4, 5] /// ``` pub fn get_all_next(zip_list: ZipList(a)) -> List(a) { zip_list.next } /// Get the previous elements of the ZipList /// /// ## Examples /// ```gleam /// new([1, 2], 3, [4, 5]) /// |> get_all_previous /// // => [1, 2] /// ``` pub fn get_all_previous(zip_list: ZipList(a)) -> List(a) { zip_list.previous } /// Get the current element of the ZipList /// /// ## Examples /// ```gleam /// new([1, 2], 3, [4, 5]) /// |> current /// // => 3 /// ``` pub fn current(zip_list: ZipList(a)) -> a { zip_list.current } /// Get the index of the current element in the ZipList /// /// ## Examples /// ```gleam /// new([1, 2], 3, [4, 5]) /// |> index /// // => 2 /// ``` /// ```gleam /// new([], 3, [4, 5]) /// |> index /// // => 0 /// ``` pub fn index(zip_list: ZipList(a)) -> Int { list.length(zip_list.previous) } /// Get the next element of the ZipList /// Returns `Error(Nil)` if there is no next element /// /// ## Examples /// ```gleam /// new([1, 2], 3, [4, 5]) /// |> get_next /// // => Ok(4) /// ``` /// ```gleam /// new([1, 2, 3], 4, []) /// |> get_next /// // => Error(Nil) /// ``` pub fn get_next(zip_list: ZipList(a)) -> Result(a, Nil) { case zip_list.next { [] -> Error(Nil) [head, ..] -> Ok(head) } } /// Get the previous element of the ZipList /// Returns `Error(Nil)` if there is no previous element /// /// ## Examples /// ```gleam /// new([1, 2], 3, [4, 5]) /// |> get_previous /// // => Ok(2) /// ``` /// ```gleam /// new([], 3, [4, 5]) /// |> get_previous /// // => Error(Nil) /// ``` pub fn get_previous(zip_list: ZipList(a)) -> Result(a, Nil) { case list.reverse(zip_list.previous) { [] -> Error(Nil) [head, ..] -> Ok(head) } } /// Check if the current element is the first element of the ZipList /// /// ## Examples /// ```gleam /// new([1, 2], 3, [4, 5]) /// |> is_first /// // => False /// ``` /// ```gleam /// new([], 3, [4, 5]) /// |> is_first /// // => True /// ``` pub fn is_first(zip_list: ZipList(a)) -> Bool { list.is_empty(zip_list.previous) } /// Check if the current element is the last element of the ZipList /// /// ## Examples /// ```gleam /// new([1, 2], 3, [4, 5]) /// |> is_last /// // => False /// ``` /// ```gleam /// new([1, 2], 3, []) /// |> is_last /// // => True /// ``` pub fn is_last(zip_list: ZipList(a)) -> Bool { list.is_empty(zip_list.next) } /// Get the length of the ZipList /// /// ## Examples /// ```gleam /// new([1, 2], 3, [4, 5]) /// |> length /// // => 5 /// ``` pub fn length(zip_list: ZipList(a)) -> Int { list.length(zip_list.previous) + 1 + list.length(zip_list.next) }