Skip to content

Commit

Permalink
Extract BTree types into Types.mo to avoid cyclic dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
ByronBecker committed Nov 20, 2022
1 parent 108885b commit a860de9
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 71 deletions.
62 changes: 6 additions & 56 deletions src/BTree.mo
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// The BTree module collection of functions and types

import Types "./Types";
import BS "./BinarySearch";
import AU "./ArrayUtil";

Expand All @@ -13,30 +14,11 @@ import Option "mo:base/Option";


module {
public type Node<K, V> = {
#leaf: Leaf<K, V>;
#internal: Internal<K, V>;
};

public type Data<K, V> = {
kvs: [var ?(K, V)];
var count: Nat;
};

public type Internal<K, V> = {
data: Data<K, V>;
children: [var ?Node<K, V>]
};

public type Leaf<K, V> = {
data: Data<K, V>;
};

public type BTree<K, V> = {
var root: Node<K, V>;
order: Nat;
};

public type BTree<K, V> = Types.BTree<K, V>;
public type Node<K, V> = Types.Node<K, V>;
public type Internal<K, V> = Types.Internal<K, V>;
public type Leaf<K, V> = Types.Leaf<K, V>;
public type Data<K, V> = Types.Data<K, V>;

// TODO - enforce BTrees to have order of at least 4
public func init<K, V>(order: Nat): BTree<K, V> = {
Expand Down Expand Up @@ -132,38 +114,6 @@ module {
};


// get helper if internal node
func getFromInternal<K, V>(internalNode: Internal<K, V>, compare: (K, K) -> O.Order, key: K): ?V {
switch(getKeyIndex<K, V>(internalNode.data, compare, key)) {
case (#keyFound(index)) { getExistingValueFromIndex(internalNode.data, index) };
case (#notFound(index)) {
switch(internalNode.children[index]) {
// expects the child to be there, otherwise there's a bug in binary search or the tree is invalid
case null { assert false; null };
case (?#leaf(leafNode)) { getFromLeaf(leafNode, compare, key)};
case (?#internal(internalNode)) { getFromInternal(internalNode, compare, key)}
}
}
}
};

// get function helper if leaf node
func getFromLeaf<K, V>(leafNode: Leaf<K, V>, compare: (K, K) -> O.Order, key: K): ?V {
switch(getKeyIndex<K, V>(leafNode.data, compare, key)) {
case (#keyFound(index)) { getExistingValueFromIndex(leafNode.data, index) };
case _ null;
}
};

// get function helper that retrieves an existing value in the case that the key is found
func getExistingValueFromIndex<K, V>(data: Data<K, V>, index: Nat): ?V {
switch(data.kvs[index]) {
case null { null };
case (?ov) { ?ov.1 }
}
};


// This type is used to signal to the parent calling context what happened in the level below
type IntermediateInsertResult<K, V> = {
// element was inserted or replaced, returning the old value (?value or null)
Expand Down
16 changes: 8 additions & 8 deletions src/Check.mo
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import O "mo:base/Order";
import Option "mo:base/Option";
import Result "mo:base/Result";

import BT "./BTree";
import Types "./Types";


module {
/// Checks a BTree for validity, checking for both key ordering and node height/depth equivalence
public func check<K, V>(t: BT.BTree<K, V>, compare: (K, K) -> O.Order): Bool {
public func check<K, V>(t: Types.BTree<K, V>, compare: (K, K) -> O.Order): Bool {
switch(checkTreeDepthIsValid(t)) {
case (#err) { return false };
case _ {}
Expand All @@ -28,11 +28,11 @@ module {
};

// Ensures that the Btree is balanced and all sibling/cousin nodes (at same level) have the same height
public func checkTreeDepthIsValid<K, V>(t: BT.BTree<K, V>): CheckDepthResult {
public func checkTreeDepthIsValid<K, V>(t: Types.BTree<K, V>): CheckDepthResult {
depthCheckerHelper(t.root)
};

func depthCheckerHelper<K, V>(node: BT.Node<K, V>): CheckDepthResult {
func depthCheckerHelper<K, V>(node: Types.Node<K, V>): CheckDepthResult {
switch(node) {
case (#leaf(_)) { #ok(1) };
case (#internal(internalNode)) {
Expand Down Expand Up @@ -75,7 +75,7 @@ module {
};

/// Ensures the ordering of all elements in the BTree is valid
public func checkDataOrderIsValid<K, V>(t : BT.BTree<K, V>, compare: (K, K) -> O.Order): CheckOrderResult {
public func checkDataOrderIsValid<K, V>(t : Types.BTree<K, V>, compare: (K, K) -> O.Order): CheckOrderResult {
// allow for empty root (valid)
switch(t.root) {
case (#leaf(leafNode)) {
Expand All @@ -89,14 +89,14 @@ module {
rec(t.root, t.order, infCompare(compare), #infmin, #infmax)
};

func rec<K, V>(node : BT.Node<K, V>, order: Nat, compare : InfCompare<K>, lower : Inf<K>, upper : Inf<K>): CheckOrderResult {
func rec<K, V>(node : Types.Node<K, V>, order: Nat, compare : InfCompare<K>, lower : Inf<K>, upper : Inf<K>): CheckOrderResult {
switch (node) {
case (#leaf(leafNode)) { checkData(leafNode.data, order, compare, lower, upper) };
case (#internal(internalNode)) { checkInternal(internalNode, order, compare, lower, upper) };
}
};

func checkData<K, V>(data : BT.Data<K, V>, order: Nat, compare : InfCompare<K>, lower : Inf<K>, upper : Inf<K>): CheckOrderResult {
func checkData<K, V>(data : Types.Data<K, V>, order: Nat, compare : InfCompare<K>, lower : Inf<K>, upper : Inf<K>): CheckOrderResult {
let expectedMaxKeys: Nat = order - 1;
if (data.kvs.size() != expectedMaxKeys) { return #err };

Expand Down Expand Up @@ -126,7 +126,7 @@ module {
#ok
};

func checkInternal<K, V>(internal : BT.Internal<K, V>, order: Nat, compare: InfCompare<K>, lower : Inf<K>, upper : Inf<K>): CheckOrderResult {
func checkInternal<K, V>(internal : Types.Internal<K, V>, order: Nat, compare: InfCompare<K>, lower : Inf<K>, upper : Inf<K>): CheckOrderResult {
if (
internal.children.size() != order
or
Expand Down
26 changes: 26 additions & 0 deletions src/Types.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

module {
public type Node<K, V> = {
#leaf: Leaf<K, V>;
#internal: Internal<K, V>;
};

public type Data<K, V> = {
kvs: [var ?(K, V)];
var count: Nat;
};

public type Internal<K, V> = {
data: Data<K, V>;
children: [var ?Node<K, V>]
};

public type Leaf<K, V> = {
data: Data<K, V>;
};

public type BTree<K, V> = {
var root: Node<K, V>;
order: Nat;
};
}
15 changes: 8 additions & 7 deletions test/CheckTest.mo
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Nat "mo:base/Nat";

import Check "../src/Check";
import BT "../src/BTree";
import Types "../src/Types";

let orderResultTestableItem = func(result: Check.CheckOrderResult): T.TestableItem<Check.CheckOrderResult> = {
display = func(r: Check.CheckOrderResult): Text = switch(r) {
Expand Down Expand Up @@ -97,7 +98,7 @@ let checkTreeDepthIsValidSuite = S.suite("checkTreeDepthIsValid", [
]),
S.test("uneven & invalid BTree depth",
do {
let t: BT.BTree<Nat, Nat> = {
let t: Types.BTree<Nat, Nat> = {
var root = #internal({
data = {
kvs = [var ?(6, 6), ?(15, 15), null];
Expand Down Expand Up @@ -207,7 +208,7 @@ let checkDataOrderIsValidSuite = S.suite("checkDataDepthIsValid", [
S.suite("are not valid btrees", [
S.test("if have invalid nested leaf data order",
do {
let t: BT.BTree<Nat, Nat> = {
let t: Types.BTree<Nat, Nat> = {
var root = #internal({
data = {
kvs = [var ?(6, 6), null, null];
Expand Down Expand Up @@ -238,7 +239,7 @@ let checkDataOrderIsValidSuite = S.suite("checkDataDepthIsValid", [
),
S.test("if have invalid internal node data order",
do {
let t: BT.BTree<Nat, Nat> = {
let t: Types.BTree<Nat, Nat> = {
var root = #internal({
data = {
kvs = [var ?(6, 6), ?(0, 0), null];
Expand Down Expand Up @@ -269,7 +270,7 @@ let checkDataOrderIsValidSuite = S.suite("checkDataDepthIsValid", [
),
S.test("if have a null before a non-null key-value pair in a leaf",
do {
let t: BT.BTree<Nat, Nat> = {
let t: Types.BTree<Nat, Nat> = {
var root = #internal({
data = {
kvs = [var ?(6, 6), null, null];
Expand Down Expand Up @@ -300,7 +301,7 @@ let checkDataOrderIsValidSuite = S.suite("checkDataDepthIsValid", [
),
S.test("if have a null before a non-null key-value pair in an internal",
do {
let t: BT.BTree<Nat, Nat> = {
let t: Types.BTree<Nat, Nat> = {
var root = #internal({
data = {
kvs = [var null, ?(6, 6), null];
Expand Down Expand Up @@ -331,7 +332,7 @@ let checkDataOrderIsValidSuite = S.suite("checkDataDepthIsValid", [
),
S.test("if invalid number of children",
do {
let t: BT.BTree<Nat, Nat> = {
let t: Types.BTree<Nat, Nat> = {
var root = #internal({
data = {
kvs = [var ?(6, 6), ?(0, 0), null];
Expand Down Expand Up @@ -363,7 +364,7 @@ let checkDataOrderIsValidSuite = S.suite("checkDataDepthIsValid", [
),
S.test("if invalid number of keys",
do {
let t: BT.BTree<Nat, Nat> = {
let t: Types.BTree<Nat, Nat> = {
var root = #internal({
data = {
kvs = [var ?(6, 6), ?(0, 0), null];
Expand Down

0 comments on commit a860de9

Please sign in to comment.