Skip to content

Commit

Permalink
PersistentOrderedSet: add min()/max(), all()/some() methods
Browse files Browse the repository at this point in the history
  • Loading branch information
GoPavel committed Nov 5, 2024
1 parent 879519c commit 4dd99ac
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 1 deletion.
148 changes: 147 additions & 1 deletion src/PersistentOrderedSet.mo
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,50 @@ module {
public func contains(s : Set<T>, 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>(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>) : ?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>(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>): ?T
= Internal.min(s.root);

/// [Set union](https://en.wikipedia.org/wiki/Union_(set_theory)) operation.
///
/// Example:
Expand Down Expand Up @@ -296,7 +340,7 @@ module {
public func map<T1>(s : Set<T1>, f : T1 -> T) : Set<T>
= Internal.foldLeft(s.root, empty(), func (elem : T1, acc : Set<T>) : Set<T> { 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`.
Expand Down Expand Up @@ -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>(Nat.compare);
/// let set = natSet.fromIter<Text>(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<T>, 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>(Nat.compare);
/// let set = setOps.fromIter<Text>(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<T>, pred: (T) -> Bool): Bool
= Internal.some(s.root, pred);
};

module Internal {
Expand All @@ -603,6 +693,62 @@ module {
f (s.root, elem);
};

public func max<V>(m: Tree<V>): ?V {
func rightmost(m: Tree<V>): 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<V>(m: Tree<V>): ?V {
func leftmost(m: Tree<V>): 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<V>(m: Tree<V>, 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<V>(m: Tree<V>, 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<T> = List.List<{ #tr : Tree<T>; #x : T }>;

type SetTraverser<T> = (Tree<T>, T, Tree<T>, IterRep<T>) -> IterRep<T>;
Expand Down
22 changes: 22 additions & 0 deletions test/PersistentOrderedSet.prop.test.mo
Original file line number Diff line number Diff line change
Expand Up @@ -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<?Nat>(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<?Nat>(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<Bool>(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<Bool>(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)) {
Expand Down
60 changes: 60 additions & 0 deletions test/PersistentOrderedSet.test.mo
Original file line number Diff line number Diff line change
Expand Up @@ -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))
)
]
)
);
Expand Down Expand Up @@ -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))
),
]
)
);
Expand Down Expand Up @@ -342,6 +372,36 @@ func rebalanceTests(buildTestSet : () -> Set.Set<Nat>) : [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<Nat> {
Expand Down

0 comments on commit 4dd99ac

Please sign in to comment.