From eb0c5f1a0f20046d9c7464c5d1c4101ee55d512d Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Thu, 20 Feb 2025 12:47:00 +0100 Subject: [PATCH 01/40] =?UTF-8?q?first=20commit=20=F0=9F=A4=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Types.mo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Types.mo b/src/Types.mo index a5574e01..1240483f 100644 --- a/src/Types.mo +++ b/src/Types.mo @@ -104,7 +104,7 @@ module { #leaf }; - public type Queue = (Stack.Stack, Stack.Stack); // FIX ME + public type Queue = (Stack.Stack, Stack.Stack); // FIXME public type Set = () // Placeholder } } From d2f06d1fd4428fb0b8ea059bbeb25f0fb9e0bc45 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Thu, 20 Feb 2025 13:25:27 +0100 Subject: [PATCH 02/40] `empty`, `isEmpty`, `size` and `singleton` (with docs) --- src/pure/Queue.mo | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 60dd01a7..51ecacfc 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -41,9 +41,8 @@ module { /// Runtime: `O(1)`. /// /// Space: `O(1)`. - public func empty() : Queue { - todo() - }; + public func empty() : Queue = + ({var top = null; var size = 0}, {var top = null; var size = 0}); /// Determine whether a queue is empty. /// Returns true if `queue` is empty, otherwise `false`. @@ -59,17 +58,39 @@ module { /// Runtime: `O(1)`. /// /// Space: `O(1)`. - public func isEmpty(queue : Queue) : Bool { - todo() - }; + public func isEmpty(queue : Queue) : Bool = + queue.0.size + queue.1.size == 0; - public func singleton(item : T) : Queue { - todo() - }; + /// Create a new queue comprising a single element. + /// + /// Example: + /// ```motoko + /// import Queue "mo:base/Queue"; + /// + /// Queue.singleton(25) + /// ``` + /// + /// Runtime: `O(1)`. + /// + /// Space: `O(1)`. + public func singleton(item : T) : Queue = + ({var top = null; var size = 0}, {var top = ?{ value = item; next = null }; var size = 0}); - public func size(queue : Queue) : Nat { - todo() - }; + /// Determine the number of elements contained in a queue. + /// + /// Example: + /// ```motoko + /// import {singleton, size} "mo:base/Queue"; + /// + /// let queue = singleton(42); + /// size(queue) // => 1 + /// ``` + /// + /// Runtime: `O(1)`. + /// + /// Space: `O(1)`. + public func size(queue : Queue) : Nat = + queue.0.size + queue.1.size; public func contains(queue : Queue, item : T) : Bool { todo() From 2d1aa8dd7b0c2d0d7dd28d6526da4a16402c25a5 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Fri, 21 Feb 2025 20:50:28 +0100 Subject: [PATCH 03/40] trying a double-speed runner to halve the list but it doesn't work --- src/pure/Queue.mo | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 51ecacfc..6e9f3943 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -21,13 +21,14 @@ /// `n` denotes the number of elements stored in the queue. import Iter "../Iter"; +import List "List"; import Order "../Order"; import Types "../Types"; import { todo } "../Debug"; -module { +module Queue /* FIXME */ { /// Double-ended queue data type. - public type Queue = Types.Pure.Queue; + public type Queue = (Types.Pure.List, Nat, Types.Pure.List);//Types.Pure.Queue; /// Create a new empty queue. /// @@ -42,7 +43,7 @@ module { /// /// Space: `O(1)`. public func empty() : Queue = - ({var top = null; var size = 0}, {var top = null; var size = 0}); + (null, 0, null); /// Determine whether a queue is empty. /// Returns true if `queue` is empty, otherwise `false`. @@ -59,7 +60,7 @@ module { /// /// Space: `O(1)`. public func isEmpty(queue : Queue) : Bool = - queue.0.size + queue.1.size == 0; + queue.1 == 0; /// Create a new queue comprising a single element. /// @@ -74,7 +75,7 @@ module { /// /// Space: `O(1)`. public func singleton(item : T) : Queue = - ({var top = null; var size = 0}, {var top = ?{ value = item; next = null }; var size = 0}); + (null, 1, ?(item, null)); /// Determine the number of elements contained in a queue. /// @@ -90,7 +91,7 @@ module { /// /// Space: `O(1)`. public func size(queue : Queue) : Nat = - queue.0.size + queue.1.size; + queue.1; public func contains(queue : Queue, item : T) : Bool { todo() @@ -110,9 +111,11 @@ module { /// Runtime: `O(1)`. /// /// Space: `O(1)`. - public func peekFront(queue : Queue) : ?T { - todo() - }; + public func peekFront(queue : Queue) : ?T = + switch queue { + case ((?(x, _), _, _) or (_, _, ?(x, null))) ?x; + case _ null + }; /// Inspect the optional element on the back end of a queue. /// Returns `null` if `queue` is empty. Otherwise, the back element of `queue`. @@ -128,8 +131,30 @@ module { /// Runtime: `O(1)`. /// /// Space: `O(1)`. - public func peekBack(queue : Queue) : ?T { - todo() + public func peekBack(queue : Queue) : ?T = + switch queue { + case ((_, _, ?(x, _)) or (?(x, null), _, _)) ?x; + case _ null + }; + + // helper to split the list evenly + public /* private FIXME */ func half(list : Types.Pure.List, fast : Types.Pure.List) : (Types.Pure.List, Types.Pure.List) = + switch list { + case (null or (?(_, null))) (list, null); + case (?(h, ?(i, t))) { + switch fast { + case (null or ?(_, null)) (?(h, ?(i, null)), t); + case (?(_, ?(_, faster))) { + let (front, back) = half (t, faster); + (?(h, ?(i, front)), back) + } + } + } + }; + + public /* private FIXME */ func rebalance(list : Types.Pure.List) : (Types.Pure.List, Types.Pure.List) { + let (front, back) = half (list, list); + (front, List.reverse back) }; /// Insert a new element on the front end of a queue. From c731df95c8456798cc61186fe6d9e996611c5811 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Fri, 21 Feb 2025 21:12:10 +0100 Subject: [PATCH 04/40] `pushFront` and `pushBack` --- src/pure/Queue.mo | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 6e9f3943..63d74e23 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -138,23 +138,28 @@ module Queue /* FIXME */ { }; // helper to split the list evenly - public /* private FIXME */ func half(list : Types.Pure.List, fast : Types.Pure.List) : (Types.Pure.List, Types.Pure.List) = - switch list { - case (null or (?(_, null))) (list, null); - case (?(h, ?(i, t))) { - switch fast { - case (null or ?(_, null)) (?(h, ?(i, null)), t); - case (?(_, ?(_, faster))) { - let (front, back) = half (t, faster); - (?(h, ?(i, front)), back) - } - } + func takeDrop(list : Types.Pure.List, n : Nat) : (Types.Pure.List, Types.Pure.List) = + if (n == 0) (null, list) + else switch list { + case null (null, null); + case (?(h, t)) { + let (f, b) = takeDrop(t, n - 1); + (?(h, f), b) } }; - public /* private FIXME */ func rebalance(list : Types.Pure.List) : (Types.Pure.List, Types.Pure.List) { - let (front, back) = half (list, list); - (front, List.reverse back) + func check(q : Queue) : Queue { + switch q { + case (null, n, r) { + let (a, b) = takeDrop(r, n / 2); + (List.reverse b, n, a) + }; + case (f, n, null) { + let (a, b) = takeDrop(f, n / 2); + (a, n, List.reverse b) + }; + case q q + } }; /// Insert a new element on the front end of a queue. @@ -174,9 +179,8 @@ module Queue /* FIXME */ { /// Space: `O(n)` worst-case, amortized to `O(1)`. /// /// `n` denotes the number of elements stored in the queue. - public func pushFront(queue : Queue, element : T) : Queue { - todo() - }; + public func pushFront((f, n, b) : Queue, element : T) : Queue = + check (?(element, f), n + 1, b); /// Insert a new element on the back end of a queue. /// Returns the new queue with all the elements of `queue`, followed by `element` on the back. @@ -195,9 +199,8 @@ module Queue /* FIXME */ { /// Space: `O(n)` worst-case, amortized to `O(1)`. /// /// `n` denotes the number of elements stored in the queue. - public func pushBack(queue : Queue, element : T) : Queue { - todo() - }; + public func pushBack((f, n, b) : Queue, element : T) : Queue = + check (f, n + 1, ?(element, b)); /// Remove the element on the front end of a queue. /// Returns `null` if `queue` is empty. Otherwise, it returns a pair of From ec8d6aa25563281f078a43be740dbe60ccf845bf Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Fri, 21 Feb 2025 21:28:25 +0100 Subject: [PATCH 05/40] `popFront` and `popBack` --- src/pure/Queue.mo | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 63d74e23..31f04c1e 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -231,9 +231,13 @@ module Queue /* FIXME */ { /// Space: `O(n)` worst-case, amortized to `O(1)`. /// /// `n` denotes the number of elements stored in the queue. - public func popFront(queue : Queue) : ?(T, Queue) { - todo() - }; + public func popFront(queue : Queue) : ?(T, Queue) = + if (queue.1 == 0) null + else switch queue { + case (?(i, f), n, b) ?(i, (f, n - 1, b)); + case (null, _, ?(i, null)) ?(i, (null, 0, null)); + case _ popFront (check queue) + }; /// Remove the element on the back end of a queue. /// Returns `null` if `queue` is empty. Otherwise, it returns a pair of @@ -266,9 +270,13 @@ module Queue /* FIXME */ { /// Space: `O(n)` worst-case, amortized to `O(1)`. /// /// `n` denotes the number of elements stored in the queue. - public func popBack(queue : Queue) : ?(Queue, T) { - todo() - }; + public func popBack(queue : Queue) : ?(Queue, T) = + if (queue.1 == 0) null + else switch queue { + case (f, n, ?(i, b)) ?(i, (f, n - 1, b)); + case (?(i, null), _, null) ?(i, (null, 0, null)); + case _ popBack (check queue) + }; public func fromIter(iter : Iter.Iter) : Queue { todo() From 17f5e014f551185b74cc700b5a40f8656429aec2 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Fri, 21 Feb 2025 21:33:15 +0100 Subject: [PATCH 06/40] `fromIter` --- src/pure/Queue.mo | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 31f04c1e..31836074 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -273,13 +273,14 @@ module Queue /* FIXME */ { public func popBack(queue : Queue) : ?(Queue, T) = if (queue.1 == 0) null else switch queue { - case (f, n, ?(i, b)) ?(i, (f, n - 1, b)); - case (?(i, null), _, null) ?(i, (null, 0, null)); + case (f, n, ?(i, b)) ?((f, n - 1, b), i); + case (?(i, null), _, null) ?((null, 0, null), i); case _ popBack (check queue) }; public func fromIter(iter : Iter.Iter) : Queue { - todo() + let list = List.fromIter iter; + check ((list, List.size list, null)) }; public func values(queue : Queue) : Iter.Iter { From df1757650f29b9c84f31f6716d74c83157cab558 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Fri, 21 Feb 2025 21:58:40 +0100 Subject: [PATCH 07/40] `values`, `map`, `filter` and `filterMap` --- src/pure/Queue.mo | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 31836074..7a6904fe 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -283,9 +283,8 @@ module Queue /* FIXME */ { check ((list, List.size list, null)) }; - public func values(queue : Queue) : Iter.Iter { - todo() - }; + public func values(queue : Queue) : Iter.Iter = + Iter.concat(List.values(queue.0), List.values(List.reverse(queue.2))); public func equal(queue1 : Queue, queue2 : Queue) : Bool { todo() @@ -304,15 +303,22 @@ module Queue /* FIXME */ { }; public func map(queue : Queue, f : T1 -> T2) : Queue { - todo() + let (fr, n, b) = queue; + (List.map(fr, f), n, List.map(b, f)) }; public func filter(queue : Queue, f : T -> Bool) : Queue { - todo() + let (fr, n, b) = queue; + let front = List.filter(fr, f); + let back = List.filter(b, f); + (front, List.size front + List.size back, back) }; public func filterMap(queue : Queue, f : T -> ?U) : Queue { - todo() + let (fr, n, b) = queue; + let front = List.filterMap(fr, f); + let back = List.filterMap(b, f); + (front, List.size front + List.size back, back) }; public func toText(queue : Queue, f : T -> Text) : Text { From 325f3b88a09d206bbe64148c180d0a5ce6846c9d Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Sat, 22 Feb 2025 11:30:54 +0100 Subject: [PATCH 08/40] add a debugging invariant --- src/pure/Queue.mo | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 7a6904fe..f969a760 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -90,8 +90,10 @@ module Queue /* FIXME */ { /// Runtime: `O(1)`. /// /// Space: `O(1)`. - public func size(queue : Queue) : Nat = - queue.1; + public func size(queue : Queue) : Nat { + debug assert queue.1 == List.size(queue.0) + List.size(queue.2); + queue.1 + }; public func contains(queue : Queue, item : T) : Bool { todo() From efd656d4828e804a46fe3b2caf65131992787115 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Sat, 22 Feb 2025 11:48:17 +0100 Subject: [PATCH 09/40] `compare` --- src/pure/Queue.mo | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index f969a760..30949960 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -293,16 +293,20 @@ module Queue /* FIXME */ { }; public func all(queue : Queue, predicate : T -> Bool) : Bool { - todo() + for (item in values queue) + if (not (predicate item)) return false; + return true }; public func any(queue : Queue, predicate : T -> Bool) : Bool { - todo() + for (item in values queue) + if (predicate item) return true; + return false }; - public func forEach(queue : Queue, f : T -> ()) { - todo() - }; + public func forEach(queue : Queue, f : T -> ()) = + for (item in values queue) + f item; public func map(queue : Queue, f : T1 -> T2) : Queue { let (fr, n, b) = queue; @@ -310,7 +314,7 @@ module Queue /* FIXME */ { }; public func filter(queue : Queue, f : T -> Bool) : Queue { - let (fr, n, b) = queue; + let (fr, _, b) = queue; let front = List.filter(fr, f); let back = List.filter(b, f); (front, List.size front + List.size back, back) @@ -328,7 +332,16 @@ module Queue /* FIXME */ { }; public func compare(queue1 : Queue, queue2 : Queue, compare : (T, T) -> Order.Order) : Order.Order { - todo() - }; + let (i1, i2) = (values queue1, values queue2); + loop switch (i1.next(), i2.next()) { + case (null, null) return #equal; + case (null, _) return #less; + case (_, null) return #greater; + case (?v1, ?v2) switch (compare(v1, v2)) { + case (#equal) (); + case c return c + } + } + } } From 6807aa7a0ffeb80b2316e441436adb3bf1e6f2b4 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Sat, 22 Feb 2025 11:49:55 +0100 Subject: [PATCH 10/40] tweak --- src/pure/Queue.mo | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 30949960..8ab16c70 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -334,13 +334,13 @@ module Queue /* FIXME */ { public func compare(queue1 : Queue, queue2 : Queue, compare : (T, T) -> Order.Order) : Order.Order { let (i1, i2) = (values queue1, values queue2); loop switch (i1.next(), i2.next()) { - case (null, null) return #equal; - case (null, _) return #less; - case (_, null) return #greater; case (?v1, ?v2) switch (compare(v1, v2)) { case (#equal) (); case c return c - } + }; + case (null, null) return #equal; + case (null, _) return #less; + case (_, null) return #greater } } From 3941a9478d4d8c8a4356f1ab089510b14c117648 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Sun, 23 Feb 2025 20:17:16 +0100 Subject: [PATCH 11/40] simplify --- src/pure/Queue.mo | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 8ab16c70..43fb525b 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -27,8 +27,10 @@ import Types "../Types"; import { todo } "../Debug"; module Queue /* FIXME */ { + type List = Types.Pure.List; + /// Double-ended queue data type. - public type Queue = (Types.Pure.List, Nat, Types.Pure.List);//Types.Pure.Queue; + public type Queue = (List, Nat, List);//Types.Pure.Queue; /// Create a new empty queue. /// @@ -140,7 +142,7 @@ module Queue /* FIXME */ { }; // helper to split the list evenly - func takeDrop(list : Types.Pure.List, n : Nat) : (Types.Pure.List, Types.Pure.List) = + func takeDrop(list : List, n : Nat) : (List, List) = if (n == 0) (null, list) else switch list { case null (null, null); From cc7e6e2a0cb2e24f0aa4281fa3b02ccd5b01347a Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 15:28:36 +0100 Subject: [PATCH 12/40] Add previous `Queue` tests --- test/pure/Queue.test.mo | 521 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 521 insertions(+) create mode 100644 test/pure/Queue.test.mo diff --git a/test/pure/Queue.test.mo b/test/pure/Queue.test.mo new file mode 100644 index 00000000..72daabc3 --- /dev/null +++ b/test/pure/Queue.test.mo @@ -0,0 +1,521 @@ +import Suite "mo:matchers/Suite"; +import T "mo:matchers/Testable"; +import M "mo:matchers/Matchers"; + +import Prim "mo:prim"; +import Queue "../../src/pure/Queue"; +import Array "../../src/Array"; +import Nat "../../src/Nat"; +import Iter "../../src/Iter"; + +let { run; test; suite } = Suite; + +func iterateForward(deque : Queue.Queue) : Iter.Iter { + var current = deque; + object { + public func next() : ?T { + switch (Queue.popFront(current)) { + case null null; + case (?result) { + current := result.1; + ?result.0 + } + } + } + } +}; + +func iterateBackward(deque : Queue.Queue) : Iter.Iter { + var current = deque; + object { + public func next() : ?T { + switch (Queue.popBack(current)) { + case null null; + case (?result) { + current := result.0; + ?result.1 + } + } + } + } +}; + +func toText(deque : Queue.Queue) : Text { + var text = "["; + var isFirst = true; + for (element in iterateForward(deque)) { + if (not isFirst) { + text #= ", " + } else { + isFirst := false + }; + text #= debug_show (element) + }; + text #= "]"; + text +}; + +let natQueueTestable : T.Testable> = object { + public func display(deque : Queue.Queue) : Text { + toText(deque) + }; + public func equals(first : Queue.Queue, second : Queue.Queue) : Bool { + Array.equal(Iter.toArray(iterateForward(first)), Iter.toArray(iterateForward(second)), Nat.equal) + } +}; + +func matchFrontRemoval(element : Nat, remainder : Queue.Queue) : M.Matcher)> { + let testable = T.tuple2Testable(T.natTestable, natQueueTestable); + M.equals(T.optional(testable, ?(element, remainder))) +}; + +func matchEmptyFrontRemoval() : M.Matcher)> { + let testable = T.tuple2Testable(T.natTestable, natQueueTestable); + M.equals(T.optional(testable, null : ?(Nat, Queue.Queue))) +}; + +func matchBackRemoval(remainder : Queue.Queue, element : Nat) : M.Matcher, Nat)> { + let testable = T.tuple2Testable(natQueueTestable, T.natTestable); + M.equals(T.optional(testable, ?(remainder, element))) +}; + +func matchEmptyBackRemoval() : M.Matcher, Nat)> { + let testable = T.tuple2Testable(natQueueTestable, T.natTestable); + M.equals(T.optional(testable, null : ?(Queue.Queue, Nat))) +}; + +func reduceFront(deque : Queue.Queue, amount : Nat) : Queue.Queue { + var current = deque; + for (_ in Iter.range(1, amount)) { + switch (Queue.popFront(current)) { + case null Prim.trap("should not be null"); + case (?result) current := result.1 + } + }; + current +}; + +func reduceBack(deque : Queue.Queue, amount : Nat) : Queue.Queue { + var current = deque; + for (_ in Iter.range(1, amount)) { + switch (Queue.popBack(current)) { + case null Prim.trap("should not be null"); + case (?result) current := result.0 + } + }; + current +}; + +/* --------------------------------------- */ + +var deque = Queue.empty(); + +run( + suite( + "construct", + [ + test( + "empty", + Queue.isEmpty(deque), + M.equals(T.bool(true)) + ), + test( + "iterate forward", + Iter.toArray(iterateForward(deque)), + M.equals(T.array(T.natTestable, [])) + ), + test( + "iterate backward", + Iter.toArray(iterateBackward(deque)), + M.equals(T.array(T.natTestable, [])) + ), + test( + "peek front", + Queue.peekFront(deque), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), + test( + "peek back", + Queue.peekBack(deque), + M.equals(T.optional(T.natTestable, null : ?Nat)) + ), + test( + "pop front", + Queue.popFront(deque), + matchEmptyFrontRemoval() + ), + test( + "pop back", + Queue.popBack(deque), + matchEmptyBackRemoval() + ) + ] + ) +); + +/* --------------------------------------- */ + +deque := Queue.pushFront(Queue.empty(), 1); + +run( + suite( + "single item", + [ + test( + "not empty", + Queue.isEmpty(deque), + M.equals(T.bool(false)) + ), + test( + "iterate forward", + Iter.toArray(iterateForward(deque)), + M.equals(T.array(T.natTestable, [1])) + ), + test( + "iterate backward", + Iter.toArray(iterateBackward(deque)), + M.equals(T.array(T.natTestable, [1])) + ), + test( + "peek front", + Queue.peekFront(deque), + M.equals(T.optional(T.natTestable, ?1)) + ), + test( + "peek back", + Queue.peekBack(deque), + M.equals(T.optional(T.natTestable, ?1)) + ), + test( + "pop front", + Queue.popFront(deque), + matchFrontRemoval(1, Queue.empty()) + ), + test( + "pop back", + Queue.popBack(deque), + matchBackRemoval(Queue.empty(), 1) + ) + ] + ) +); + +/* --------------------------------------- */ + +let testSize = 100; + +func populateForward(from : Nat, to : Nat) : Queue.Queue { + var deque = Queue.empty(); + for (number in Iter.range(from, to)) { + deque := Queue.pushFront(deque, number) + }; + deque +}; + +deque := populateForward(1, testSize); + +run( + suite( + "forward insertion", + [ + test( + "not empty", + Queue.isEmpty(deque), + M.equals(T.bool(false)) + ), + test( + "iterate forward", + Iter.toArray(iterateForward(deque)), + M.equals( + T.array( + T.natTestable, + Array.tabulate( + testSize, + func(index : Nat) : Nat { + testSize - index + } + ) + ) + ) + ), + test( + "iterate backward", + Iter.toArray(iterateBackward(deque)), + M.equals( + T.array( + T.natTestable, + Array.tabulate( + testSize, + func(index : Nat) : Nat { + index + 1 + } + ) + ) + ) + ), + test( + "peek front", + Queue.peekFront(deque), + M.equals(T.optional(T.natTestable, ?testSize)) + ), + test( + "peek back", + Queue.peekBack(deque), + M.equals(T.optional(T.natTestable, ?1)) + ), + test( + "pop front", + Queue.popFront(deque), + matchFrontRemoval(testSize, populateForward(1, testSize - 1)) + ), + test( + "empty after front removal", + Queue.isEmpty(reduceFront(deque, testSize)), + M.equals(T.bool(true)) + ), + test( + "empty after front removal", + Queue.isEmpty(reduceBack(deque, testSize)), + M.equals(T.bool(true)) + ) + ] + ) +); + +/* --------------------------------------- */ + +func populateBackward(from : Nat, to : Nat) : Queue.Queue { + var deque = Queue.empty(); + for (number in Iter.range(from, to)) { + deque := Queue.pushBack(deque, number) + }; + deque +}; + +deque := populateBackward(1, testSize); + +run( + suite( + "backward insertion", + [ + test( + "not empty", + Queue.isEmpty(deque), + M.equals(T.bool(false)) + ), + test( + "iterate forward", + Iter.toArray(iterateForward(deque)), + M.equals( + T.array( + T.natTestable, + Array.tabulate( + testSize, + func(index : Nat) : Nat { + index + 1 + } + ) + ) + ) + ), + test( + "iterate backward", + Iter.toArray(iterateBackward(deque)), + M.equals( + T.array( + T.natTestable, + Array.tabulate( + testSize, + func(index : Nat) : Nat { + testSize - index + } + ) + ) + ) + ), + test( + "peek front", + Queue.peekFront(deque), + M.equals(T.optional(T.natTestable, ?1)) + ), + test( + "peek back", + Queue.peekBack(deque), + M.equals(T.optional(T.natTestable, ?testSize)) + ), + test( + "pop front", + Queue.popFront(deque), + matchFrontRemoval(1, populateBackward(2, testSize)) + ), + test( + "pop back", + Queue.popBack(deque), + matchBackRemoval(populateBackward(1, testSize - 1), testSize) + ), + test( + "empty after front removal", + Queue.isEmpty(reduceFront(deque, testSize)), + M.equals(T.bool(true)) + ), + test( + "empty after front removal", + Queue.isEmpty(reduceBack(deque, testSize)), + M.equals(T.bool(true)) + ) + ] + ) +); + +/* --------------------------------------- */ + +object Random { + var number = 4711; + public func next() : Int { + number := (123138118391 * number + 133489131) % 9999; + number + } +}; + +func randomPopulate(amount : Nat) : Queue.Queue { + var current = Queue.empty(); + for (number in Iter.range(1, amount)) { + current := if (Random.next() % 2 == 0) { + Queue.pushFront(current, Nat.sub(amount, number)) + } else { + Queue.pushBack(current, amount + number) + } + }; + current +}; + +func isSorted(deque : Queue.Queue) : Bool { + let array = Iter.toArray(iterateForward(deque)); + let sorted = Array.sort(array, Nat.compare); + Array.equal(array, sorted, Nat.equal) +}; + +func randomRemoval(deque : Queue.Queue, amount : Nat) : Queue.Queue { + var current = deque; + for (number in Iter.range(1, amount)) { + current := if (Random.next() % 2 == 0) { + let pair = Queue.popFront(current); + switch pair { + case null Prim.trap("should not be null"); + case (?result) result.1 + } + } else { + let pair = Queue.popBack(current); + switch pair { + case null Prim.trap("should not be null"); + case (?result) result.0 + } + } + }; + current +}; + +deque := randomPopulate(testSize); + +run( + suite( + "random insertion", + [ + test( + "not empty", + Queue.isEmpty(deque), + M.equals(T.bool(false)) + ), + test( + "correct order", + isSorted(deque), + M.equals(T.bool(true)) + ), + test( + "consistent iteration", + Iter.toArray(iterateForward(deque)), + M.equals(T.array(T.natTestable, Array.reverse(Iter.toArray(iterateBackward(deque))))) + ), + test( + "random quarter removal", + isSorted(randomRemoval(deque, testSize / 4)), + M.equals(T.bool(true)) + ), + test( + "random half removal", + isSorted(randomRemoval(deque, testSize / 2)), + M.equals(T.bool(true)) + ), + test( + "random three quarter removal", + isSorted(randomRemoval(deque, testSize * 3 / 4)), + M.equals(T.bool(true)) + ), + test( + "random total removal", + Queue.isEmpty(randomRemoval(deque, testSize)), + M.equals(T.bool(true)) + ) + ] + ) +); + +/* --------------------------------------- */ + +func randomInsertionDeletion(steps : Nat) : Queue.Queue { + var current = Queue.empty(); + var size = 0; + for (number in Iter.range(1, steps)) { + let random = Random.next(); + current := switch (random % 4) { + case 0 { + size += 1; + Queue.pushFront(current, Nat.sub(steps, number)) + }; + case 1 { + size += 1; + Queue.pushBack(current, steps + number) + }; + case 2 { + switch (Queue.popFront(current)) { + case null { + assert (size == 0); + current + }; + case (?result) { + size -= 1; + result.1 + } + } + }; + case 3 { + switch (Queue.popBack(current)) { + case null { + assert (size == 0); + current + }; + case (?result) { + size -= 1; + result.0 + } + } + }; + case _ Prim.trap("Impossible case") + }; + assert (isSorted(current)) + }; + current +}; + +run( + suite( + "completely random", + [ + test( + "random insertion and deletion", + isSorted(randomInsertionDeletion(1000)), + M.equals(T.bool(true)) + ) + ] + ) +) From 2b12d85780124c22f1b24b344b97163d86be745b Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 15:42:52 +0100 Subject: [PATCH 13/40] obsolete --- test/pure/Queue.test.skip.mo | 521 ----------------------------------- 1 file changed, 521 deletions(-) delete mode 100644 test/pure/Queue.test.skip.mo diff --git a/test/pure/Queue.test.skip.mo b/test/pure/Queue.test.skip.mo deleted file mode 100644 index b46d2728..00000000 --- a/test/pure/Queue.test.skip.mo +++ /dev/null @@ -1,521 +0,0 @@ -import Prim "mo:prim"; -import Queue "../../src/pure/Queue"; -import Array "../../src/Array"; -import Nat "../../src/Nat"; -import Iter "../../src/Iter"; - -import Suite "mo:matchers/Suite"; -import T "mo:matchers/Testable"; -import M "mo:matchers/Matchers"; - -let { run; test; suite } = Suite; - -func iterateForward(deque : Queue.Queue) : Iter.Iter { - var current = deque; - object { - public func next() : ?T { - switch (Queue.popFront(current)) { - case null null; - case (?result) { - current := result.1; - ?result.0 - } - } - } - } -}; - -func iterateBackward(deque : Queue.Queue) : Iter.Iter { - var current = deque; - object { - public func next() : ?T { - switch (Queue.popBack(current)) { - case null null; - case (?result) { - current := result.0; - ?result.1 - } - } - } - } -}; - -func toText(deque : Queue.Queue) : Text { - var text = "["; - var isFirst = true; - for (element in iterateForward(deque)) { - if (not isFirst) { - text #= ", " - } else { - isFirst := false - }; - text #= debug_show (element) - }; - text #= "]"; - text -}; - -let natQueueTestable : T.Testable> = object { - public func display(deque : Queue.Queue) : Text { - toText(deque) - }; - public func equals(first : Queue.Queue, second : Queue.Queue) : Bool { - Array.equal(Iter.toArray(iterateForward(first)), Iter.toArray(iterateForward(second)), Nat.equal) - } -}; - -func matchFrontRemoval(element : Nat, remainder : Queue.Queue) : M.Matcher)> { - let testable = T.tuple2Testable(T.natTestable, natQueueTestable); - M.equals(T.optional(testable, ?(element, remainder))) -}; - -func matchEmptyFrontRemoval() : M.Matcher)> { - let testable = T.tuple2Testable(T.natTestable, natQueueTestable); - M.equals(T.optional(testable, null : ?(Nat, Queue.Queue))) -}; - -func matchBackRemoval(remainder : Queue.Queue, element : Nat) : M.Matcher, Nat)> { - let testable = T.tuple2Testable(natQueueTestable, T.natTestable); - M.equals(T.optional(testable, ?(remainder, element))) -}; - -func matchEmptyBackRemoval() : M.Matcher, Nat)> { - let testable = T.tuple2Testable(natQueueTestable, T.natTestable); - M.equals(T.optional(testable, null : ?(Queue.Queue, Nat))) -}; - -func reduceFront(deque : Queue.Queue, amount : Nat) : Queue.Queue { - var current = deque; - for (_ in Nat.range(1, amount)) { - switch (Queue.popFront(current)) { - case null Prim.trap("should not be null"); - case (?result) current := result.1 - } - }; - current -}; - -func reduceBack(deque : Queue.Queue, amount : Nat) : Queue.Queue { - var current = deque; - for (_ in Nat.range(1, amount)) { - switch (Queue.popBack(current)) { - case null Prim.trap("should not be null"); - case (?result) current := result.0 - } - }; - current -}; - -/* --------------------------------------- */ - -var deque = Queue.empty(); - -run( - suite( - "construct", - [ - test( - "empty", - Queue.isEmpty(deque), - M.equals(T.bool(true)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals(T.array(T.natTestable, [])) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals(T.array(T.natTestable, [])) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, null : ?Nat)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, null : ?Nat)) - ), - test( - "pop front", - Queue.popFront(deque), - matchEmptyFrontRemoval() - ), - test( - "pop back", - Queue.popBack(deque), - matchEmptyBackRemoval() - ) - ] - ) -); - -/* --------------------------------------- */ - -deque := Queue.pushFront(Queue.empty(), 1); - -run( - suite( - "single item", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals(T.array(T.natTestable, [1])) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals(T.array(T.natTestable, [1])) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "pop front", - Queue.popFront(deque), - matchFrontRemoval(1, Queue.empty()) - ), - test( - "pop back", - Queue.popBack(deque), - matchBackRemoval(Queue.empty(), 1) - ) - ] - ) -); - -/* --------------------------------------- */ - -let testSize = 100; - -func populateForward(from : Nat, to : Nat) : Queue.Queue { - var deque = Queue.empty(); - for (number in Nat.range(from, to)) { - deque := Queue.pushFront(deque, number) - }; - deque -}; - -deque := populateForward(1, testSize); - -run( - suite( - "forward insertion", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - testSize - index - } - ) - ) - ) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - index + 1 - } - ) - ) - ) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, ?testSize)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "pop front", - Queue.popFront(deque), - matchFrontRemoval(testSize, populateForward(1, testSize - 1)) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceFront(deque, testSize)), - M.equals(T.bool(true)) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceBack(deque, testSize)), - M.equals(T.bool(true)) - ) - ] - ) -); - -/* --------------------------------------- */ - -func populateBackward(from : Nat, to : Nat) : Queue.Queue { - var deque = Queue.empty(); - for (number in Nat.range(from, to)) { - deque := Queue.pushBack(deque, number) - }; - deque -}; - -deque := populateBackward(1, testSize); - -run( - suite( - "backward insertion", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - index + 1 - } - ) - ) - ) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - testSize - index - } - ) - ) - ) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, ?testSize)) - ), - test( - "pop front", - Queue.popFront(deque), - matchFrontRemoval(1, populateBackward(2, testSize)) - ), - test( - "pop back", - Queue.popBack(deque), - matchBackRemoval(populateBackward(1, testSize - 1), testSize) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceFront(deque, testSize)), - M.equals(T.bool(true)) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceBack(deque, testSize)), - M.equals(T.bool(true)) - ) - ] - ) -); - -/* --------------------------------------- */ - -object Random { - var number = 4711; - public func next() : Int { - number := (123138118391 * number + 133489131) % 9999; - number - } -}; - -func randomPopulate(amount : Nat) : Queue.Queue { - var current = Queue.empty(); - for (number in Nat.range(1, amount)) { - current := if (Random.next() % 2 == 0) { - Queue.pushFront(current, Nat.sub(amount, number)) - } else { - Queue.pushBack(current, amount + number) - } - }; - current -}; - -func isSorted(deque : Queue.Queue) : Bool { - let array = Iter.toArray(iterateForward(deque)); - let sorted = Array.sort(array, Nat.compare); - Array.equal(array, sorted, Nat.equal) -}; - -func randomRemoval(deque : Queue.Queue, amount : Nat) : Queue.Queue { - var current = deque; - for (number in Nat.range(1, amount)) { - current := if (Random.next() % 2 == 0) { - let pair = Queue.popFront(current); - switch pair { - case null Prim.trap("should not be null"); - case (?result) result.1 - } - } else { - let pair = Queue.popBack(current); - switch pair { - case null Prim.trap("should not be null"); - case (?result) result.0 - } - } - }; - current -}; - -deque := randomPopulate(testSize); - -run( - suite( - "random insertion", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "correct order", - isSorted(deque), - M.equals(T.bool(true)) - ), - test( - "consistent iteration", - Iter.toArray(iterateForward(deque)), - M.equals(T.array(T.natTestable, Array.reverse(Iter.toArray(iterateBackward(deque))))) - ), - test( - "random quarter removal", - isSorted(randomRemoval(deque, testSize / 4)), - M.equals(T.bool(true)) - ), - test( - "random half removal", - isSorted(randomRemoval(deque, testSize / 2)), - M.equals(T.bool(true)) - ), - test( - "random three quarter removal", - isSorted(randomRemoval(deque, testSize * 3 / 4)), - M.equals(T.bool(true)) - ), - test( - "random total removal", - Queue.isEmpty(randomRemoval(deque, testSize)), - M.equals(T.bool(true)) - ) - ] - ) -); - -/* --------------------------------------- */ - -func randomInsertionDeletion(steps : Nat) : Queue.Queue { - var current = Queue.empty(); - var size = 0; - for (number in Nat.range(1, steps)) { - let random = Random.next(); - current := switch (random % 4) { - case 0 { - size += 1; - Queue.pushFront(current, Nat.sub(steps, number)) - }; - case 1 { - size += 1; - Queue.pushBack(current, steps + number) - }; - case 2 { - switch (Queue.popFront(current)) { - case null { - assert (size == 0); - current - }; - case (?result) { - size -= 1; - result.1 - } - } - }; - case 3 { - switch (Queue.popBack(current)) { - case null { - assert (size == 0); - current - }; - case (?result) { - size -= 1; - result.0 - } - } - }; - case _ Prim.trap("Impossible case") - }; - assert (isSorted(current)) - }; - current -}; - -run( - suite( - "completely random", - [ - test( - "random insertion and deletion", - isSorted(randomInsertionDeletion(1000)), - M.equals(T.bool(true)) - ) - ] - ) -) From 5aebe3118e3cd41e4ed1e6e0524f577a7fa228a1 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 15:46:11 +0100 Subject: [PATCH 14/40] preliminary `range` for tests' sake --- src/Iter.mo | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Iter.mo b/src/Iter.mo index 1a2a4e4c..d3641f3a 100644 --- a/src/Iter.mo +++ b/src/Iter.mo @@ -389,4 +389,21 @@ module { fromArray(Array.reverse(toArray(iter))) // TODO: optimize }; + /// Creates an iterator that produces all `Nat`s from `x` to `y` including + /// both of the bounds. + /// ```motoko + /// import Iter "mo:base/Iter"; + /// let iter = Iter.range(1, 3); + /// assert(?1 == iter.next()); + /// assert(?2 == iter.next()); + /// assert(?3 == iter.next()); + /// assert(null == iter.next()); + /// ``` + public class range(x : Nat, y : Int) { + var i = x; + public func next() : ?Nat { + if (i > y) null else { let j = i; i += 1; ?j } + } + }; + } From b76fd9fbf3408ddd0bb9c7d444ed8fbd20501be0 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 15:52:57 +0100 Subject: [PATCH 15/40] add debug assertions --- src/pure/Queue.mo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 43fb525b..699b752d 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -118,7 +118,7 @@ module Queue /* FIXME */ { public func peekFront(queue : Queue) : ?T = switch queue { case ((?(x, _), _, _) or (_, _, ?(x, null))) ?x; - case _ null + case _ { debug assert List.isEmpty(queue.2); null } }; /// Inspect the optional element on the back end of a queue. @@ -138,7 +138,7 @@ module Queue /* FIXME */ { public func peekBack(queue : Queue) : ?T = switch queue { case ((_, _, ?(x, _)) or (?(x, null), _, _)) ?x; - case _ null + case _ { debug assert List.isEmpty(queue.0); null } }; // helper to split the list evenly From 9bcb3a131e410e192f687ea5cb9abbd7dab9ff6a Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 15:59:00 +0100 Subject: [PATCH 16/40] adhere to interface --- src/pure/Queue.mo | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 699b752d..c5639a05 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -183,8 +183,8 @@ module Queue /* FIXME */ { /// Space: `O(n)` worst-case, amortized to `O(1)`. /// /// `n` denotes the number of elements stored in the queue. - public func pushFront((f, n, b) : Queue, element : T) : Queue = - check (?(element, f), n + 1, b); + public func pushFront(queue : Queue, element : T) : Queue = + check (?(element, queue.0), queue.1 + 1, queue.2); /// Insert a new element on the back end of a queue. /// Returns the new queue with all the elements of `queue`, followed by `element` on the back. @@ -203,8 +203,8 @@ module Queue /* FIXME */ { /// Space: `O(n)` worst-case, amortized to `O(1)`. /// /// `n` denotes the number of elements stored in the queue. - public func pushBack((f, n, b) : Queue, element : T) : Queue = - check (f, n + 1, ?(element, b)); + public func pushBack(queue : Queue, element : T) : Queue = + check (queue.0, queue.1 + 1, ?(element, queue.2)); /// Remove the element on the front end of a queue. /// Returns `null` if `queue` is empty. Otherwise, it returns a pair of From f80d229314d601a24fe64238e857e56f93480f9a Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 19:12:48 +0100 Subject: [PATCH 17/40] implement `contains` for pure `List` --- src/pure/List.mo | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/pure/List.mo b/src/pure/List.mo index 35d78b60..74cb6b57 100644 --- a/src/pure/List.mo +++ b/src/pure/List.mo @@ -61,9 +61,23 @@ module { case (?(_, t)) 1 + size t }; - public func contains(list : List, item : T) : Bool { - todo() - }; + /// Return whether the `list` contains `element` when compared using the `equal` predicate. + /// + /// Example: + /// ```motoko include=initialize + /// List.contains(?(0, ?(1, ?(2, null))), Nat.equal, 1) // => true + /// ``` + /// + /// Runtime: O(size) + /// + /// Space: O(1) + /// + /// *Runtime and space assumes that `equal` runs in O(1) time and space. + public func contains(list : List, equal : (T, T) -> Bool, element : T) : Bool = + switch list { + case (?(h, t)) equal(h, element) or contains(t, equal, element); + case _ false + }; /// Access any item in a list, zero-based. /// From d4e292a9ee9a971545ec62088ef545a7459ace46 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 19:14:06 +0100 Subject: [PATCH 18/40] Implement `Queue.contains` in terms of `List.contains` --- src/pure/Queue.mo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index c5639a05..150efc9a 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -97,8 +97,8 @@ module Queue /* FIXME */ { queue.1 }; - public func contains(queue : Queue, item : T) : Bool { - todo() + public func contains(queue : Queue, equal : (T, T) -> Bool, element : T) : Bool { + List.contains(queue.0, equal, element) or List.contains(queue.2, equal, element) }; /// Inspect the optional element on the front end of a queue. From 3135ebc879b874f80cb67a4514c9575d8db0edbf Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 19:22:44 +0100 Subject: [PATCH 19/40] implement `toText` --- src/pure/Queue.mo | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 150efc9a..22979314 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -330,7 +330,14 @@ module Queue /* FIXME */ { }; public func toText(queue : Queue, f : T -> Text) : Text { - todo() + var text = "[/*Q*/ "; + func add(item : T) { + if (text.size() > 7) text #= ", "; + text #= f(item) + }; + List.forEach(queue.0, add); + List.forEach(queue.2, add); + text # "]" }; public func compare(queue1 : Queue, queue2 : Queue, compare : (T, T) -> Order.Order) : Order.Order { From 8141fa00d20a5c0de52710b55583a12aabb857c2 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 19:23:31 +0100 Subject: [PATCH 20/40] no more `todo` --- src/pure/List.mo | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pure/List.mo b/src/pure/List.mo index 74cb6b57..22ff956f 100644 --- a/src/pure/List.mo +++ b/src/pure/List.mo @@ -14,7 +14,6 @@ import Iter "../Iter"; import Order "../Order"; import Result "../Result"; import Types "../Types"; -import { todo } "../Debug"; module { From 4118ff7b75ed2fba8f19b4d2fd38ce39899135a1 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 19:31:39 +0100 Subject: [PATCH 21/40] define `Queue` correctly --- src/Types.mo | 2 +- src/pure/Queue.mo | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Types.mo b/src/Types.mo index f129e7a9..63ca8599 100644 --- a/src/Types.mo +++ b/src/Types.mo @@ -145,7 +145,7 @@ module { #leaf }; - public type Queue = (Stack.Stack, Stack.Stack); // FIXME + public type Queue = (List, Nat, List); public type Set = () // Placeholder } } diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 22979314..c346e4b1 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -30,7 +30,7 @@ module Queue /* FIXME */ { type List = Types.Pure.List; /// Double-ended queue data type. - public type Queue = (List, Nat, List);//Types.Pure.Queue; + public type Queue = Types.Pure.Queue; /// Create a new empty queue. /// From 67af1deae77b395542b294617a664710c5ccf3eb Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 19:40:28 +0100 Subject: [PATCH 22/40] fix error --- validation/api/api.lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 4b604a99..a29b5202 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -1295,7 +1295,7 @@ "public func all(queue : Queue, predicate : T -> Bool) : Bool", "public func any(queue : Queue, predicate : T -> Bool) : Bool", "public func compare(queue1 : Queue, queue2 : Queue, compare : (T, T) -> Order.Order) : Order.Order", - "public func contains(queue : Queue, equal : T -> Bool, item : T) : Bool", + "public func contains(queue : Queue, equal : (T, T) -> Bool, item : T) : Bool", "public func empty() : Queue", "public func equal(queue1 : Queue, queue2 : Queue) : Bool", "public func filter(queue : Queue, f : T -> Bool) : Queue", @@ -1351,4 +1351,4 @@ "public func values(set : Set) : Iter.Iter" ] } -] \ No newline at end of file +] From ddd5c5c92e9234df86db6b61a4fd8d7467438d06 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 19:57:29 +0100 Subject: [PATCH 23/40] use `Nat.range` --- test/pure/Queue.test.mo | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/pure/Queue.test.mo b/test/pure/Queue.test.mo index 72daabc3..c3e7ff2f 100644 --- a/test/pure/Queue.test.mo +++ b/test/pure/Queue.test.mo @@ -86,7 +86,7 @@ func matchEmptyBackRemoval() : M.Matcher, Nat)> { func reduceFront(deque : Queue.Queue, amount : Nat) : Queue.Queue { var current = deque; - for (_ in Iter.range(1, amount)) { + for (_ in Nat.range(1, amount)) { switch (Queue.popFront(current)) { case null Prim.trap("should not be null"); case (?result) current := result.1 @@ -97,7 +97,7 @@ func reduceFront(deque : Queue.Queue, amount : Nat) : Queue.Queue { func reduceBack(deque : Queue.Queue, amount : Nat) : Queue.Queue { var current = deque; - for (_ in Iter.range(1, amount)) { + for (_ in Nat.range(1, amount)) { switch (Queue.popBack(current)) { case null Prim.trap("should not be null"); case (?result) current := result.0 @@ -206,7 +206,7 @@ let testSize = 100; func populateForward(from : Nat, to : Nat) : Queue.Queue { var deque = Queue.empty(); - for (number in Iter.range(from, to)) { + for (number in Nat.range(from, to)) { deque := Queue.pushFront(deque, number) }; deque @@ -286,7 +286,7 @@ run( func populateBackward(from : Nat, to : Nat) : Queue.Queue { var deque = Queue.empty(); - for (number in Iter.range(from, to)) { + for (number in Nat.range(from, to)) { deque := Queue.pushBack(deque, number) }; deque @@ -379,7 +379,7 @@ object Random { func randomPopulate(amount : Nat) : Queue.Queue { var current = Queue.empty(); - for (number in Iter.range(1, amount)) { + for (number in Nat.range(1, amount)) { current := if (Random.next() % 2 == 0) { Queue.pushFront(current, Nat.sub(amount, number)) } else { @@ -397,7 +397,7 @@ func isSorted(deque : Queue.Queue) : Bool { func randomRemoval(deque : Queue.Queue, amount : Nat) : Queue.Queue { var current = deque; - for (number in Iter.range(1, amount)) { + for (number in Nat.range(1, amount)) { current := if (Random.next() % 2 == 0) { let pair = Queue.popFront(current); switch pair { @@ -465,7 +465,7 @@ run( func randomInsertionDeletion(steps : Nat) : Queue.Queue { var current = Queue.empty(); var size = 0; - for (number in Iter.range(1, steps)) { + for (number in Nat.range(1, steps)) { let random = Random.next(); current := switch (random % 4) { case 0 { From e999f16a82a4dd17033300f2f9440c8658620bab Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 19:59:16 +0100 Subject: [PATCH 24/40] fix --- test/pure/Queue.test.mo | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/pure/Queue.test.mo b/test/pure/Queue.test.mo index c3e7ff2f..30c34ca4 100644 --- a/test/pure/Queue.test.mo +++ b/test/pure/Queue.test.mo @@ -86,7 +86,7 @@ func matchEmptyBackRemoval() : M.Matcher, Nat)> { func reduceFront(deque : Queue.Queue, amount : Nat) : Queue.Queue { var current = deque; - for (_ in Nat.range(1, amount)) { + for (_ in Nat.range(0, amount)) { switch (Queue.popFront(current)) { case null Prim.trap("should not be null"); case (?result) current := result.1 @@ -97,7 +97,7 @@ func reduceFront(deque : Queue.Queue, amount : Nat) : Queue.Queue { func reduceBack(deque : Queue.Queue, amount : Nat) : Queue.Queue { var current = deque; - for (_ in Nat.range(1, amount)) { + for (_ in Nat.range(0, amount)) { switch (Queue.popBack(current)) { case null Prim.trap("should not be null"); case (?result) current := result.0 @@ -379,7 +379,7 @@ object Random { func randomPopulate(amount : Nat) : Queue.Queue { var current = Queue.empty(); - for (number in Nat.range(1, amount)) { + for (number in Nat.range(0, amount)) { current := if (Random.next() % 2 == 0) { Queue.pushFront(current, Nat.sub(amount, number)) } else { @@ -397,7 +397,7 @@ func isSorted(deque : Queue.Queue) : Bool { func randomRemoval(deque : Queue.Queue, amount : Nat) : Queue.Queue { var current = deque; - for (number in Nat.range(1, amount)) { + for (number in Nat.range(0, amount)) { current := if (Random.next() % 2 == 0) { let pair = Queue.popFront(current); switch pair { From de2e29a5d6ee2dc0912b83911d1043a4cd73e7fd Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 20:01:47 +0100 Subject: [PATCH 25/40] there is `Nat.range` now --- src/Iter.mo | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/Iter.mo b/src/Iter.mo index d3641f3a..ce7ed29c 100644 --- a/src/Iter.mo +++ b/src/Iter.mo @@ -16,7 +16,7 @@ module { /// the Iterator that cannot be put back, so keep that in mind when sharing /// iterators between consumers. /// - /// An iterater `i` can be iterated over using + /// An iterator `i` can be iterated over using /// ``` /// for (x in i) { /// …do something with x… @@ -389,21 +389,4 @@ module { fromArray(Array.reverse(toArray(iter))) // TODO: optimize }; - /// Creates an iterator that produces all `Nat`s from `x` to `y` including - /// both of the bounds. - /// ```motoko - /// import Iter "mo:base/Iter"; - /// let iter = Iter.range(1, 3); - /// assert(?1 == iter.next()); - /// assert(?2 == iter.next()); - /// assert(?3 == iter.next()); - /// assert(null == iter.next()); - /// ``` - public class range(x : Nat, y : Int) { - var i = x; - public func next() : ?Nat { - if (i > y) null else { let j = i; i += 1; ?j } - } - }; - } From f55f6d8581149c95894ab19274495c9a316b2f62 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 20:02:57 +0100 Subject: [PATCH 26/40] restore --- validation/api/api.lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index a29b5202..47f8f7fa 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -1351,4 +1351,4 @@ "public func values(set : Set) : Iter.Iter" ] } -] +] \ No newline at end of file From 672b5de68a8f8e7f24b0678f51d3922270229e6b Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 21:44:48 +0100 Subject: [PATCH 27/40] add `equal` API to pure `List` --- validation/api/api.lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 47f8f7fa..8c862cc4 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -486,6 +486,7 @@ "public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order", "public func contains(list : List, equal : (T, T) -> Bool, element : T) : Bool", "public func empty() : List", + "public func equal(list1 : List, list2 : List, f : (T, T) -> Bool) : Bool", "public func entries(list : List) : Iter.Iter<(T, Nat)>", "public func entriesRev(list : List) : Iter.Iter<(T, Nat)>", "public func equal(list1 : List, list2 : List, equal : (T, T) -> Bool) : Bool", From cb10fea8399d38629e76881dfda698716a41cfd6 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 21:51:44 +0100 Subject: [PATCH 28/40] simplify --- src/pure/List.mo | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/pure/List.mo b/src/pure/List.mo index 1a36cd1b..e6d49999 100644 --- a/src/pure/List.mo +++ b/src/pure/List.mo @@ -544,20 +544,13 @@ module { /// /// Space: O(1) /// - /// *Runtime and space assumes that `equal` runs in O(1) time and space. - public func equal(list1 : List, list2 : List, equalFunc : (T, T) -> Bool) : Bool { + /// *Runtime and space assumes that `equalFunc` runs in O(1) time and space. + public func equal(list1 : List, list2 : List, equalFunc : (T, T) -> Bool) : Bool = switch (list1, list2) { case (null, null) true; - case (?(h1, t1), ?(h2, t2)) { - if (equalFunc(h1, h2)) { - equal(t1, t2, equalFunc) - } else { - false - } - }; + case (?(h1, t1), ?(h2, t2)) equalFunc(h1, h2) and equal(t1, t2, equalFunc); case _ false; - } - }; + }; /// Compare two lists using lexicographic ordering specified by argument function `compare`. /// From 781fff78d399134b9a0295370377d934d4990444 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Tue, 25 Feb 2025 23:31:02 +0100 Subject: [PATCH 29/40] `npm run validate` --- validation/api/api.lock.json | 1 - 1 file changed, 1 deletion(-) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 8c862cc4..47f8f7fa 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -486,7 +486,6 @@ "public func compare(list1 : List, list2 : List, compare : (T, T) -> Order.Order) : Order.Order", "public func contains(list : List, equal : (T, T) -> Bool, element : T) : Bool", "public func empty() : List", - "public func equal(list1 : List, list2 : List, f : (T, T) -> Bool) : Bool", "public func entries(list : List) : Iter.Iter<(T, Nat)>", "public func entriesRev(list : List) : Iter.Iter<(T, Nat)>", "public func equal(list1 : List, list2 : List, equal : (T, T) -> Bool) : Bool", From 8f568df69f6fef31e8d4843e09aaf8101828e16c Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Wed, 26 Feb 2025 12:53:22 +0100 Subject: [PATCH 30/40] use `trap` instead of `assert` See https://github.com/dfinity/new-motoko-base/pull/170#discussion_r1971433312 --- src/pure/List.mo | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pure/List.mo b/src/pure/List.mo index 276eca4d..b030583e 100644 --- a/src/pure/List.mo +++ b/src/pure/List.mo @@ -13,6 +13,7 @@ import Array "../Array"; import Iter "../Iter"; import Order "../Order"; import Result "../Result"; +import { trap } "../Runtime"; import Types "../Types"; module { @@ -729,7 +730,7 @@ module { /// Space: O(size) public func chunks(list : List, n : Nat) : List> = switch (split(list, n)) { - case (null, _) { assert n > 0; null }; + case (null, _) { if (n == 0) trap "pure/List.chunks()"; null }; case (pre, null) ?(pre, null); case (pre, post) ?(pre, chunks(post, n)); }; From fc238d190ad12bc12c5ff0b55ef25157d7a63375 Mon Sep 17 00:00:00 2001 From: rvanasa Date: Wed, 26 Feb 2025 11:00:25 -0700 Subject: [PATCH 31/40] Rewrite pure Queue tests to use 'mo:test' --- test/pure/Queue.test.mo | 674 +++++++++++++++++++++------------------- 1 file changed, 362 insertions(+), 312 deletions(-) diff --git a/test/pure/Queue.test.mo b/test/pure/Queue.test.mo index 30c34ca4..60261060 100644 --- a/test/pure/Queue.test.mo +++ b/test/pure/Queue.test.mo @@ -1,14 +1,9 @@ -import Suite "mo:matchers/Suite"; -import T "mo:matchers/Testable"; -import M "mo:matchers/Matchers"; - -import Prim "mo:prim"; import Queue "../../src/pure/Queue"; import Array "../../src/Array"; import Nat "../../src/Nat"; import Iter "../../src/Iter"; - -let { run; test; suite } = Suite; +import Prim "mo:prim"; +import { suite; test; expect } = "mo:test"; func iterateForward(deque : Queue.Queue) : Iter.Iter { var current = deque; @@ -55,35 +50,6 @@ func toText(deque : Queue.Queue) : Text { text }; -let natQueueTestable : T.Testable> = object { - public func display(deque : Queue.Queue) : Text { - toText(deque) - }; - public func equals(first : Queue.Queue, second : Queue.Queue) : Bool { - Array.equal(Iter.toArray(iterateForward(first)), Iter.toArray(iterateForward(second)), Nat.equal) - } -}; - -func matchFrontRemoval(element : Nat, remainder : Queue.Queue) : M.Matcher)> { - let testable = T.tuple2Testable(T.natTestable, natQueueTestable); - M.equals(T.optional(testable, ?(element, remainder))) -}; - -func matchEmptyFrontRemoval() : M.Matcher)> { - let testable = T.tuple2Testable(T.natTestable, natQueueTestable); - M.equals(T.optional(testable, null : ?(Nat, Queue.Queue))) -}; - -func matchBackRemoval(remainder : Queue.Queue, element : Nat) : M.Matcher, Nat)> { - let testable = T.tuple2Testable(natQueueTestable, T.natTestable); - M.equals(T.optional(testable, ?(remainder, element))) -}; - -func matchEmptyBackRemoval() : M.Matcher, Nat)> { - let testable = T.tuple2Testable(natQueueTestable, T.natTestable); - M.equals(T.optional(testable, null : ?(Queue.Queue, Nat))) -}; - func reduceFront(deque : Queue.Queue, amount : Nat) : Queue.Queue { var current = deque; for (_ in Nat.range(0, amount)) { @@ -106,101 +72,133 @@ func reduceBack(deque : Queue.Queue, amount : Nat) : Queue.Queue { current }; -/* --------------------------------------- */ - var deque = Queue.empty(); -run( - suite( - "construct", - [ - test( - "empty", - Queue.isEmpty(deque), - M.equals(T.bool(true)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals(T.array(T.natTestable, [])) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals(T.array(T.natTestable, [])) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, null : ?Nat)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, null : ?Nat)) - ), - test( - "pop front", - Queue.popFront(deque), - matchEmptyFrontRemoval() - ), - test( - "pop back", - Queue.popBack(deque), - matchEmptyBackRemoval() - ) - ] - ) -); +suite( + "construct", + func() { + test( + "empty", + func() { + expect.bool(Queue.isEmpty(deque)).isTrue() + } + ); -/* --------------------------------------- */ + test( + "iterate forward", + func() { + expect.array(Iter.toArray(iterateForward(deque)), Nat.toText, Nat.equal).size(0) + } + ); -deque := Queue.pushFront(Queue.empty(), 1); + test( + "iterate backward", + func() { + expect.array(Iter.toArray(iterateBackward(deque)), Nat.toText, Nat.equal).size(0) + } + ); -run( - suite( - "single item", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals(T.array(T.natTestable, [1])) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals(T.array(T.natTestable, [1])) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "pop front", - Queue.popFront(deque), - matchFrontRemoval(1, Queue.empty()) - ), - test( - "pop back", - Queue.popBack(deque), - matchBackRemoval(Queue.empty(), 1) - ) - ] - ) + test( + "peek front", + func() { + expect.option(Queue.peekFront(deque), Nat.toText, Nat.equal).isNull() + } + ); + + test( + "peek back", + func() { + expect.option(Queue.peekBack(deque), Nat.toText, Nat.equal).isNull() + } + ); + + test( + "pop front", + func() { + expect.option<(Nat, Queue.Queue)>( + Queue.popFront(deque), + func(n, _) = Nat.toText(n), + func(a, b) = a.0 == b.0 + ).isNull() + } + ); + + test( + "pop back", + func() { + expect.option<(Queue.Queue, Nat)>( + Queue.popBack(deque), + func(_, n) = Nat.toText(n), + func(a, b) = a.1 == b.1 + ).isNull() + } + ) + } ); -/* --------------------------------------- */ +deque := Queue.pushFront(Queue.empty(), 1); + +suite( + "single item", + func() { + test( + "not empty", + func() { + expect.bool(Queue.isEmpty(deque)).isFalse() + } + ); + + test( + "iterate forward", + func() { + expect.array(Iter.toArray(iterateForward(deque)), Nat.toText, Nat.equal).equal([1]) + } + ); + + test( + "iterate backward", + func() { + expect.array(Iter.toArray(iterateBackward(deque)), Nat.toText, Nat.equal).equal([1]) + } + ); + + test( + "peek front", + func() { + expect.option(Queue.peekFront(deque), Nat.toText, Nat.equal).equal(?1) + } + ); + + test( + "peek back", + func() { + expect.option(Queue.peekBack(deque), Nat.toText, Nat.equal).equal(?1) + } + ); + + test( + "pop front", + func() { + expect.option<(Nat, Queue.Queue)>( + Queue.popFront(deque), + func(n, _) = Nat.toText(n), + func(a, b) = a.0 == b.0 + ).equal(?(1, Queue.empty())) + } + ); + + test( + "pop back", + func() { + expect.option<(Queue.Queue, Nat)>( + Queue.popBack(deque), + func(_, n) = Nat.toText(n), + func(a, b) = a.1 == b.1 + ).equal(?(Queue.empty(), 1)) + } + ) + } +); let testSize = 100; @@ -214,75 +212,92 @@ func populateForward(from : Nat, to : Nat) : Queue.Queue { deque := populateForward(1, testSize); -run( - suite( - "forward insertion", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - testSize - index - } - ) +suite( + "forward insertion", + func() { + test( + "not empty", + func() { + expect.bool(Queue.isEmpty(deque)).isFalse() + } + ); + + test( + "iterate forward", + func() { + expect.array( + Iter.toArray(iterateForward(deque)), + Nat.toText, + Nat.equal + ).equal( + Array.tabulate( + testSize, + func(index : Nat) : Nat { + testSize - index + } ) ) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - index + 1 - } - ) + } + ); + + test( + "iterate backward", + func() { + expect.array( + Iter.toArray(iterateBackward(deque)), + Nat.toText, + Nat.equal + ).equal( + Array.tabulate( + testSize, + func(index : Nat) : Nat { + index + 1 + } ) ) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, ?testSize)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "pop front", - Queue.popFront(deque), - matchFrontRemoval(testSize, populateForward(1, testSize - 1)) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceFront(deque, testSize)), - M.equals(T.bool(true)) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceBack(deque, testSize)), - M.equals(T.bool(true)) - ) - ] - ) -); + } + ); -/* --------------------------------------- */ + test( + "peek front", + func() { + expect.option(Queue.peekFront(deque), Nat.toText, Nat.equal).equal(?testSize) + } + ); + + test( + "peek back", + func() { + expect.option(Queue.peekBack(deque), Nat.toText, Nat.equal).equal(?1) + } + ); + + test( + "pop front", + func() { + expect.option<(Nat, Queue.Queue)>( + Queue.popFront(deque), + func(n, _) = Nat.toText(n), + func(a, b) = a.0 == b.0 + ).equal(?(testSize, populateForward(1, testSize - 1))) + } + ); + + test( + "empty after front removal", + func() { + expect.bool(Queue.isEmpty(reduceFront(deque, testSize))).isTrue() + } + ); + + test( + "empty after back removal", + func() { + expect.bool(Queue.isEmpty(reduceBack(deque, testSize))).isTrue() + } + ) + } +); func populateBackward(from : Nat, to : Nat) : Queue.Queue { var deque = Queue.empty(); @@ -294,80 +309,103 @@ func populateBackward(from : Nat, to : Nat) : Queue.Queue { deque := populateBackward(1, testSize); -run( - suite( - "backward insertion", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - index + 1 - } - ) +suite( + "backward insertion", + func() { + test( + "not empty", + func() { + expect.bool(Queue.isEmpty(deque)).isFalse() + } + ); + + test( + "iterate forward", + func() { + expect.array( + Iter.toArray(iterateForward(deque)), + Nat.toText, + Nat.equal + ).equal( + Array.tabulate( + testSize, + func(index : Nat) : Nat { + index + 1 + } ) ) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - testSize - index - } - ) + } + ); + + test( + "iterate backward", + func() { + expect.array( + Iter.toArray(iterateBackward(deque)), + Nat.toText, + Nat.equal + ).equal( + Array.tabulate( + testSize, + func(index : Nat) : Nat { + testSize - index + } ) ) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, ?testSize)) - ), - test( - "pop front", - Queue.popFront(deque), - matchFrontRemoval(1, populateBackward(2, testSize)) - ), - test( - "pop back", - Queue.popBack(deque), - matchBackRemoval(populateBackward(1, testSize - 1), testSize) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceFront(deque, testSize)), - M.equals(T.bool(true)) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceBack(deque, testSize)), - M.equals(T.bool(true)) - ) - ] - ) -); + } + ); + + test( + "peek front", + func() { + expect.option(Queue.peekFront(deque), Nat.toText, Nat.equal).equal(?1) + } + ); + + test( + "peek back", + func() { + expect.option(Queue.peekBack(deque), Nat.toText, Nat.equal).equal(?testSize) + } + ); + + test( + "pop front", + func() { + expect.option<(Nat, Queue.Queue)>( + Queue.popFront(deque), + func(n, _) = Nat.toText(n), + func(a, b) = a.0 == b.0 + ).equal(?(1, populateBackward(2, testSize))) + } + ); + + test( + "pop back", + func() { + expect.option<(Queue.Queue, Nat)>( + Queue.popBack(deque), + func(_, n) = Nat.toText(n), + func(a, b) = a.1 == b.1 + ).equal(?(populateBackward(1, testSize - 1), testSize)) + } + ); + + test( + "empty after front removal", + func() { + expect.bool(Queue.isEmpty(reduceFront(deque, testSize))).isTrue() + } + ); -/* --------------------------------------- */ + test( + "empty after back removal", + func() { + expect.bool(Queue.isEmpty(reduceBack(deque, testSize))).isTrue() + } + ) + } +); object Random { var number = 4711; @@ -417,55 +455,68 @@ func randomRemoval(deque : Queue.Queue, amount : Nat) : Queue.Queue { deque := randomPopulate(testSize); -run( - suite( - "random insertion", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "correct order", - isSorted(deque), - M.equals(T.bool(true)) - ), - test( - "consistent iteration", - Iter.toArray(iterateForward(deque)), - M.equals(T.array(T.natTestable, Array.reverse(Iter.toArray(iterateBackward(deque))))) - ), - test( - "random quarter removal", - isSorted(randomRemoval(deque, testSize / 4)), - M.equals(T.bool(true)) - ), - test( - "random half removal", - isSorted(randomRemoval(deque, testSize / 2)), - M.equals(T.bool(true)) - ), - test( - "random three quarter removal", - isSorted(randomRemoval(deque, testSize * 3 / 4)), - M.equals(T.bool(true)) - ), - test( - "random total removal", - Queue.isEmpty(randomRemoval(deque, testSize)), - M.equals(T.bool(true)) - ) - ] - ) -); +suite( + "random insertion", + func() { + test( + "not empty", + func() { + expect.bool(Queue.isEmpty(deque)).isFalse() + } + ); + + test( + "correct order", + func() { + expect.bool(isSorted(deque)).isTrue() + } + ); + + test( + "consistent iteration", + func() { + expect.array( + Iter.toArray(iterateForward(deque)), + Nat.toText, + Nat.equal + ).equal(Array.reverse(Iter.toArray(iterateBackward(deque)))) + } + ); -/* --------------------------------------- */ + test( + "random quarter removal", + func() { + expect.bool(isSorted(randomRemoval(deque, testSize / 4))).isTrue() + } + ); + + test( + "random half removal", + func() { + expect.bool(isSorted(randomRemoval(deque, testSize / 2))).isTrue() + } + ); + + test( + "random three quarter removal", + func() { + expect.bool(isSorted(randomRemoval(deque, testSize * 3 / 4))).isTrue() + } + ); + + test( + "random total removal", + func() { + expect.bool(Queue.isEmpty(randomRemoval(deque, testSize))).isTrue() + } + ) + } +); func randomInsertionDeletion(steps : Nat) : Queue.Queue { var current = Queue.empty(); var size = 0; - for (number in Nat.range(1, steps)) { + for (number in Nat.range(0, steps - 1)) { let random = Random.next(); current := switch (random % 4) { case 0 { @@ -507,15 +558,14 @@ func randomInsertionDeletion(steps : Nat) : Queue.Queue { current }; -run( - suite( - "completely random", - [ - test( - "random insertion and deletion", - isSorted(randomInsertionDeletion(1000)), - M.equals(T.bool(true)) - ) - ] - ) +suite( + "completely random", + func() { + test( + "random insertion and deletion", + func() { + expect.bool(isSorted(randomInsertionDeletion(1000))).isTrue() + } + ) + } ) From 1cd5fcc14be1673abedb05b29b489005aa0c57c1 Mon Sep 17 00:00:00 2001 From: rvanasa Date: Wed, 26 Feb 2025 11:48:59 -0700 Subject: [PATCH 32/40] Fix tests --- src/pure/Queue.mo | 23 +++-- test/pure/Queue.test.mo | 192 ++++++++++++++++++++++------------------ 2 files changed, 121 insertions(+), 94 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 3fb59075..70ca0cc4 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -24,9 +24,8 @@ import Iter "../Iter"; import List "List"; import Order "../Order"; import Types "../Types"; -import { todo } "../Debug"; -module Queue /* FIXME */ { +module Queue { type List = Types.Pure.List; /// Double-ended queue data type. @@ -146,7 +145,7 @@ module Queue /* FIXME */ { else switch list { case null (null, null); case (?(h, t)) { - let (f, b) = takeDrop(t, n - 1); + let (f, b) = takeDrop(t, n - 1 : Nat); (?(h, f), b) } }; @@ -289,8 +288,20 @@ module Queue /* FIXME */ { public func values(queue : Queue) : Iter.Iter = Iter.concat(List.values(queue.0), List.values(List.reverse(queue.2))); - public func equal(queue1 : Queue, queue2 : Queue) : Bool { - todo() + public func equal(queue1 : Queue, queue2 : Queue, equal : (T, T) -> Bool) : Bool { + if (queue1.1 != queue2.1) { + return false; + }; + let (iter1, iter2) = (values(queue1), values(queue2)); + loop { + switch (iter1.next(), iter2.next()) { + case (null, null) { return true }; + case (?v1, ?v2) { + if (not equal(v1, v2)) { return false }; + }; + case (_, _) { return false }; + }; + }; }; public func all(queue : Queue, predicate : T -> Bool) : Bool { @@ -322,7 +333,7 @@ module Queue /* FIXME */ { }; public func filterMap(queue : Queue, f : T -> ?U) : Queue { - let (fr, n, b) = queue; + let (fr, _n, b) = queue; let front = List.filterMap(fr, f); let back = List.filterMap(b, f); (front, List.size front + List.size back, back) diff --git a/test/pure/Queue.test.mo b/test/pure/Queue.test.mo index 60261060..38ee1e5f 100644 --- a/test/pure/Queue.test.mo +++ b/test/pure/Queue.test.mo @@ -5,8 +5,8 @@ import Iter "../../src/Iter"; import Prim "mo:prim"; import { suite; test; expect } = "mo:test"; -func iterateForward(deque : Queue.Queue) : Iter.Iter { - var current = deque; +func iterateForward(queue : Queue.Queue) : Iter.Iter { + var current = queue; object { public func next() : ?T { switch (Queue.popFront(current)) { @@ -20,8 +20,8 @@ func iterateForward(deque : Queue.Queue) : Iter.Iter { } }; -func iterateBackward(deque : Queue.Queue) : Iter.Iter { - var current = deque; +func iterateBackward(queue : Queue.Queue) : Iter.Iter { + var current = queue; object { public func next() : ?T { switch (Queue.popBack(current)) { @@ -35,10 +35,10 @@ func iterateBackward(deque : Queue.Queue) : Iter.Iter { } }; -func toText(deque : Queue.Queue) : Text { +func toText(queue : Queue.Queue) : Text { var text = "["; var isFirst = true; - for (element in iterateForward(deque)) { + for (element in iterateForward(queue)) { if (not isFirst) { text #= ", " } else { @@ -50,8 +50,24 @@ func toText(deque : Queue.Queue) : Text { text }; -func reduceFront(deque : Queue.Queue, amount : Nat) : Queue.Queue { - var current = deque; +func frontToText(t : (Nat, Queue.Queue)) : Text { + "(" # Nat.toText(t.0) # ", " # toText(t.1) # ")" +}; + +func frontEqual(t1 : (Nat, Queue.Queue), t2 : (Nat, Queue.Queue)) : Bool { + t1.0 == t2.0 and Queue.equal(t1.1, t2.1, Nat.equal) +}; + +func backToText(t : (Queue.Queue, Nat)) : Text { + "(" # toText(t.0) # ", " # Nat.toText(t.1) # ")" +}; + +func backEqual(t1 : (Queue.Queue, Nat), t2 : (Queue.Queue, Nat)) : Bool { + t1.1 == t2.1 and Queue.equal(t1.0, t2.0, Nat.equal) +}; + +func reduceFront(queue : Queue.Queue, amount : Nat) : Queue.Queue { + var current = queue; for (_ in Nat.range(0, amount)) { switch (Queue.popFront(current)) { case null Prim.trap("should not be null"); @@ -61,8 +77,8 @@ func reduceFront(deque : Queue.Queue, amount : Nat) : Queue.Queue { current }; -func reduceBack(deque : Queue.Queue, amount : Nat) : Queue.Queue { - var current = deque; +func reduceBack(queue : Queue.Queue, amount : Nat) : Queue.Queue { + var current = queue; for (_ in Nat.range(0, amount)) { switch (Queue.popBack(current)) { case null Prim.trap("should not be null"); @@ -72,7 +88,7 @@ func reduceBack(deque : Queue.Queue, amount : Nat) : Queue.Queue { current }; -var deque = Queue.empty(); +var queue = Queue.empty(); suite( "construct", @@ -80,45 +96,45 @@ suite( test( "empty", func() { - expect.bool(Queue.isEmpty(deque)).isTrue() + expect.bool(Queue.isEmpty(queue)).isTrue() } ); test( "iterate forward", func() { - expect.array(Iter.toArray(iterateForward(deque)), Nat.toText, Nat.equal).size(0) + expect.array(Iter.toArray(iterateForward(queue)), Nat.toText, Nat.equal).size(0) } ); test( "iterate backward", func() { - expect.array(Iter.toArray(iterateBackward(deque)), Nat.toText, Nat.equal).size(0) + expect.array(Iter.toArray(iterateBackward(queue)), Nat.toText, Nat.equal).size(0) } ); test( "peek front", func() { - expect.option(Queue.peekFront(deque), Nat.toText, Nat.equal).isNull() + expect.option(Queue.peekFront(queue), Nat.toText, Nat.equal).isNull() } ); test( "peek back", func() { - expect.option(Queue.peekBack(deque), Nat.toText, Nat.equal).isNull() + expect.option(Queue.peekBack(queue), Nat.toText, Nat.equal).isNull() } ); test( "pop front", func() { - expect.option<(Nat, Queue.Queue)>( - Queue.popFront(deque), - func(n, _) = Nat.toText(n), - func(a, b) = a.0 == b.0 + expect.option( + Queue.popFront(queue), + frontToText, + frontEqual ).isNull() } ); @@ -126,17 +142,17 @@ suite( test( "pop back", func() { - expect.option<(Queue.Queue, Nat)>( - Queue.popBack(deque), - func(_, n) = Nat.toText(n), - func(a, b) = a.1 == b.1 + expect.option( + Queue.popBack(queue), + backToText, + backEqual ).isNull() } ) } ); -deque := Queue.pushFront(Queue.empty(), 1); +queue := Queue.pushFront(Queue.empty(), 1); suite( "single item", @@ -144,45 +160,45 @@ suite( test( "not empty", func() { - expect.bool(Queue.isEmpty(deque)).isFalse() + expect.bool(Queue.isEmpty(queue)).isFalse() } ); test( "iterate forward", func() { - expect.array(Iter.toArray(iterateForward(deque)), Nat.toText, Nat.equal).equal([1]) + expect.array(Iter.toArray(iterateForward(queue)), Nat.toText, Nat.equal).equal([1]) } ); test( "iterate backward", func() { - expect.array(Iter.toArray(iterateBackward(deque)), Nat.toText, Nat.equal).equal([1]) + expect.array(Iter.toArray(iterateBackward(queue)), Nat.toText, Nat.equal).equal([1]) } ); test( "peek front", func() { - expect.option(Queue.peekFront(deque), Nat.toText, Nat.equal).equal(?1) + expect.option(Queue.peekFront(queue), Nat.toText, Nat.equal).equal(?1) } ); test( "peek back", func() { - expect.option(Queue.peekBack(deque), Nat.toText, Nat.equal).equal(?1) + expect.option(Queue.peekBack(queue), Nat.toText, Nat.equal).equal(?1) } ); test( "pop front", func() { - expect.option<(Nat, Queue.Queue)>( - Queue.popFront(deque), - func(n, _) = Nat.toText(n), - func(a, b) = a.0 == b.0 + expect.option( + Queue.popFront(queue), + frontToText, + frontEqual ).equal(?(1, Queue.empty())) } ); @@ -190,10 +206,10 @@ suite( test( "pop back", func() { - expect.option<(Queue.Queue, Nat)>( - Queue.popBack(deque), - func(_, n) = Nat.toText(n), - func(a, b) = a.1 == b.1 + expect.option( + Queue.popBack(queue), + backToText, + backEqual ).equal(?(Queue.empty(), 1)) } ) @@ -203,14 +219,14 @@ suite( let testSize = 100; func populateForward(from : Nat, to : Nat) : Queue.Queue { - var deque = Queue.empty(); + var queue = Queue.empty(); for (number in Nat.range(from, to)) { - deque := Queue.pushFront(deque, number) + queue := Queue.pushFront(queue, number) }; - deque + queue }; -deque := populateForward(1, testSize); +queue := populateForward(1, testSize + 1); suite( "forward insertion", @@ -218,7 +234,7 @@ suite( test( "not empty", func() { - expect.bool(Queue.isEmpty(deque)).isFalse() + expect.bool(Queue.isEmpty(queue)).isFalse() } ); @@ -226,7 +242,7 @@ suite( "iterate forward", func() { expect.array( - Iter.toArray(iterateForward(deque)), + Iter.toArray(iterateForward(queue)), Nat.toText, Nat.equal ).equal( @@ -244,7 +260,7 @@ suite( "iterate backward", func() { expect.array( - Iter.toArray(iterateBackward(deque)), + Iter.toArray(iterateBackward(queue)), Nat.toText, Nat.equal ).equal( @@ -261,53 +277,53 @@ suite( test( "peek front", func() { - expect.option(Queue.peekFront(deque), Nat.toText, Nat.equal).equal(?testSize) + expect.option(Queue.peekFront(queue), Nat.toText, Nat.equal).equal(?testSize) } ); test( "peek back", func() { - expect.option(Queue.peekBack(deque), Nat.toText, Nat.equal).equal(?1) + expect.option(Queue.peekBack(queue), Nat.toText, Nat.equal).equal(?1) } ); test( "pop front", func() { - expect.option<(Nat, Queue.Queue)>( - Queue.popFront(deque), - func(n, _) = Nat.toText(n), - func(a, b) = a.0 == b.0 - ).equal(?(testSize, populateForward(1, testSize - 1))) + expect.option( + Queue.popFront(queue), + frontToText, + frontEqual + ).equal(?(testSize, populateForward(1, testSize))) } ); test( "empty after front removal", func() { - expect.bool(Queue.isEmpty(reduceFront(deque, testSize))).isTrue() + expect.bool(Queue.isEmpty(reduceFront(queue, testSize))).isTrue() } ); test( "empty after back removal", func() { - expect.bool(Queue.isEmpty(reduceBack(deque, testSize))).isTrue() + expect.bool(Queue.isEmpty(reduceBack(queue, testSize))).isTrue() } ) } ); func populateBackward(from : Nat, to : Nat) : Queue.Queue { - var deque = Queue.empty(); + var queue = Queue.empty(); for (number in Nat.range(from, to)) { - deque := Queue.pushBack(deque, number) + queue := Queue.pushBack(queue, number) }; - deque + queue }; -deque := populateBackward(1, testSize); +queue := populateBackward(1, testSize + 1); suite( "backward insertion", @@ -315,7 +331,7 @@ suite( test( "not empty", func() { - expect.bool(Queue.isEmpty(deque)).isFalse() + expect.bool(Queue.isEmpty(queue)).isFalse() } ); @@ -323,7 +339,7 @@ suite( "iterate forward", func() { expect.array( - Iter.toArray(iterateForward(deque)), + Iter.toArray(iterateForward(queue)), Nat.toText, Nat.equal ).equal( @@ -341,7 +357,7 @@ suite( "iterate backward", func() { expect.array( - Iter.toArray(iterateBackward(deque)), + Iter.toArray(iterateBackward(queue)), Nat.toText, Nat.equal ).equal( @@ -358,50 +374,50 @@ suite( test( "peek front", func() { - expect.option(Queue.peekFront(deque), Nat.toText, Nat.equal).equal(?1) + expect.option(Queue.peekFront(queue), Nat.toText, Nat.equal).equal(?1) } ); test( "peek back", func() { - expect.option(Queue.peekBack(deque), Nat.toText, Nat.equal).equal(?testSize) + expect.option(Queue.peekBack(queue), Nat.toText, Nat.equal).equal(?testSize) } ); test( "pop front", func() { - expect.option<(Nat, Queue.Queue)>( - Queue.popFront(deque), - func(n, _) = Nat.toText(n), - func(a, b) = a.0 == b.0 - ).equal(?(1, populateBackward(2, testSize))) + expect.option( + Queue.popFront(queue), + frontToText, + frontEqual + ).equal(?(1, populateBackward(2, testSize + 1))) } ); test( "pop back", func() { - expect.option<(Queue.Queue, Nat)>( - Queue.popBack(deque), - func(_, n) = Nat.toText(n), - func(a, b) = a.1 == b.1 - ).equal(?(populateBackward(1, testSize - 1), testSize)) + expect.option( + Queue.popBack(queue), + backToText, + backEqual + ).equal(?(populateBackward(1, testSize), testSize)) } ); test( "empty after front removal", func() { - expect.bool(Queue.isEmpty(reduceFront(deque, testSize))).isTrue() + expect.bool(Queue.isEmpty(reduceFront(queue, testSize))).isTrue() } ); test( "empty after back removal", func() { - expect.bool(Queue.isEmpty(reduceBack(deque, testSize))).isTrue() + expect.bool(Queue.isEmpty(reduceBack(queue, testSize))).isTrue() } ) } @@ -427,14 +443,14 @@ func randomPopulate(amount : Nat) : Queue.Queue { current }; -func isSorted(deque : Queue.Queue) : Bool { - let array = Iter.toArray(iterateForward(deque)); +func isSorted(queue : Queue.Queue) : Bool { + let array = Iter.toArray(iterateForward(queue)); let sorted = Array.sort(array, Nat.compare); Array.equal(array, sorted, Nat.equal) }; -func randomRemoval(deque : Queue.Queue, amount : Nat) : Queue.Queue { - var current = deque; +func randomRemoval(queue : Queue.Queue, amount : Nat) : Queue.Queue { + var current = queue; for (number in Nat.range(0, amount)) { current := if (Random.next() % 2 == 0) { let pair = Queue.popFront(current); @@ -453,7 +469,7 @@ func randomRemoval(deque : Queue.Queue, amount : Nat) : Queue.Queue { current }; -deque := randomPopulate(testSize); +queue := randomPopulate(testSize); suite( "random insertion", @@ -461,14 +477,14 @@ suite( test( "not empty", func() { - expect.bool(Queue.isEmpty(deque)).isFalse() + expect.bool(Queue.isEmpty(queue)).isFalse() } ); test( "correct order", func() { - expect.bool(isSorted(deque)).isTrue() + expect.bool(isSorted(queue)).isTrue() } ); @@ -476,38 +492,38 @@ suite( "consistent iteration", func() { expect.array( - Iter.toArray(iterateForward(deque)), + Iter.toArray(iterateForward(queue)), Nat.toText, Nat.equal - ).equal(Array.reverse(Iter.toArray(iterateBackward(deque)))) + ).equal(Array.reverse(Iter.toArray(iterateBackward(queue)))) } ); test( "random quarter removal", func() { - expect.bool(isSorted(randomRemoval(deque, testSize / 4))).isTrue() + expect.bool(isSorted(randomRemoval(queue, testSize / 4))).isTrue() } ); test( "random half removal", func() { - expect.bool(isSorted(randomRemoval(deque, testSize / 2))).isTrue() + expect.bool(isSorted(randomRemoval(queue, testSize / 2))).isTrue() } ); test( "random three quarter removal", func() { - expect.bool(isSorted(randomRemoval(deque, testSize * 3 / 4))).isTrue() + expect.bool(isSorted(randomRemoval(queue, testSize * 3 / 4))).isTrue() } ); test( "random total removal", func() { - expect.bool(Queue.isEmpty(randomRemoval(deque, testSize))).isTrue() + expect.bool(Queue.isEmpty(randomRemoval(queue, testSize))).isTrue() } ) } From ae290b37c669247cc4320f4cb7a4faf1271ed0e5 Mon Sep 17 00:00:00 2001 From: rvanasa Date: Wed, 26 Feb 2025 11:49:25 -0700 Subject: [PATCH 33/40] Update API lockfile --- validation/api/api.lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 47f8f7fa..17809ce8 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -1297,7 +1297,7 @@ "public func compare(queue1 : Queue, queue2 : Queue, compare : (T, T) -> Order.Order) : Order.Order", "public func contains(queue : Queue, equal : (T, T) -> Bool, item : T) : Bool", "public func empty() : Queue", - "public func equal(queue1 : Queue, queue2 : Queue) : Bool", + "public func equal(queue1 : Queue, queue2 : Queue, equal : (T, T) -> Bool) : Bool", "public func filter(queue : Queue, f : T -> Bool) : Queue", "public func filterMap(queue : Queue, f : T -> ?U) : Queue", "public func forEach(queue : Queue, f : T -> ())", From d3fdf11e2a65e421a67fa8654f204c50d528262a Mon Sep 17 00:00:00 2001 From: rvanasa Date: Wed, 26 Feb 2025 12:48:26 -0700 Subject: [PATCH 34/40] Simplify testing logic --- test/pure/Queue.test.mo | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/test/pure/Queue.test.mo b/test/pure/Queue.test.mo index 38ee1e5f..9a5c656f 100644 --- a/test/pure/Queue.test.mo +++ b/test/pure/Queue.test.mo @@ -35,23 +35,8 @@ func iterateBackward(queue : Queue.Queue) : Iter.Iter { } }; -func toText(queue : Queue.Queue) : Text { - var text = "["; - var isFirst = true; - for (element in iterateForward(queue)) { - if (not isFirst) { - text #= ", " - } else { - isFirst := false - }; - text #= debug_show (element) - }; - text #= "]"; - text -}; - func frontToText(t : (Nat, Queue.Queue)) : Text { - "(" # Nat.toText(t.0) # ", " # toText(t.1) # ")" + "(" # Nat.toText(t.0) # ", " # Queue.toText(t.1, Nat.toText) # ")" }; func frontEqual(t1 : (Nat, Queue.Queue), t2 : (Nat, Queue.Queue)) : Bool { @@ -59,7 +44,7 @@ func frontEqual(t1 : (Nat, Queue.Queue), t2 : (Nat, Queue.Queue)) : Bo }; func backToText(t : (Queue.Queue, Nat)) : Text { - "(" # toText(t.0) # ", " # Nat.toText(t.1) # ")" + "(" # Queue.toText(t.0, Nat.toText) # ", " # Nat.toText(t.1) # ")" }; func backEqual(t1 : (Queue.Queue, Nat), t2 : (Queue.Queue, Nat)) : Bool { From cc92c1b4abf773715bde74d2ef9f8eab907c6050 Mon Sep 17 00:00:00 2001 From: rvanasa Date: Wed, 26 Feb 2025 12:50:43 -0700 Subject: [PATCH 35/40] Remove module name --- src/pure/Queue.mo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 70ca0cc4..653ed876 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -25,7 +25,7 @@ import List "List"; import Order "../Order"; import Types "../Types"; -module Queue { +module { type List = Types.Pure.List; /// Double-ended queue data type. From e8b51fb93825b542c219fb84176384790688c39c Mon Sep 17 00:00:00 2001 From: rvanasa Date: Wed, 26 Feb 2025 12:55:38 -0700 Subject: [PATCH 36/40] Implement mutable Queue toPure() and fromPure() --- src/Queue.mo | 59 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/src/Queue.mo b/src/Queue.mo index b78de6c0..7571180a 100644 --- a/src/Queue.mo +++ b/src/Queue.mo @@ -43,19 +43,66 @@ import Iter "Iter"; import Order "Order"; import Types "Types"; +import PureQueue "pure/Queue"; module { public type Queue = Types.Queue.Queue; type Node = Types.Queue.Node; - // public func toPure(queue : Queue) : PureQueue.Queue { - // todo(); - // }; + /// Converts a mutable queue to an immutable, purely functional queue. + /// + /// Example: + /// ```motoko + /// import Queue "mo:base/Queue"; + /// + /// persistent actor { + /// let queue = Queue.fromIter([1, 2, 3].values()); + /// let pureQueue = Queue.toPure(queue); + /// } + /// ``` + /// + /// Runtime: O(n) + /// Space: O(n) + /// `n` denotes the number of elements stored in the queue. + public func toPure(queue : Queue) : PureQueue.Queue { + let pureQueue = PureQueue.empty(); + let iter = values(queue); + var current = pureQueue; + loop { + switch(iter.next()) { + case null { return current }; + case (?val) { current := PureQueue.pushBack(current, val) }; + }; + }; + }; - // public func fromPure(queue : PureQueue.Queue) : Queue { - // todo(); - // }; + /// Converts an immutable, purely functional queue to a mutable queue. + /// + /// Example: + /// ```motoko + /// import Queue "mo:base/Queue"; + /// import PureQueue "mo:base/pure/Queue"; + /// + /// persistent actor { + /// let pureQueue = PureQueue.fromIter([1, 2, 3].values()); + /// let queue = Queue.fromPure(pureQueue); + /// } + /// ``` + /// + /// Runtime: O(n) + /// Space: O(n) + /// `n` denotes the number of elements stored in the queue. + public func fromPure(queue : PureQueue.Queue) : Queue { + let mutableQueue = empty(); + let iter = PureQueue.values(queue); + loop { + switch(iter.next()) { + case null { return mutableQueue }; + case (?val) { pushBack(mutableQueue, val) }; + }; + }; + }; /// Create a new empty mutable double-ended queue. /// From afd3ea4bb49109f52e1826473f0288e4c22daab3 Mon Sep 17 00:00:00 2001 From: Ryan Vandersmith Date: Wed, 26 Feb 2025 12:55:52 -0700 Subject: [PATCH 37/40] Rewrite `pure/Queue` tests to use `test` package (#179) * Rewrite pure Queue tests to use 'mo:test' * Fix tests * Update API lockfile --- src/pure/Queue.mo | 23 +- test/pure/Queue.test.mo | 734 +++++++++++++++++++---------------- validation/api/api.lock.json | 2 +- 3 files changed, 418 insertions(+), 341 deletions(-) diff --git a/src/pure/Queue.mo b/src/pure/Queue.mo index 3fb59075..70ca0cc4 100644 --- a/src/pure/Queue.mo +++ b/src/pure/Queue.mo @@ -24,9 +24,8 @@ import Iter "../Iter"; import List "List"; import Order "../Order"; import Types "../Types"; -import { todo } "../Debug"; -module Queue /* FIXME */ { +module Queue { type List = Types.Pure.List; /// Double-ended queue data type. @@ -146,7 +145,7 @@ module Queue /* FIXME */ { else switch list { case null (null, null); case (?(h, t)) { - let (f, b) = takeDrop(t, n - 1); + let (f, b) = takeDrop(t, n - 1 : Nat); (?(h, f), b) } }; @@ -289,8 +288,20 @@ module Queue /* FIXME */ { public func values(queue : Queue) : Iter.Iter = Iter.concat(List.values(queue.0), List.values(List.reverse(queue.2))); - public func equal(queue1 : Queue, queue2 : Queue) : Bool { - todo() + public func equal(queue1 : Queue, queue2 : Queue, equal : (T, T) -> Bool) : Bool { + if (queue1.1 != queue2.1) { + return false; + }; + let (iter1, iter2) = (values(queue1), values(queue2)); + loop { + switch (iter1.next(), iter2.next()) { + case (null, null) { return true }; + case (?v1, ?v2) { + if (not equal(v1, v2)) { return false }; + }; + case (_, _) { return false }; + }; + }; }; public func all(queue : Queue, predicate : T -> Bool) : Bool { @@ -322,7 +333,7 @@ module Queue /* FIXME */ { }; public func filterMap(queue : Queue, f : T -> ?U) : Queue { - let (fr, n, b) = queue; + let (fr, _n, b) = queue; let front = List.filterMap(fr, f); let back = List.filterMap(b, f); (front, List.size front + List.size back, back) diff --git a/test/pure/Queue.test.mo b/test/pure/Queue.test.mo index 30c34ca4..38ee1e5f 100644 --- a/test/pure/Queue.test.mo +++ b/test/pure/Queue.test.mo @@ -1,17 +1,12 @@ -import Suite "mo:matchers/Suite"; -import T "mo:matchers/Testable"; -import M "mo:matchers/Matchers"; - -import Prim "mo:prim"; import Queue "../../src/pure/Queue"; import Array "../../src/Array"; import Nat "../../src/Nat"; import Iter "../../src/Iter"; +import Prim "mo:prim"; +import { suite; test; expect } = "mo:test"; -let { run; test; suite } = Suite; - -func iterateForward(deque : Queue.Queue) : Iter.Iter { - var current = deque; +func iterateForward(queue : Queue.Queue) : Iter.Iter { + var current = queue; object { public func next() : ?T { switch (Queue.popFront(current)) { @@ -25,8 +20,8 @@ func iterateForward(deque : Queue.Queue) : Iter.Iter { } }; -func iterateBackward(deque : Queue.Queue) : Iter.Iter { - var current = deque; +func iterateBackward(queue : Queue.Queue) : Iter.Iter { + var current = queue; object { public func next() : ?T { switch (Queue.popBack(current)) { @@ -40,10 +35,10 @@ func iterateBackward(deque : Queue.Queue) : Iter.Iter { } }; -func toText(deque : Queue.Queue) : Text { +func toText(queue : Queue.Queue) : Text { var text = "["; var isFirst = true; - for (element in iterateForward(deque)) { + for (element in iterateForward(queue)) { if (not isFirst) { text #= ", " } else { @@ -55,37 +50,24 @@ func toText(deque : Queue.Queue) : Text { text }; -let natQueueTestable : T.Testable> = object { - public func display(deque : Queue.Queue) : Text { - toText(deque) - }; - public func equals(first : Queue.Queue, second : Queue.Queue) : Bool { - Array.equal(Iter.toArray(iterateForward(first)), Iter.toArray(iterateForward(second)), Nat.equal) - } +func frontToText(t : (Nat, Queue.Queue)) : Text { + "(" # Nat.toText(t.0) # ", " # toText(t.1) # ")" }; -func matchFrontRemoval(element : Nat, remainder : Queue.Queue) : M.Matcher)> { - let testable = T.tuple2Testable(T.natTestable, natQueueTestable); - M.equals(T.optional(testable, ?(element, remainder))) +func frontEqual(t1 : (Nat, Queue.Queue), t2 : (Nat, Queue.Queue)) : Bool { + t1.0 == t2.0 and Queue.equal(t1.1, t2.1, Nat.equal) }; -func matchEmptyFrontRemoval() : M.Matcher)> { - let testable = T.tuple2Testable(T.natTestable, natQueueTestable); - M.equals(T.optional(testable, null : ?(Nat, Queue.Queue))) +func backToText(t : (Queue.Queue, Nat)) : Text { + "(" # toText(t.0) # ", " # Nat.toText(t.1) # ")" }; -func matchBackRemoval(remainder : Queue.Queue, element : Nat) : M.Matcher, Nat)> { - let testable = T.tuple2Testable(natQueueTestable, T.natTestable); - M.equals(T.optional(testable, ?(remainder, element))) +func backEqual(t1 : (Queue.Queue, Nat), t2 : (Queue.Queue, Nat)) : Bool { + t1.1 == t2.1 and Queue.equal(t1.0, t2.0, Nat.equal) }; -func matchEmptyBackRemoval() : M.Matcher, Nat)> { - let testable = T.tuple2Testable(natQueueTestable, T.natTestable); - M.equals(T.optional(testable, null : ?(Queue.Queue, Nat))) -}; - -func reduceFront(deque : Queue.Queue, amount : Nat) : Queue.Queue { - var current = deque; +func reduceFront(queue : Queue.Queue, amount : Nat) : Queue.Queue { + var current = queue; for (_ in Nat.range(0, amount)) { switch (Queue.popFront(current)) { case null Prim.trap("should not be null"); @@ -95,8 +77,8 @@ func reduceFront(deque : Queue.Queue, amount : Nat) : Queue.Queue { current }; -func reduceBack(deque : Queue.Queue, amount : Nat) : Queue.Queue { - var current = deque; +func reduceBack(queue : Queue.Queue, amount : Nat) : Queue.Queue { + var current = queue; for (_ in Nat.range(0, amount)) { switch (Queue.popBack(current)) { case null Prim.trap("should not be null"); @@ -106,268 +88,340 @@ func reduceBack(deque : Queue.Queue, amount : Nat) : Queue.Queue { current }; -/* --------------------------------------- */ - -var deque = Queue.empty(); - -run( - suite( - "construct", - [ - test( - "empty", - Queue.isEmpty(deque), - M.equals(T.bool(true)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals(T.array(T.natTestable, [])) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals(T.array(T.natTestable, [])) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, null : ?Nat)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, null : ?Nat)) - ), - test( - "pop front", - Queue.popFront(deque), - matchEmptyFrontRemoval() - ), - test( - "pop back", - Queue.popBack(deque), - matchEmptyBackRemoval() - ) - ] - ) -); +var queue = Queue.empty(); + +suite( + "construct", + func() { + test( + "empty", + func() { + expect.bool(Queue.isEmpty(queue)).isTrue() + } + ); -/* --------------------------------------- */ - -deque := Queue.pushFront(Queue.empty(), 1); - -run( - suite( - "single item", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals(T.array(T.natTestable, [1])) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals(T.array(T.natTestable, [1])) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "pop front", - Queue.popFront(deque), - matchFrontRemoval(1, Queue.empty()) - ), - test( - "pop back", - Queue.popBack(deque), - matchBackRemoval(Queue.empty(), 1) - ) - ] - ) + test( + "iterate forward", + func() { + expect.array(Iter.toArray(iterateForward(queue)), Nat.toText, Nat.equal).size(0) + } + ); + + test( + "iterate backward", + func() { + expect.array(Iter.toArray(iterateBackward(queue)), Nat.toText, Nat.equal).size(0) + } + ); + + test( + "peek front", + func() { + expect.option(Queue.peekFront(queue), Nat.toText, Nat.equal).isNull() + } + ); + + test( + "peek back", + func() { + expect.option(Queue.peekBack(queue), Nat.toText, Nat.equal).isNull() + } + ); + + test( + "pop front", + func() { + expect.option( + Queue.popFront(queue), + frontToText, + frontEqual + ).isNull() + } + ); + + test( + "pop back", + func() { + expect.option( + Queue.popBack(queue), + backToText, + backEqual + ).isNull() + } + ) + } ); -/* --------------------------------------- */ +queue := Queue.pushFront(Queue.empty(), 1); + +suite( + "single item", + func() { + test( + "not empty", + func() { + expect.bool(Queue.isEmpty(queue)).isFalse() + } + ); + + test( + "iterate forward", + func() { + expect.array(Iter.toArray(iterateForward(queue)), Nat.toText, Nat.equal).equal([1]) + } + ); + + test( + "iterate backward", + func() { + expect.array(Iter.toArray(iterateBackward(queue)), Nat.toText, Nat.equal).equal([1]) + } + ); + + test( + "peek front", + func() { + expect.option(Queue.peekFront(queue), Nat.toText, Nat.equal).equal(?1) + } + ); + + test( + "peek back", + func() { + expect.option(Queue.peekBack(queue), Nat.toText, Nat.equal).equal(?1) + } + ); + + test( + "pop front", + func() { + expect.option( + Queue.popFront(queue), + frontToText, + frontEqual + ).equal(?(1, Queue.empty())) + } + ); + + test( + "pop back", + func() { + expect.option( + Queue.popBack(queue), + backToText, + backEqual + ).equal(?(Queue.empty(), 1)) + } + ) + } +); let testSize = 100; func populateForward(from : Nat, to : Nat) : Queue.Queue { - var deque = Queue.empty(); + var queue = Queue.empty(); for (number in Nat.range(from, to)) { - deque := Queue.pushFront(deque, number) + queue := Queue.pushFront(queue, number) }; - deque + queue }; -deque := populateForward(1, testSize); - -run( - suite( - "forward insertion", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - testSize - index - } - ) +queue := populateForward(1, testSize + 1); + +suite( + "forward insertion", + func() { + test( + "not empty", + func() { + expect.bool(Queue.isEmpty(queue)).isFalse() + } + ); + + test( + "iterate forward", + func() { + expect.array( + Iter.toArray(iterateForward(queue)), + Nat.toText, + Nat.equal + ).equal( + Array.tabulate( + testSize, + func(index : Nat) : Nat { + testSize - index + } ) ) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - index + 1 - } - ) + } + ); + + test( + "iterate backward", + func() { + expect.array( + Iter.toArray(iterateBackward(queue)), + Nat.toText, + Nat.equal + ).equal( + Array.tabulate( + testSize, + func(index : Nat) : Nat { + index + 1 + } ) ) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, ?testSize)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "pop front", - Queue.popFront(deque), - matchFrontRemoval(testSize, populateForward(1, testSize - 1)) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceFront(deque, testSize)), - M.equals(T.bool(true)) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceBack(deque, testSize)), - M.equals(T.bool(true)) - ) - ] - ) -); + } + ); -/* --------------------------------------- */ + test( + "peek front", + func() { + expect.option(Queue.peekFront(queue), Nat.toText, Nat.equal).equal(?testSize) + } + ); + + test( + "peek back", + func() { + expect.option(Queue.peekBack(queue), Nat.toText, Nat.equal).equal(?1) + } + ); + + test( + "pop front", + func() { + expect.option( + Queue.popFront(queue), + frontToText, + frontEqual + ).equal(?(testSize, populateForward(1, testSize))) + } + ); + + test( + "empty after front removal", + func() { + expect.bool(Queue.isEmpty(reduceFront(queue, testSize))).isTrue() + } + ); + + test( + "empty after back removal", + func() { + expect.bool(Queue.isEmpty(reduceBack(queue, testSize))).isTrue() + } + ) + } +); func populateBackward(from : Nat, to : Nat) : Queue.Queue { - var deque = Queue.empty(); + var queue = Queue.empty(); for (number in Nat.range(from, to)) { - deque := Queue.pushBack(deque, number) + queue := Queue.pushBack(queue, number) }; - deque + queue }; -deque := populateBackward(1, testSize); - -run( - suite( - "backward insertion", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "iterate forward", - Iter.toArray(iterateForward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - index + 1 - } - ) +queue := populateBackward(1, testSize + 1); + +suite( + "backward insertion", + func() { + test( + "not empty", + func() { + expect.bool(Queue.isEmpty(queue)).isFalse() + } + ); + + test( + "iterate forward", + func() { + expect.array( + Iter.toArray(iterateForward(queue)), + Nat.toText, + Nat.equal + ).equal( + Array.tabulate( + testSize, + func(index : Nat) : Nat { + index + 1 + } ) ) - ), - test( - "iterate backward", - Iter.toArray(iterateBackward(deque)), - M.equals( - T.array( - T.natTestable, - Array.tabulate( - testSize, - func(index : Nat) : Nat { - testSize - index - } - ) + } + ); + + test( + "iterate backward", + func() { + expect.array( + Iter.toArray(iterateBackward(queue)), + Nat.toText, + Nat.equal + ).equal( + Array.tabulate( + testSize, + func(index : Nat) : Nat { + testSize - index + } ) ) - ), - test( - "peek front", - Queue.peekFront(deque), - M.equals(T.optional(T.natTestable, ?1)) - ), - test( - "peek back", - Queue.peekBack(deque), - M.equals(T.optional(T.natTestable, ?testSize)) - ), - test( - "pop front", - Queue.popFront(deque), - matchFrontRemoval(1, populateBackward(2, testSize)) - ), - test( - "pop back", - Queue.popBack(deque), - matchBackRemoval(populateBackward(1, testSize - 1), testSize) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceFront(deque, testSize)), - M.equals(T.bool(true)) - ), - test( - "empty after front removal", - Queue.isEmpty(reduceBack(deque, testSize)), - M.equals(T.bool(true)) - ) - ] - ) -); + } + ); + + test( + "peek front", + func() { + expect.option(Queue.peekFront(queue), Nat.toText, Nat.equal).equal(?1) + } + ); + + test( + "peek back", + func() { + expect.option(Queue.peekBack(queue), Nat.toText, Nat.equal).equal(?testSize) + } + ); + + test( + "pop front", + func() { + expect.option( + Queue.popFront(queue), + frontToText, + frontEqual + ).equal(?(1, populateBackward(2, testSize + 1))) + } + ); + + test( + "pop back", + func() { + expect.option( + Queue.popBack(queue), + backToText, + backEqual + ).equal(?(populateBackward(1, testSize), testSize)) + } + ); -/* --------------------------------------- */ + test( + "empty after front removal", + func() { + expect.bool(Queue.isEmpty(reduceFront(queue, testSize))).isTrue() + } + ); + + test( + "empty after back removal", + func() { + expect.bool(Queue.isEmpty(reduceBack(queue, testSize))).isTrue() + } + ) + } +); object Random { var number = 4711; @@ -389,14 +443,14 @@ func randomPopulate(amount : Nat) : Queue.Queue { current }; -func isSorted(deque : Queue.Queue) : Bool { - let array = Iter.toArray(iterateForward(deque)); +func isSorted(queue : Queue.Queue) : Bool { + let array = Iter.toArray(iterateForward(queue)); let sorted = Array.sort(array, Nat.compare); Array.equal(array, sorted, Nat.equal) }; -func randomRemoval(deque : Queue.Queue, amount : Nat) : Queue.Queue { - var current = deque; +func randomRemoval(queue : Queue.Queue, amount : Nat) : Queue.Queue { + var current = queue; for (number in Nat.range(0, amount)) { current := if (Random.next() % 2 == 0) { let pair = Queue.popFront(current); @@ -415,57 +469,70 @@ func randomRemoval(deque : Queue.Queue, amount : Nat) : Queue.Queue { current }; -deque := randomPopulate(testSize); - -run( - suite( - "random insertion", - [ - test( - "not empty", - Queue.isEmpty(deque), - M.equals(T.bool(false)) - ), - test( - "correct order", - isSorted(deque), - M.equals(T.bool(true)) - ), - test( - "consistent iteration", - Iter.toArray(iterateForward(deque)), - M.equals(T.array(T.natTestable, Array.reverse(Iter.toArray(iterateBackward(deque))))) - ), - test( - "random quarter removal", - isSorted(randomRemoval(deque, testSize / 4)), - M.equals(T.bool(true)) - ), - test( - "random half removal", - isSorted(randomRemoval(deque, testSize / 2)), - M.equals(T.bool(true)) - ), - test( - "random three quarter removal", - isSorted(randomRemoval(deque, testSize * 3 / 4)), - M.equals(T.bool(true)) - ), - test( - "random total removal", - Queue.isEmpty(randomRemoval(deque, testSize)), - M.equals(T.bool(true)) - ) - ] - ) -); +queue := randomPopulate(testSize); + +suite( + "random insertion", + func() { + test( + "not empty", + func() { + expect.bool(Queue.isEmpty(queue)).isFalse() + } + ); + + test( + "correct order", + func() { + expect.bool(isSorted(queue)).isTrue() + } + ); + + test( + "consistent iteration", + func() { + expect.array( + Iter.toArray(iterateForward(queue)), + Nat.toText, + Nat.equal + ).equal(Array.reverse(Iter.toArray(iterateBackward(queue)))) + } + ); + + test( + "random quarter removal", + func() { + expect.bool(isSorted(randomRemoval(queue, testSize / 4))).isTrue() + } + ); + + test( + "random half removal", + func() { + expect.bool(isSorted(randomRemoval(queue, testSize / 2))).isTrue() + } + ); + + test( + "random three quarter removal", + func() { + expect.bool(isSorted(randomRemoval(queue, testSize * 3 / 4))).isTrue() + } + ); -/* --------------------------------------- */ + test( + "random total removal", + func() { + expect.bool(Queue.isEmpty(randomRemoval(queue, testSize))).isTrue() + } + ) + } +); func randomInsertionDeletion(steps : Nat) : Queue.Queue { var current = Queue.empty(); var size = 0; - for (number in Nat.range(1, steps)) { + for (number in Nat.range(0, steps - 1)) { let random = Random.next(); current := switch (random % 4) { case 0 { @@ -507,15 +574,14 @@ func randomInsertionDeletion(steps : Nat) : Queue.Queue { current }; -run( - suite( - "completely random", - [ - test( - "random insertion and deletion", - isSorted(randomInsertionDeletion(1000)), - M.equals(T.bool(true)) - ) - ] - ) +suite( + "completely random", + func() { + test( + "random insertion and deletion", + func() { + expect.bool(isSorted(randomInsertionDeletion(1000))).isTrue() + } + ) + } ) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 47f8f7fa..17809ce8 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -1297,7 +1297,7 @@ "public func compare(queue1 : Queue, queue2 : Queue, compare : (T, T) -> Order.Order) : Order.Order", "public func contains(queue : Queue, equal : (T, T) -> Bool, item : T) : Bool", "public func empty() : Queue", - "public func equal(queue1 : Queue, queue2 : Queue) : Bool", + "public func equal(queue1 : Queue, queue2 : Queue, equal : (T, T) -> Bool) : Bool", "public func filter(queue : Queue, f : T -> Bool) : Queue", "public func filterMap(queue : Queue, f : T -> ?U) : Queue", "public func forEach(queue : Queue, f : T -> ())", From 12e5947dbea3515f716559cd87fdb2d787bdd07e Mon Sep 17 00:00:00 2001 From: rvanasa Date: Wed, 26 Feb 2025 12:59:23 -0700 Subject: [PATCH 38/40] Add tests for Queue.toPure() and Queue.fromPure() --- src/Queue.mo | 4 +-- test/Queue.test.mo | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/Queue.mo b/src/Queue.mo index 7571180a..040351e7 100644 --- a/src/Queue.mo +++ b/src/Queue.mo @@ -58,7 +58,7 @@ module { /// /// persistent actor { /// let queue = Queue.fromIter([1, 2, 3].values()); - /// let pureQueue = Queue.toPure(queue); + /// let pureQueue = Queue.toPure(queue); /// } /// ``` /// @@ -86,7 +86,7 @@ module { /// /// persistent actor { /// let pureQueue = PureQueue.fromIter([1, 2, 3].values()); - /// let queue = Queue.fromPure(pureQueue); + /// let queue = Queue.fromPure(pureQueue); /// } /// ``` /// diff --git a/test/Queue.test.mo b/test/Queue.test.mo index a28893d8..3a26c690 100644 --- a/test/Queue.test.mo +++ b/test/Queue.test.mo @@ -2,6 +2,7 @@ import Suite "mo:matchers/Suite"; import T "mo:matchers/Testable"; import M "mo:matchers/Matchers"; import Queue "../src/Queue"; +import PureQueue "../src/pure/Queue"; import Iter "../src/Iter"; import Nat "../src/Nat"; import Runtime "../src/Runtime"; @@ -388,6 +389,94 @@ run( ) ); +run( + suite( + "pure queue conversions", + [ + test( + "empty to pure", + do { + let queue = Queue.empty(); + let pureQueue = Queue.toPure(queue); + PureQueue.isEmpty(pureQueue) + }, + M.equals(T.bool(true)) + ), + test( + "empty from pure", + do { + let pureQueue = PureQueue.empty(); + let queue = Queue.fromPure(pureQueue); + Queue.isEmpty(queue) + }, + M.equals(T.bool(true)) + ), + test( + "singleton to pure", + do { + let queue = Queue.singleton(1); + let pureQueue = Queue.toPure(queue); + Iter.toArray(PureQueue.values(pureQueue)) + }, + M.equals(T.array(T.natTestable, [1])) + ), + test( + "singleton from pure", + do { + let pureQueue = PureQueue.pushBack(PureQueue.empty(), 1); + let queue = Queue.fromPure(pureQueue); + Iter.toArray(Queue.values(queue)) + }, + M.equals(T.array(T.natTestable, [1])) + ), + test( + "multiple elements to pure", + do { + let queue = Queue.fromIter([1, 2, 3].vals()); + let pureQueue = Queue.toPure(queue); + Iter.toArray(PureQueue.values(pureQueue)) + }, + M.equals(T.array(T.natTestable, [1, 2, 3])) + ), + test( + "multiple elements from pure", + do { + var pureQueue = PureQueue.empty(); + pureQueue := PureQueue.pushBack(pureQueue, 1); + pureQueue := PureQueue.pushBack(pureQueue, 2); + pureQueue := PureQueue.pushBack(pureQueue, 3); + let queue = Queue.fromPure(pureQueue); + Iter.toArray(Queue.values(queue)) + }, + M.equals(T.array(T.natTestable, [1, 2, 3])) + ), + test( + "round trip mutable to pure to mutable", + do { + let original = Queue.fromIter([1, 2, 3].vals()); + let pureQueue = Queue.toPure(original); + let roundTrip = Queue.fromPure(pureQueue); + Iter.toArray(Queue.values(roundTrip)) + }, + M.equals(T.array(T.natTestable, [1, 2, 3])) + ), + test( + "round trip pure to mutable to pure", + do { + var original = PureQueue.empty(); + original := PureQueue.pushBack(original, 1); + original := PureQueue.pushBack(original, 2); + original := PureQueue.pushBack(original, 3); + let mutableQueue = Queue.fromPure(original); + let roundTrip = Queue.toPure(mutableQueue); + Iter.toArray(PureQueue.values(roundTrip)) + }, + M.equals(T.array(T.natTestable, [1, 2, 3])) + ) + ] + ) +); + // TODO: Use PRNG in new base library class Random(seed : Nat) { var number = seed; From 05b7ab4aa10a6bcee6b45dfa7812e3999d30a0e0 Mon Sep 17 00:00:00 2001 From: rvanasa Date: Wed, 26 Feb 2025 13:03:37 -0700 Subject: [PATCH 39/40] Rename local variable --- src/Queue.mo | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Queue.mo b/src/Queue.mo index 040351e7..38c349b3 100644 --- a/src/Queue.mo +++ b/src/Queue.mo @@ -93,13 +93,13 @@ module { /// Runtime: O(n) /// Space: O(n) /// `n` denotes the number of elements stored in the queue. - public func fromPure(queue : PureQueue.Queue) : Queue { - let mutableQueue = empty(); - let iter = PureQueue.values(queue); + public func fromPure(pureQueue : PureQueue.Queue) : Queue { + let queue = empty(); + let iter = PureQueue.values(pureQueue); loop { switch(iter.next()) { - case null { return mutableQueue }; - case (?val) { pushBack(mutableQueue, val) }; + case null { return queue }; + case (?val) { pushBack(queue, val) }; }; }; }; From a9d33679d78ad09136314b74ab8162ed181e4c7d Mon Sep 17 00:00:00 2001 From: rvanasa Date: Wed, 26 Feb 2025 13:03:56 -0700 Subject: [PATCH 40/40] Update API lockfile --- validation/api/api.lock.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/validation/api/api.lock.json b/validation/api/api.lock.json index 17809ce8..e373b171 100644 --- a/validation/api/api.lock.json +++ b/validation/api/api.lock.json @@ -875,6 +875,7 @@ "public func filterMap(queue : Queue, project : T -> ?U) : Queue", "public func forEach(queue : Queue, operation : T -> ())", "public func fromIter(iter : Iter.Iter) : Queue", + "public func fromPure(pureQueue : PureQueue.Queue) : Queue", "public func isEmpty(queue : Queue) : Bool", "public func map(queue : Queue, project : T -> U) : Queue", "public func peekBack(queue : Queue) : ?T", @@ -886,6 +887,7 @@ "public type Queue", "public func singleton(element : T) : Queue", "public func size(queue : Queue) : Nat", + "public func toPure(queue : Queue) : PureQueue.Queue", "public func toText(queue : Queue, format : T -> Text) : Text", "public func values(queue : Queue) : Iter.Iter" ]