diff --git a/list/list.mbt b/list/list.mbt index 80609d856..480f3eb0d 100644 --- a/list/list.mbt +++ b/list/list.mbt @@ -818,3 +818,193 @@ test "lookup" { @assertion.assert_eq(ls.lookup(3), Some("c"))? @assertion.assert_eq(ls.lookup(4), None)? } + +/// Returns the first element of list that satisfies f. +/// +/// # Example +/// +/// ``` +/// println(List::[1, 3, 5, 8].find(fn(element) -> Bool { element % 2 == 0})) +/// // output: 8 +/// ``` +pub fn find[T](self : List[T], f : (T) -> Bool) -> T { + match self { + Nil => abort("find: no matching element") + Cons(element, list) => if f(element) { element } else { list.find(f) } + } +} + +test "find" { + @assertion.assert_eq( + List::[1, 3, 5, 8].find(fn(element) -> Bool { element % 2 == 0 }), + 8, + )? +} + +/// Find the first element in the list that satisfies f. +/// +/// # Example +/// +/// ``` +/// println(List::[1, 3, 5, 8].find(fn(element) -> Bool { element % 2 == 0})) +/// // output: Some(8) +/// ``` +pub fn find_option[T](self : List[T], f : (T) -> Bool) -> Option[T] { + match self { + Nil => None + Cons(element, list) => + if f(element) { + Some(element) + } else { + list.find_option(f) + } + } +} + +test "find_option" { + @assertion.assert_eq( + List::[1, 3, 5, 8].find_option(fn(element) -> Bool { element % 2 == 0 }), + Some(8), + )? + @assertion.assert_eq( + List::[1, 3, 5, 7].find_option(fn(element) -> Bool { element % 2 == 0 }), + None, + )? +} + +/// Find the first element in the list that satisfies f and passes the index as an argument. +/// +/// # Example +/// +/// ``` +/// println(List::[1, 3, 5, 8].findi(fn(element, i) -> Bool { (element % 2 == 0) && (i == 3) })) +/// // output: 8 +/// ``` +pub fn findi[T](self : List[T], f : (T, Int) -> Bool) -> T { + fn find(list : List[T], index : Int) -> T { + match list { + Nil => abort("findi: no matching element") + Cons(element, list) => + if f(element, index) { + element + } else { + find(list, index + 1) + } + } + } + + find(self, 0) +} + +test "findi" { + @assertion.assert_eq( + List::[1, 3, 5, 8].findi( + fn(element, i) -> Bool { element % 2 == 0 && i == 3 }, + ), + 8, + )? +} + +/// Find the first element in the list that satisfies f and passes the index as an argument. +/// +/// # Example +/// +/// ``` +/// println(List::[1, 3, 5, 8].findi_option(fn(element) -> Bool { (element % 2 == 0) && (i == 3) })) +/// // output: Some(8) +/// +/// println(List::[1, 3, 8, 5].findi_option(fn(element) -> Bool { (element % 2 == 0) && (i == 3) })) +/// // output: None +/// ``` +pub fn findi_option[T](self : List[T], f : (T, Int) -> Bool) -> Option[T] { + fn find_option(list : List[T], index : Int) -> Option[T] { + match list { + Nil => None + Cons(element, list) => + if f(element, index) { + Some(element) + } else { + find_option(list, index + 1) + } + } + } + + find_option(self, 0) +} + +test "findi_option" { + @assertion.assert_eq( + List::[1, 3, 5, 8].findi_option( + fn(element, i) -> Bool { element % 2 == 0 && i == 3 }, + ), + Some(8), + )? + @assertion.assert_eq( + List::[1, 3, 8, 5].findi_option( + fn(element, i) -> Bool { element % 2 == 0 && i == 3 }, + ), + None, + )? +} + +/// Returns true if list starts with prefix. +/// +/// # Example +/// +/// ``` +/// println(List::[1, 2, 3, 4, 5].is_prefix(List::[1, 2, 3])) +/// // output: true +/// ``` +pub fn is_prefix[T : Eq](self : List[T], prefix : List[T]) -> Bool { + match prefix { + Nil => true + Cons(h, t) => + match self { + Nil => false + Cons(h1, t1) => h == h1 && t1.is_prefix(t) + } + } +} + +test "is_prefix" { + @assertion.assert_true(List::[1, 2, 3, 4, 5].is_prefix(List::[1, 2, 3]))? + @assertion.assert_false(List::[1, 2, 3, 4, 5].is_prefix(List::[3, 2, 3]))? +} + +/// Compares two lists for equality. +/// +/// # Example +/// +/// ``` +/// println(List::[1, 2, 3].equal(List::[1, 2, 3])) +/// // output: true +/// ``` +pub fn equal[T : Eq](self : List[T], other : List[T]) -> Bool { + match (self, other) { + (Nil, Nil) => true + (Cons(h, t), Cons(h1, t1)) => if h == h1 { t.equal(t1) } else { false } + _ => false + } +} + +test "equal" { + @assertion.assert_true(List::[1, 2, 3].equal(List::[1, 2, 3]))? + @assertion.assert_false(List::[1, 2, 3].equal(List::[1, 3, 3]))? +} + +/// Returns true if list ends with suffix. +/// +/// # Example +/// +/// ``` +/// println(List::[1, 2, 3, 4, 5].is_suffix(List::[3, 4, 5])) +/// // output: true +/// ``` +pub fn is_suffix[T : Eq](self : List[T], suffix : List[T]) -> Bool { + self.reverse().is_prefix(suffix.reverse()) +} + +test "is_suffix" { + @assertion.assert_true(List::[1, 2, 3, 4, 5].is_suffix(List::[3, 4, 5]))? + @assertion.assert_false(List::[1, 2, 3, 4, 5].is_suffix(List::[3, 4, 6]))? +} diff --git a/stack/moon.pkg.json b/stack/moon.pkg.json new file mode 100644 index 000000000..04e95142a --- /dev/null +++ b/stack/moon.pkg.json @@ -0,0 +1,7 @@ +{ + "import": [ + { "path": "moonbitlang/core/assertion", "alias": "assertion" }, + { "path": "moonbitlang/core/list", "alias": "list" }, + { "path": "moonbitlang/core/array", "alias": "array" } + ] +} \ No newline at end of file diff --git a/stack/stack.mbt b/stack/stack.mbt new file mode 100644 index 000000000..0b3e462de --- /dev/null +++ b/stack/stack.mbt @@ -0,0 +1,487 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This module provides a List-based Stack implementation. + +struct Stack[T] { + mut elements : List[T] + mut len : Int +} derive(Debug, Show, Default) + +/// Create an empty stack +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::new() +/// println(s) // output: {elements: Nil, len: 0} +/// ``` +pub fn Stack::new[T]() -> Stack[T] { + { elements: Nil, len: 0 } +} + +test "new" { + let s : Stack[Unit] = Stack::new() + @assertion.assert_eq(s.elements, Nil)? +} + +/// Create a stack based on all elements in list +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_list(Cons(1, Cons(2, Cons(3, Nil)))) +/// println(s) // output: {elements: Cons(3, Cons(2, Cons(1, Nil))), len: 3} +/// ``` +pub fn Stack::from_list[T](list : List[T]) -> Stack[T] { + { elements: list.reverse(), len: list.length() } +} + +test "from_list" { + let s : Stack[Int] = Stack::from_list(Cons(1, Cons(2, Cons(3, Nil)))) + @assertion.assert_eq(s.elements, Cons(3, Cons(2, Cons(1, Nil))))? + @assertion.assert_eq(s.len, 3)? +} + +/// Create a stack based on all elements in array +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_array([1, 2, 3])) +/// println(s) // output: {elements: Cons(3, Cons(2, Cons(1, Nil))), len: 3} +/// ``` +pub fn Stack::from_array[T](array : Array[T]) -> Stack[T] { + { elements: List::from_array(array).reverse(), len: array.length() } +} + +test "from_array" { + let s : Stack[Int] = Stack::[1, 2, 3] + @assertion.assert_eq(s.elements, Cons(3, Cons(2, Cons(1, Nil))))? + @assertion.assert_eq(s.len, 3)? +} + +/// Create a stack based on another stack +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_list(Cons(1, Cons(2, Cons(3, Nil)))) +/// let s1: Stack[Int] = Stack::from_stack(s) +/// println(s1) // output: {elements: Cons(3, Cons(2, Cons(1, Nil))), len: 3} +/// ``` +pub fn Stack::from_stack[T](other : Stack[T]) -> Stack[T] { + { ..other, } +} + +test "from_stack" { + let s : Stack[Int] = Stack::from_list(Cons(3, Cons(2, Cons(1, Nil)))) + let s1 : Stack[Int] = Stack::from_stack(s) + @assertion.assert_eq(s1.elements, s.elements)? +} + +/// Clear all elements in Stack +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_list(Cons(1, Cons(2, Cons(3, Nil)))) +/// s.clear() +/// println(s) //output: {elements: Nil, len: 0} +/// ``` +pub fn clear[T](self : Stack[T]) { + self.elements = Nil + self.len = 0 +} + +test "clear" { + let s : Stack[Int] = Stack::from_list(Cons(1, Cons(2, Cons(3, Nil)))) + s.clear() + @assertion.assert_eq(s.elements, Nil)? +} + +/// Same as the `clear()`, but returns an cleared stack +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_list(Cons(1, Cons(2, Cons(3, Nil)))).return_with_clear() +/// println(s) //output: {elements: Nil, len: 0} +/// ``` +pub fn return_with_clear[T](self : Stack[T]) -> Stack[T] { + self.clear() + self +} + +test "return_with_clear" { + let s : Stack[Int] = Stack::from_list(Cons(1, Cons(2, Cons(3, Nil)))).return_with_clear() + @assertion.assert_eq(s.elements, Nil)? +} + +/// Returns a copy of the current Stack +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_list(Cons(1, Cons(2, Cons(3, Nil)))) +/// let s1: Stack[Int] = s.copy() +/// println(s) // output: {elements: Cons(1, Cons(2, Cons(3, Nil))), len: 3} +/// ``` +pub fn copy[T](self : Stack[T]) -> Stack[T] { + { ..self, } +} + +test "copy" { + let s : Stack[Int] = Stack::from_list(Cons(1, Cons(2, Cons(3, Nil)))) + let s1 : Stack[Int] = s.copy() + @assertion.assert_eq(s1.elements, s.elements)? +} + +/// Push an element into the stack +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::new() +/// s.push(1) +/// println(s) // output: {elements: Cons(1, Nil), len: 1} +/// ``` +pub fn push[T](self : Stack[T], x : T) -> Unit { + self.elements = Cons(x, self.elements) + self.len = self.len + 1 +} + +test "push" { + let s : Stack[Int] = Stack::new() + s.push(1) + @assertion.assert_eq(s.elements, Cons(1, Nil))? + @assertion.assert_eq(s.len, 1)? +} + +/// Push a list into the stack +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::new() +/// s.push_list(Cons(1, Cons(2, Cons(3, Nil)))) +/// println(s) // output: {elements: Cons(3, Cons(2, Cons(1, Nil))), len: 3} +/// ``` +pub fn push_list[T](self : Stack[T], list : List[T]) -> Unit { + list.iter(fn(i) -> Unit { self.push(i) }) +} + +test "push_list" { + let s : Stack[Int] = Stack::new() + s.push_list(Cons(1, Cons(2, Cons(3, Nil)))) + @assertion.assert_eq(s.elements, Cons(3, Cons(2, Cons(1, Nil))))? + @assertion.assert_eq(s.len, 3)? +} + +/// Push other stack into the current stack +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_list(Cons(1, (Cons 2, Cons(3, Nil))) +/// let s1: Stack[Int] = Stack::new() +/// s1.push_stack(s) +/// println(s1) // output: {elements: Cons(1, Cons(2, Cons(3, Nil))), len: 3} +/// ``` +pub fn push_stack[T](self : Stack[T], stack : Stack[T]) -> Unit { + stack.elements.iter(fn(i) -> Unit { self.push(i) }) +} + +test "push_stack" { + let s : Stack[Int] = Stack::from_list(Cons(1, Cons(2, Cons(3, Nil)))) + let s1 : Stack[Int] = Stack::new() + s1.push_stack(s) + @assertion.assert_eq(s1.elements, Cons(1, Cons(2, Cons(3, Nil))))? + @assertion.assert_eq(s.len, s1.len)? +} + +/// Push an array into the stack. +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::new() +/// s.push_array([1, 2, 3]) +/// println(s) // output: {elements: Cons(3, Cons(2, Cons(1, Nil))), len: 3} +/// ``` +pub fn push_array[T](self : Stack[T], array : Array[T]) -> Unit { + array.iter(fn(i) -> Unit { self.push(i) }) +} + +test "push_array" { + let s : Stack[Int] = Stack::new() + s.push_array([1, 2, 3]) + @assertion.assert_eq(s.elements, Cons(3, Cons(2, Cons(1, Nil))))? + @assertion.assert_eq(s.len, 3)? +} + +/// Pop an element from the top of the stack. +/// If there are elements in the stack, return `Some (the top element of the stack)`, otherwise return `None`. +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_array([1,2,3]) +/// let s1: Stack[Int] = Stack::new() +/// println(s.pop_option()) // output: Some(1) +/// println(s) // output: { elements: Cons(2, Cons(1, Nil)), len: 2 } +/// println(s1.pop()) // output: None +/// ``` +pub fn pop_option[T](self : Stack[T]) -> Option[T] { + match self.elements { + Cons(hd, tl) => { + self.elements = tl + self.len = self.len - 1 + Some(hd) + } + Nil => None + } +} + +test "pop_option" { + let s : Stack[Int] = Stack::[1, 2, 3] + let s1 : Stack[Int] = Stack::new() + @assertion.assert_eq(s.pop_option(), Some(3))? + @assertion.assert_eq(s.elements, Cons(2, Cons(1, Nil)))? + @assertion.assert_eq(s.len, 2)? + @assertion.assert_eq(s1.pop_option(), None)? + @assertion.assert_eq(s1.elements, Nil)? + @assertion.assert_eq(s1.len, 0)? +} + +/// Pop an element from the top of the stack. +/// If there are elements in the stack, return the top element of the stack, otherwise abort. +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_array([1,2,3]) +/// let s1: Stack[Int] = Stack::new() +/// println(s.pop()) // output: 1 +/// println(s) // output: { elements: Cons(2, Cons(1, Nil)), len: 2 } +/// println(s1.pop()) // abort. +/// ``` +pub fn pop[T](self : Stack[T]) -> T { + match self.elements { + Cons(hd, tl) => { + self.elements = tl + self.len = self.len - 1 + hd + } + Nil => abort("pop of empty stack") + } +} + +test "pop" { + let s : Stack[Int] = Stack::[1, 2, 3] + @assertion.assert_eq(s.pop(), 3)? + @assertion.assert_eq(s.elements, Cons(2, Cons(1, Nil)))? + @assertion.assert_eq(s.len, 2)? +} + +/// Drop the element at the top of the stack. +/// Like pop, but does not return elements and does nothing if the Stack is empty. +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_array([1,2,3]) +/// s.drop() +/// println(s) // output: { elements: Cons(2, Cons(3, Nil)), len: 2 } +/// ``` +pub fn drop[T](self : Stack[T]) -> Unit { + match self.elements { + Cons(_hd, tl) => { + self.elements = tl + self.len = self.len - 1 + } + Nil => () + } +} + +test "drop" { + let s : Stack[Int] = Stack::[1, 2, 3] + s.drop() + @assertion.assert_eq(s.elements, Cons(2, Cons(1, Nil)))? + @assertion.assert_eq(s.len, 2)? +} + +/// Drop the element at the top of the stack. +/// Like drop, but when the drop is successful, it returns `Ok(())`, and when it fails, it returns `Err(())` +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_array([1,2,3]) +/// s.drop() +/// println(s) // output: { elements: Cons(2, Cons(3, Nil)), len: 2 } +/// ``` +pub fn drop_result[T](self : Stack[T]) -> Result[Unit, Unit] { + match self.elements { + Cons(_hd, tl) => { + self.elements = tl + self.len = self.len - 1 + Ok(()) + } + Nil => Err(()) + } +} + +test "drop_result" { + let s : Stack[Int] = Stack::[1, 2, 3] + let s1 : Stack[Int] = Stack::new() + @assertion.assert_eq(s1.drop_result(), Err(()))? + @assertion.assert_eq(s.drop_result(), Ok(()))? + @assertion.assert_eq(s.elements, Cons(2, Cons(1, Nil)))? + @assertion.assert_eq(s.len, 2)? +} + +/// Only the top element of the stack is returned and will not be pop or drop. +/// If there are elements in the stack, return `Some (the top element of the stack)`, otherwise return `None`. +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_array([1,2,3]) +/// println(s.top_option()) // output: Some(3) +/// println(s) // output: { elements: Cons(3, Cons(2, Cons(1, Nil))), len: 3 } +/// ``` +pub fn top_option[T](self : Stack[T]) -> Option[T] { + match self.elements { + Cons(hd, _) => Some(hd) + Nil => None + } +} + +test "top_option" { + let s : Stack[Int] = Stack::[1, 2, 3] + @assertion.assert_eq(s.top_option(), Some(3))? + @assertion.assert_eq(s.elements, Cons(3, Cons(2, Cons(1, Nil))))? + @assertion.assert_eq(s.len, 3)? +} + +/// Only the top element of the stack is returned and will not be pop or drop. +/// If there are elements in the stack, return the top element of the stack, otherwise abort. +/// +/// # Example +/// +/// ``` +/// let s: Stack[Int] = Stack::from_array([1,2,3]) +/// let s1: Stack[Int] = Stack::new() +/// println(s1.top()) // abort +/// println(s.top()) // output: 3 +/// println(s) // output: { elements: Cons(3, Cons(2, Cons(1, Nil))), len: 3 } +/// ``` +pub fn top[T](self : Stack[T]) -> T { + match self.elements { + Cons(hd, _) => hd + Nil => abort("top of the empty stack") + } +} + +test "top" { + let s : Stack[Int] = Stack::[1, 2, 3] + @assertion.assert_eq(s.top(), 3)? + @assertion.assert_eq(s.elements, Cons(3, Cons(2, Cons(1, Nil))))? + @assertion.assert_eq(s.len, 3)? +} + +/// If stack is empty, return true, otherwise return false. +/// +/// # Example +/// +/// ``` +/// println(Stack::from_array([1, 2, 3]).is_empty()) // output: false +/// println(Stack::new().is_empty()) // output: true +/// ``` +pub fn is_empty[T](self : Stack[T]) -> Bool { + self.len == 0 +} + +test "is_empty" { + let empty : Stack[Unit] = Stack::new() + @assertion.assert_false(Stack::[1, 2, 3].is_empty())? + @assertion.assert_true(empty.is_empty())? +} + +/// Returns the number of elements of the Stack +pub fn length[T](self : Stack[T]) -> Int { + self.len +} + +/// Convert stack to list +/// +/// # Example +/// +/// ``` +/// println(Stack::[3, 2, 1].to_list()) // output: Cons(1, Cons(2, Cons(3, Nil))) +/// ``` +pub fn to_list[T](self : Stack[T]) -> List[T] { + self.elements +} + +test "to_list" { + @assertion.assert_eq( + Stack::[3, 2, 1].to_list(), + Cons(1, Cons(2, Cons(3, Nil))), + )? +} + +/// Convert stack to array +/// +/// # Example +/// +/// ``` +/// println(Stack::[3, 2, 1].to_array()) // output: [1, 2, 3] +/// ``` +pub fn to_array[T : Default](self : Stack[T]) -> Array[T] { + self.elements.to_array() +} + +test "to_array" { + @assertion.assert_eq(Stack::[3, 2, 1].to_array(), [1, 2, 3])? +} + +/// Compare two stacks. +/// +/// NOTE: Since the current standard library List lacks the equal or op_equal function, +/// this function internally implements the equal function of List. +/// +/// # Example +/// +/// ``` +/// println(Stack::[2, 4, 6].equal(Stack::[2, 4, 6])) +/// // output: true +/// ``` +pub fn equal[T : Eq](self : Stack[T], other : Stack[T]) -> Bool { + if self.len == other.len { + self.elements.equal(other.elements) + } else { + false + } +} + +pub fn op_equal[T : Eq](self : Stack[T], other : Stack[T]) -> Bool { + self.equal(other) +} + +test "equal" { + @assertion.assert_true(Stack::[2, 4, 6].equal(Stack::[2, 4, 6]))? + @assertion.assert_false(Stack::[2, 4, 6].equal(Stack::[2, 4, 7]))? +}