From 4dd99aca2d1b3f8d57af90763b54271cae96c8d7 Mon Sep 17 00:00:00 2001 From: Pavel Golovin Date: Tue, 5 Nov 2024 01:47:40 +0100 Subject: [PATCH] PersistentOrderedSet: add min()/max(), all()/some() methods --- src/PersistentOrderedSet.mo | 148 ++++++++++++++++++++++++- test/PersistentOrderedSet.prop.test.mo | 22 ++++ test/PersistentOrderedSet.test.mo | 60 ++++++++++ 3 files changed, 229 insertions(+), 1 deletion(-) diff --git a/src/PersistentOrderedSet.mo b/src/PersistentOrderedSet.mo index 07dd61cf..fc2f4a2c 100644 --- a/src/PersistentOrderedSet.mo +++ b/src/PersistentOrderedSet.mo @@ -157,6 +157,50 @@ module { public func contains(s : Set, value : T) : Bool = Internal.contains(s, compare, value); + /// Get a maximal element of the set `s` if it is not empty, otherwise returns `null` + /// + /// Example: + /// ```motoko + /// import Set "mo:base/PersistentOrderedSet"; + /// import Nat "mo:base/Nat"; + /// import Debug "mo:base/Debug"; + /// + /// let natSet = Set.SetOps(Nat.compare); + /// let s = natSet.fromIter(Iter.fromArray([0, 2, 1])); + /// let s = natSet.empty(); + /// + /// Debug.print(debug_show(natSet.max(s))); // => ?2 + /// Debug.print(debug_show(natSet.max(s2))); // => null + /// ``` + /// + /// Runtime: `O(log(n))`. + /// Space: `O(1)`. + /// where `n` denotes the number of elements in the set + public func max(s: Set) : ?T + = Internal.max(s.root); + + /// Get a minimal element of the set `s` if it is not empty, otherwise returns `null` + /// + /// Example: + /// ```motoko + /// import Set "mo:base/PersistentOrderedSet"; + /// import Nat "mo:base/Nat"; + /// import Debug "mo:base/Debug"; + /// + /// let natSet = Set.SetOps(Nat.compare); + /// let s = natSet.fromIter(Iter.fromArray([0, 2, 1])); + /// let s = natSet.empty(); + /// + /// Debug.print(debug_show(natSet.min(s))); // => ?0 + /// Debug.print(debug_show(natSet.min(s2))); // => null + /// ``` + /// + /// Runtime: `O(log(n))`. + /// Space: `O(1)`. + /// where `n` denotes the number of elements in the set + public func min(s: Set): ?T + = Internal.min(s.root); + /// [Set union](https://en.wikipedia.org/wiki/Union_(set_theory)) operation. /// /// Example: @@ -296,7 +340,7 @@ module { public func map(s : Set, f : T1 -> T) : Set = Internal.foldLeft(s.root, empty(), func (elem : T1, acc : Set) : Set { Internal.put(acc, compare, f(elem)) }); - /// Creates a new map by applying `f` to each element in the set `s`. For each element + /// Creates a new set by applying `f` to each element in the set `s`. For each element /// `x` in the old set, if `f` evaluates to `null`, the element is discarded. /// Otherwise, the entry is transformed into a new entry `x2`, where /// the new value `x2` is the result of applying `f` to `x`. @@ -577,6 +621,52 @@ module { case _ { false }; }; }; + + /// Test whether all values in the set `s` satisfy a given predicate `pred`. + /// + /// Example: + /// ```motoko + /// import Set "mo:base/PersistentOrderedSet"; + /// import Nat "mo:base/Nat"; + /// import Debug "mo:base/Debug"; + /// + /// let natSet = Set.SetOps(Nat.compare); + /// let set = natSet.fromIter(Iter.fromArray([0, 2, 1])); + /// + /// Debug.print(debug_show(natSet.all(set, func (v) = (v < 10)))); + /// // true + /// Debug.print(debug_show(natSet.all(set, func (v) = (v < 2)))); + /// // false + /// ``` + /// + /// Runtime: `O(n)`. + /// Space: `O(1)`. + /// where `n` denotes the number of elements stored in the set. + public func all(s: Set, pred: T -> Bool): Bool + = Internal.all(s.root, pred); + + /// Test if there exists a key-value pair satisfying a given predicate `pred`. + /// + /// Example: + /// ```motoko + /// import Set "mo:base/PersistentOrderedSet"; + /// import Nat "mo:base/Nat"; + /// import Debug "mo:base/Debug"; + /// + /// let setOps = Set.SetOps(Nat.compare); + /// let set = setOps.fromIter(Iter.fromArray([0, 2, 1])); + /// + /// Debug.print(debug_show(setOps.some(set, func (v) = (k >= 3)))); + /// // false + /// Debug.print(debug_show(setOps.some(set, func (v) = (k >= 0)))); + /// // true + /// ``` + /// + /// Runtime: `O(n)`. + /// Space: `O(1)`. + /// where `n` denotes the number of elements stored in the set. + public func some(s: Set, pred: (T) -> Bool): Bool + = Internal.some(s.root, pred); }; module Internal { @@ -603,6 +693,62 @@ module { f (s.root, elem); }; + public func max(m: Tree): ?V { + func rightmost(m: Tree): V { + switch m { + case (#red(_, v, #leaf)) { v }; + case (#red(_, _, r)) { rightmost(r) }; + case (#black(_, v, #leaf)) { v }; + case (#black(_, _, r)) { rightmost(r) }; + case (#leaf) { Debug.trap "PersistentOrderedSet.impossible" } + } + }; + switch m { + case (#leaf) { null }; + case (_) { ?rightmost(m) } + } + }; + + public func min(m: Tree): ?V { + func leftmost(m: Tree): V { + switch m { + case (#red(#leaf, v, _)) { v }; + case (#red(l, _, _)) { leftmost(l) }; + case (#black(#leaf, v, _)) { v }; + case (#black(l, _, _)) { leftmost(l)}; + case (#leaf) { Debug.trap "PersistentOrderedSet.impossible" } + } + }; + switch m { + case (#leaf) { null }; + case (_) { ?leftmost(m) } + } + }; + + public func all(m: Tree, pred: V -> Bool): Bool { + switch m { + case (#red(l, v, r)) { + pred(v) and all(l, pred) and all(r, pred) + }; + case (#black(l, v, r)) { + pred(v) and all(l, pred) and all(r, pred) + }; + case (#leaf) { true } + } + }; + + public func some(m: Tree, pred: V -> Bool): Bool { + switch m { + case (#red(l, v, r)) { + pred(v) or some(l, pred) or some(r, pred) + }; + case (#black(l, v, r)) { + pred(v) or some(l, pred) or some(r, pred) + }; + case (#leaf) { false } + } + }; + type IterRep = List.List<{ #tr : Tree; #x : T }>; type SetTraverser = (Tree, T, Tree, IterRep) -> IterRep; diff --git a/test/PersistentOrderedSet.prop.test.mo b/test/PersistentOrderedSet.prop.test.mo index b3234848..4439c8ca 100644 --- a/test/PersistentOrderedSet.prop.test.mo +++ b/test/PersistentOrderedSet.prop.test.mo @@ -149,6 +149,28 @@ func run_all_props(range: (Nat, Nat), size: Nat, set_samples: Nat, query_samples }), ]), + suite("min/max", [ + prop("max through fold", func (s) { + let expected = natSet.foldLeft(s, null: ?Nat, func (v, _) = ?v ); + M.equals(T.optional(T.natTestable, expected)).matches(natSet.max(s)); + }), + prop("min through fold", func (s) { + let expected = natSet.foldRight(s, null: ?Nat, func (v, _) = ?v ); + M.equals(T.optional(T.natTestable, expected)).matches(natSet.min(s)); + }), + ]), + + suite("all/some", [ + prop("all through fold", func(s) { + let pred = func(k: Nat): Bool = (k <= range.1 - 2 and range.0 + 2 <= k); + natSet.all(s, pred) == natSet.foldLeft(s, true, func (v, acc) {acc and pred(v)}) + }), + prop("some through fold", func(s) { + let pred = func(k: Nat): Bool = (k >= range.1 - 1 or range.0 + 1 >= k); + natSet.some(s, pred) == natSet.foldLeft(s, false, func (v, acc) {acc or pred(v)}) + }), + ]), + suite("delete", [ prop_with_elem("not contains(s, e) ==> delete(s, e) == s", func (s, e) { if (not natSet.contains(s, e)) { diff --git a/test/PersistentOrderedSet.test.mo b/test/PersistentOrderedSet.test.mo index e012c188..f4c49e48 100644 --- a/test/PersistentOrderedSet.test.mo +++ b/test/PersistentOrderedSet.test.mo @@ -177,6 +177,16 @@ run( natSetOps.isEmpty(buildTestSet()), M.equals(T.bool(true)) ), + test( + "max", + natSetOps.max(buildTestSet()), + M.equals(T.optional(entryTestable, null: ?Nat)) + ), + test( + "min", + natSetOps.min(buildTestSet()), + M.equals(T.optional(entryTestable, null: ?Nat)) + ) ] ) ); @@ -253,6 +263,26 @@ run( natSetOps.isEmpty(buildTestSet()), M.equals(T.bool(false)) ), + test( + "max", + natSetOps.max(buildTestSet()), + M.equals(T.optional(entryTestable, ?0)) + ), + test( + "min", + natSetOps.min(buildTestSet()), + M.equals(T.optional(entryTestable, ?0)) + ), + test( + "all", + natSetOps.all(buildTestSet(), func (k) = (k == 0)), + M.equals(T.bool(true)) + ), + test( + "some", + natSetOps.some(buildTestSet(), func (k) = (k == 0)), + M.equals(T.bool(true)) + ), ] ) ); @@ -342,6 +372,36 @@ func rebalanceTests(buildTestSet : () -> Set.Set) : [Suite.Suite] = natSetOps.isEmpty(buildTestSet()), M.equals(T.bool(false)) ), + test( + "max", + natSetOps.max(buildTestSet()), + M.equals(T.optional(entryTestable, ?2)) + ), + test( + "min", + natSetOps.min(buildTestSet()), + M.equals(T.optional(entryTestable, ?0)) + ), + test( + "all true", + natSetOps.all(buildTestSet(), func (k) = (k >= 0)), + M.equals(T.bool(true)) + ), + test( + "all false", + natSetOps.all(buildTestSet(), func (k) = (k > 0)), + M.equals(T.bool(false)) + ), + test( + "some true", + natSetOps.some(buildTestSet(), func (k) = (k >= 2)), + M.equals(T.bool(true)) + ), + test( + "some false", + natSetOps.some(buildTestSet(), func (k) = (k > 2)), + M.equals(T.bool(false)) + ), ]; buildTestSet := func() : Set.Set {