Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BTrees in Motoko. #396

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
28 changes: 26 additions & 2 deletions src/BTree.mo
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import A "Array";
import I "Iter";
import List "List";
import Text "Text";
import Option "Option";
import Order "Order";
import P "Prelude";
Expand Down Expand Up @@ -45,7 +46,7 @@ module {
switch t {
case (#leaf(d)) { return find_data<K, V>(d, k, c) };
case (#internal(i)) {
for (j in I.range(0, i.data.size())) {
for (j in I.range(0, i.data.size() - 1)) {
switch (c(k, i.data[j].0)) {
case (#equal) { return ?i.data[j].1 };
case (#less) { return find<K, V>(i.trees[j], k, c) };
Expand All @@ -57,14 +58,37 @@ module {
};
};

public module Insert {



};

// Assert that the given B-Tree instance observes all relevant invariants.
// Used for unit tests. Show function helps debug failing tests.
//
// Note: These checks-as-assertions can be refactored into value-producing checks,
// if that seems useful. Then, they can be individual matchers tests. Again, if useful.
public func assertIsValid<K, V>(
t : Tree<K, V>,
compare : (K, K) -> Order.Order,
show : K -> Text)
{
Check.root<K, V>({compare; show}, t)
};

public func assertIsValidTextKeys<V>(t : Tree<Text, V>){
Check.root<Text, V>({compare=Text.compare; show=func (t:Text) : Text { t }}, t)
};
Comment on lines +72 to +82
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it help if Check and these test methods were just moved into test/BTreeTest.mo?


/// Check that a B-Tree instance observes invariants of B-Trees.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this module is just for testing/debugging, should Check be in a different file?

When I import the default BTree module like import BTree "mo:base/BTree";, does it shake out the Check, or does it include it and bloat the import size?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it shake out the Check, or does it include it and bloat the import size?

This module is static. The compiler will not compile static code that you import but do not use. (However, class instances will always contain all methods, regardless of usage. Not applicable here.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this module is just for testing/debugging, should Check be in a different file?

Why? It's more enclosed and encapsulated here. There is no way to "hide" a top-level module in base.

/// Invariants ensure performance is what we expect.
/// For testing and debugging.
///
/// Future refactoring --- Eventually, we can return Result or
/// Option so that both valid and invalid inputs can be inspected in
/// test cases. Doing assertions directly here is easier, for now.
public module Check {
module Check {

type Inf<K> = {#infmax; #infmin; #finite : K};

Expand Down
68 changes: 44 additions & 24 deletions test/BTreeTest.mo
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,62 @@ import T "mo:matchers/Testable";

Debug.print "BTree tests: Begin.";

func check(t : BT.Tree<Text, Nat>){ BT.Check.root<Text, Nat>({compare=Text.compare; show=func (t:Text) : Text { t }}, t) };

Debug.print "empty1.";
let empty1 = #internal({data=[]; trees=[]});
check(empty1);

Debug.print "empty2.";
let empty2 = #leaf([]);
check(empty2);

Debug.print "leaf of one.";
let leaf_of_one = #leaf([("oak", 1)]);
check(leaf_of_one);

Debug.print "leaf of two.";
let leaf_of_two = #leaf([("ash", 1), ("oak", 2)]);
check(leaf_of_two);

Debug.print "leaf of three. A-C";
let leaf_of_three_a_c = #leaf([("apple", 1), ("ash", 4), ("crab apple", 3)]);
check(leaf_of_three_a_c);
let leaf_of_three_s_w = #leaf([("salix", 11), ("sallows", 44), ("willow", 33)]);
let binary_internal = #internal(
{
data=[("pine", 42)];
trees=[leaf_of_three_a_c, leaf_of_three_s_w]
});

let _ = Suite.suite(
"constructions and checks.",
[ // These checks-as-assertions can be refactored into value-producing checks,
// if that seems useful. Then, they can be individual matchers tests. Again, if useful.
Suite.test("assertions.", try {
Debug.print "empty1.";
BT.assertIsValidTextKeys(empty1);

Debug.print "leaf of three. S-W";
let leaf_of_three_s_w = #leaf([("salix", 1), ("sallows", 4), ("willow", 3)]);
check(leaf_of_three_s_w);
Debug.print "empty2.";
BT.assertIsValidTextKeys(empty2);

Debug.print "binary internal.";
let binary_internal = #internal({data=[("pine", 42)]; trees=[leaf_of_three_a_c, leaf_of_three_s_w]});
check(binary_internal);
Debug.print "leaf of one.";
BT.assertIsValidTextKeys(leaf_of_one);

Debug.print "leaf of two.";
BT.assertIsValidTextKeys(leaf_of_two);

Debug.print "leaf of three. A-C";
BT.assertIsValidTextKeys(leaf_of_three_a_c);

Debug.print "leaf of three. S-W";
BT.assertIsValidTextKeys(leaf_of_three_s_w);

Debug.print "binary internal.";
BT.assertIsValidTextKeys(binary_internal);

true
} catch _ { false },
M.equals(T.bool(true))
)]);

let _ = Suite.suite("find", [
Suite.test("pine",
BT.find<Text, Nat>(binary_internal, "pine", Text.compare),
M.equals(T.optional<Nat>(T.natTestable, ?42))
)
),
Suite.test("apple",
BT.find<Text, Nat>(binary_internal, "apple", Text.compare),
M.equals(T.optional<Nat>(T.natTestable, ?1))
),
Suite.test("willow",
BT.find<Text, Nat>(binary_internal, "willow", Text.compare),
M.equals(T.optional<Nat>(T.natTestable, ?33))
),
]);


Debug.print "BTree tests: End.";