From de49ec6f768958828820602706f6daa64a757d0c Mon Sep 17 00:00:00 2001 From: Muqiu Han Date: Mon, 11 Mar 2024 14:40:51 +0800 Subject: [PATCH] add more utility functions to the list module move the forall and equal implementations from the stack module to the list module remove redundant functions in stack module Remove function 'forall' in the list module --- list/list.mbt | 190 ++++++++++++++++++++++++++++++++++++++++++++++++ stack/stack.mbt | 165 +---------------------------------------- 2 files changed, 193 insertions(+), 162 deletions(-) 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/stack.mbt b/stack/stack.mbt index f4580522d..0b3e462de 100644 --- a/stack/stack.mbt +++ b/stack/stack.mbt @@ -189,7 +189,7 @@ test "push_list" { @assertion.assert_eq(s.len, 3)? } -/// Push other stack into the current stack` +/// Push other stack into the current stack /// /// # Example /// @@ -200,7 +200,7 @@ test "push_list" { /// 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.iter(fn(i) -> Unit { self.push(i) }) + stack.elements.iter(fn(i) -> Unit { self.push(i) }) } test "push_stack" { @@ -425,42 +425,6 @@ pub fn length[T](self : Stack[T]) -> Int { self.len } -/// Iterates over the stack. -/// -/// # Example -/// -/// ``` -/// Stack::from_array([1, 2, 3, 4, 5]).iter(print) // output: 54321 -/// ``` -pub fn iter[T](self : Stack[T], f : (T) -> Unit) -> Unit { - self.elements.iter(f) -} - -test "iter" { - let s : Stack[Int] = Stack::[5, 4, 3, 2, 1] - let mut i = 0 - let mut failed = false - s.iter(fn(x) { i = i + 1; if x != i { failed = true } }) - if failed { - return Err("iter test failed") - } -} - -/// Iterates over the stack with index. -pub fn iteri[T](self : Stack[T], f : (Int, T) -> Unit) { - self.elements.iteri(f) -} - -test "iteri" { - let mut v = 0 - let mut failed = false - let s = Stack::[5, 4, 3, 2, 1] - s.iteri(fn(i, x) { if x != v + 1 || i != v { failed = true }; v = v + 1 }) - if failed { - return Err("iteri test failed") - } -} - /// Convert stack to list /// /// # Example @@ -494,116 +458,6 @@ test "to_array" { @assertion.assert_eq(Stack::[3, 2, 1].to_array(), [1, 2, 3])? } -/// Fold the stack. -/// -/// # Example -/// -/// ``` -/// let s = Stack::from_array([1, 2, 3, 4, 5]).fold(0, fn(acc, x) { acc + x }) -/// println(s) // output: 15 -/// ``` -pub fn fold[T, U](self : Stack[T], initial : U, f : (U, T) -> U) -> U { - self.elements.fold(initial, f) -} - -test "fold" { - @assertion.assert_eq( - Stack::[1, 2, 3, 4, 5].fold(0, fn(acc, x) { acc + x }), - 15, - )? -} - -/// Filter the stack. -/// -/// # Example -/// -/// ``` -/// println(Stack::from_array([1, 2, 3, 4, 5]).filter(fn(x){ x % 2 == 0})) -/// // output: Cons(4, Cons(2, Nil)) -/// ``` -pub fn filter[T](self : Stack[T], f : (T) -> Bool) -> Stack[T] { - let elements = self.elements.filter(f) - { elements, len: elements.length() } -} - -test "filter" { - let s = Stack::[1, 2, 3, 4, 5] - let s2 = s.filter(fn(x) { x % 2 == 0 }) - @assertion.assert_eq(s2.elements, Cons(4, Cons(2, Nil)))? - @assertion.assert_eq(s2.len, 2)? -} - -/// Maps the stack. -/// -/// # Example -/// -/// ``` -/// println(Stack::from_array([1, 2, 3]).map(fn(x){ x * 2}))) -/// // output: { elements: Cons(6, Cons(4, Cons(2, Nil))), len: 3 } -/// ``` -pub fn map[T, U](self : Stack[T], f : (T) -> U) -> Stack[U] { - { elements: self.elements.map(f), len: self.len } -} - -test "map" { - let s = Stack::[1, 2, 3] - @assertion.assert_eq( - s.map(fn(x) { x * 2 }).elements, - Cons(6, Cons(4, Cons(2, Nil))), - )? -} - -/// Checks if all elements of the stack satisfy the predicate f. -/// If the stack is empty, return true. -/// -/// NOTE: Since the current standard library List lacks the forall function, -/// this function internally implements the forall function of List. -/// -/// # Example -/// -/// ``` -/// println(Stack::[2, 4, 6].forall(fn(element) -> Bool { element % 2 == 0 })) -/// // output: true -/// ``` -pub fn forall[T](self : Stack[T], f : (T) -> Bool) -> Bool { - fn list_forall(list : List[T]) -> Bool { - match list { - Nil => true - Cons(h, t) => if f(h) { list_forall(t) } else { false } - } - } - - list_forall(self.elements) -} - -test "forall" { - @assertion.assert_true( - Stack::[2, 4, 6].forall(fn(element) -> Bool { element % 2 == 0 }), - )? - @assertion.assert_false( - Stack::[1, 3, 5].forall(fn(element) -> Bool { element % 2 == 0 }), - )? -} - -/// Reverse stack -/// -/// # Example -/// -/// ``` -/// println(Stack::[2, 4, 6].reverse()) -/// // output: Cons(2, Cons(4, Cons(6, Nil))) -/// ``` -pub fn reverse[T](self : Stack[T]) -> Stack[T] { - { elements: self.elements.reverse(), len: self.len } -} - -test "reverse" { - @assertion.assert_eq( - Stack::[2, 4, 6].reverse().elements, - Cons(2, Cons(4, Cons(6, Nil))), - )? -} - /// Compare two stacks. /// /// NOTE: Since the current standard library List lacks the equal or op_equal function, @@ -616,21 +470,8 @@ test "reverse" { /// // output: true /// ``` pub fn equal[T : Eq](self : Stack[T], other : Stack[T]) -> Bool { - fn list_eq(list1 : List[T], list2 : List[T]) -> Bool { - match (list1, list2) { - (Nil, Nil) => true - (Cons(h1, t1), Cons(h2, t2)) => - if h1 == h2 { - list_eq(t1, t2) - } else { - false - } - _ => false - } - } - if self.len == other.len { - list_eq(self.elements, other.elements) + self.elements.equal(other.elements) } else { false }