From cf0d469e53463380854bad2565311d95f0da4f17 Mon Sep 17 00:00:00 2001 From: sardariuss Date: Fri, 16 Jun 2023 11:12:58 -0400 Subject: [PATCH 1/2] Add a new function scanLimitWithFilter to return the results only if the predicate is true. --- src/StableRBTree.mo | 39 ++++++++++++++++++++----- test/StableRBTreeTest.mo | 61 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/StableRBTree.mo b/src/StableRBTree.mo index 8c2cf22..eefc6d4 100644 --- a/src/StableRBTree.mo +++ b/src/StableRBTree.mo @@ -20,6 +20,9 @@ module { #leaf; }; + /// Predicate to filter scan limit results. + public type Predicate = (K, V) -> Bool; + /// Initializes an empty Red-Black Tree of type /// Returns this empty Red-Black Tree public func init(): Tree { @@ -238,7 +241,27 @@ module { } }; case (#less) { - let (results, nextKey) = iterScanLimit(t, compareTo, lowerBound, upperBound, dir, limit); + let (results, nextKey) = iterScanLimit(t, compareTo, lowerBound, upperBound, dir, limit, null); + { results = results; nextKey = nextKey }; + } + } + }; + + /// Performs a in-order scan of the Red-Black Tree between the provided key bounds, returning a number of matching entries in the direction specified (forwards/backwards) limited by the limit parameter specified in an array formatted as (K, V) for each entry + public func scanLimitWithFilter(t: Tree, compareTo: (K, K) -> O.Order, lowerBound: K, upperBound: K, dir: Direction, limit: Nat, filter: Predicate): ScanLimitResult { + switch(compareTo(lowerBound, upperBound)) { + // return empty array if lower bound is greater than upper bound + // TODO: consider returning an error in this case? + case (#greater) {{ results = []; nextKey = null }}; + // return the single entry if exists if the lower and upper bounds are equivalent + case (#equal) { + switch(get(t, compareTo, lowerBound)) { + case null {{ results = []; nextKey = null }}; + case (?value) {{ results = [(lowerBound, value)]; nextKey = null }}; + } + }; + case (#less) { + let (results, nextKey) = iterScanLimit(t, compareTo, lowerBound, upperBound, dir, limit, ?filter); { results = results; nextKey = nextKey }; } } @@ -246,7 +269,7 @@ module { type RBTreeNode = { #node: (Color, Tree, (K, ?V), Tree) }; - func iterScanLimit(t: Tree, compareTo: (K, K) -> O.Order, lowerBound: K, upperBound: K, dir: Direction, limit: Nat): ([(K, V)], ?K) { + func iterScanLimit(t: Tree, compareTo: (K, K) -> O.Order, lowerBound: K, upperBound: K, dir: Direction, limit: Nat, filter: ?Predicate): ([(K, V)], ?K) { var remaining = limit + 1; let resultBuffer: Buffer.Buffer<(K, V)> = Buffer.Buffer(0); var nextKey: ?K = null; @@ -335,12 +358,14 @@ module { case null {}; // if the popped node's value is present, prepend it to the entries list and traverse to the right child case (?value) { - if (remaining == 1) { - nextKey := ?k; - } else { - resultBuffer.add((k, value)); + if (Option.getMapped(filter, func(f: Predicate) : Bool { f(k, value); }, true)){ + if (remaining == 1) { + nextKey := ?k; + } else { + resultBuffer.add((k, value)); + }; + remaining -= 1; }; - remaining -= 1; } }; // traverse to the left or right child depending on the direction order diff --git a/test/StableRBTreeTest.mo b/test/StableRBTreeTest.mo index a62ddf5..d6fd868 100644 --- a/test/StableRBTreeTest.mo +++ b/test/StableRBTreeTest.mo @@ -1016,6 +1016,67 @@ let scanLimitInReverseSuite = suite("scanLimit in the #bwd direction", nextKey = ?"o"; }, Text.equal, Nat.equal)) ), + test("if the scan limit filter the results but not the upper bound nor the limit is reached, returns the expected result and the appropriate nextKey", + RBT.scanLimitWithFilter( + createTextNatRBTreeWithKeys(["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]), + Text.compare, + "c", + "z", + #fwd, + 24, + func(k: Text, v: Nat) : Bool { Text.less(k, "f") or Text.greater(k, "w"); } + ), + M.equals(testableRBTreeScanLimitResult({ + results = [ + ("c", 5), + ("d", 5), + ("e", 5), + ("x", 5), + ("y", 5), + ("z", 5), + ]; + nextKey = null; + }, Text.equal, Nat.equal)) + ), + test("if the scan limit filter the results and the limit is reached, returns the expected result and the appropriate nextKey", + RBT.scanLimitWithFilter( + createTextNatRBTreeWithKeys(["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]), + Text.compare, + "c", + "z", + #fwd, + 3, + func(k: Text, v: Nat) : Bool { Text.less(k, "f") or Text.greater(k, "w"); } + ), + M.equals(testableRBTreeScanLimitResult({ + results = [ + ("c", 5), + ("d", 5), + ("e", 5), + ]; + nextKey = ?"x"; + }, Text.equal, Nat.equal)) + ), + test("if the scan limit filter the results and the upper bound is reached, returns the expected result and the appropriate nextKey", + RBT.scanLimitWithFilter( + createTextNatRBTreeWithKeys(["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]), + Text.compare, + "c", + "x", + #fwd, + 24, + func(k: Text, v: Nat) : Bool { Text.less(k, "f") or Text.greater(k, "w"); } + ), + M.equals(testableRBTreeScanLimitResult({ + results = [ + ("c", 5), + ("d", 5), + ("e", 5), + ("x", 5), + ]; + nextKey = null; + }, Text.equal, Nat.equal)) + ), ] ); From 44211494bdb8d211528d71b24cc5e3df9ea59745 Mon Sep 17 00:00:00 2001 From: sardariuss Date: Tue, 15 Aug 2023 12:21:15 -0400 Subject: [PATCH 2/2] Fix bug: when there was only one result in the scan but that should be filtered out, it wasn't filtered out --- src/StableRBTree.mo | 27 +++++++++------------------ test/StableRBTreeTest.mo | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/StableRBTree.mo b/src/StableRBTree.mo index eefc6d4..6357ff9 100644 --- a/src/StableRBTree.mo +++ b/src/StableRBTree.mo @@ -229,22 +229,7 @@ module { /// Performs a in-order scan of the Red-Black Tree between the provided key bounds, returning a number of matching entries in the direction specified (forwards/backwards) limited by the limit parameter specified in an array formatted as (K, V) for each entry public func scanLimit(t: Tree, compareTo: (K, K) -> O.Order, lowerBound: K, upperBound: K, dir: Direction, limit: Nat): ScanLimitResult { - switch(compareTo(lowerBound, upperBound)) { - // return empty array if lower bound is greater than upper bound - // TODO: consider returning an error in this case? - case (#greater) {{ results = []; nextKey = null }}; - // return the single entry if exists if the lower and upper bounds are equivalent - case (#equal) { - switch(get(t, compareTo, lowerBound)) { - case null {{ results = []; nextKey = null }}; - case (?value) {{ results = [(lowerBound, value)]; nextKey = null }}; - } - }; - case (#less) { - let (results, nextKey) = iterScanLimit(t, compareTo, lowerBound, upperBound, dir, limit, null); - { results = results; nextKey = nextKey }; - } - } + scanLimitWithFilter(t, compareTo, lowerBound, upperBound, dir, limit, func(k: K, v: V) : Bool = true); }; /// Performs a in-order scan of the Red-Black Tree between the provided key bounds, returning a number of matching entries in the direction specified (forwards/backwards) limited by the limit parameter specified in an array formatted as (K, V) for each entry @@ -257,7 +242,13 @@ module { case (#equal) { switch(get(t, compareTo, lowerBound)) { case null {{ results = []; nextKey = null }}; - case (?value) {{ results = [(lowerBound, value)]; nextKey = null }}; + case (?value) { + if (filter(lowerBound, value)){ + { results = [(lowerBound, value)]; nextKey = null } + } else { + { results = []; nextKey = null } + }; + }; } }; case (#less) { @@ -358,7 +349,7 @@ module { case null {}; // if the popped node's value is present, prepend it to the entries list and traverse to the right child case (?value) { - if (Option.getMapped(filter, func(f: Predicate) : Bool { f(k, value); }, true)){ + if (Option.getMapped(filter, func(f: Predicate) : Bool = f(k, value), true)){ if (remaining == 1) { nextKey := ?k; } else { diff --git a/test/StableRBTreeTest.mo b/test/StableRBTreeTest.mo index d6fd868..3438c43 100644 --- a/test/StableRBTreeTest.mo +++ b/test/StableRBTreeTest.mo @@ -1077,6 +1077,21 @@ let scanLimitInReverseSuite = suite("scanLimit in the #bwd direction", nextKey = null; }, Text.equal, Nat.equal)) ), + test("if there is only one result and it is filtered out, returns an empty list and null nextKey", + RBT.scanLimitWithFilter( + createTextNatRBTreeWithKeys(["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]), + Text.compare, + "c", + "c", + #fwd, + 24, + func(k: Text, v: Nat) : Bool { false; } + ), + M.equals(testableRBTreeScanLimitResult({ + results = []; + nextKey = null; + }, Text.equal, Nat.equal)) + ), ] );