diff --git a/config.json b/config.json index 6e034b6..b8d01ca 100644 --- a/config.json +++ b/config.json @@ -409,6 +409,14 @@ "prerequisites": [], "difficulty": 5 }, + { + "slug": "binary-search-tree", + "name": "Binary Search Tree", + "uuid": "52957e98-b5c5-46c7-a55c-d5e14a12652f", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "book-store", "name": "Book Store", diff --git a/exercises/practice/binary-search-tree/.docs/instructions.md b/exercises/practice/binary-search-tree/.docs/instructions.md new file mode 100644 index 0000000..7625220 --- /dev/null +++ b/exercises/practice/binary-search-tree/.docs/instructions.md @@ -0,0 +1,70 @@ +# Instructions + +Insert and search for numbers in a binary tree. + +When we need to represent sorted data, an array does not make a good data structure. + +Say we have the array `[1, 3, 4, 5]`, and we add 2 to it so it becomes `[1, 3, 4, 5, 2]`. +Now we must sort the entire array again! +We can improve on this by realizing that we only need to make space for the new item `[1, nil, 3, 4, 5]`, and then adding the item in the space we added. +But this still requires us to shift many elements down by one. + +Binary Search Trees, however, can operate on sorted data much more efficiently. + +A binary search tree consists of a series of connected nodes. +Each node contains a piece of data (e.g. the number 3), a variable named `left`, and a variable named `right`. +The `left` and `right` variables point at `nil`, or other nodes. +Since these other nodes in turn have other nodes beneath them, we say that the left and right variables are pointing at subtrees. +All data in the left subtree is less than or equal to the current node's data, and all data in the right subtree is greater than the current node's data. + +For example, if we had a node containing the data 4, and we added the data 2, our tree would look like this: + +![A graph with root node 4 and a single child node 2.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2.svg) + +```text + 4 + / + 2 +``` + +If we then added 6, it would look like this: + +![A graph with root node 4 and two child nodes 2 and 6.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6.svg) + +```text + 4 + / \ + 2 6 +``` + +If we then added 3, it would look like this + +![A graph with root node 4, two child nodes 2 and 6, and a grandchild node 3.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6-3.svg) + +```text + 4 + / \ + 2 6 + \ + 3 +``` + +And if we then added 1, 5, and 7, it would look like this + +![A graph with root node 4, two child nodes 2 and 6, and four grandchild nodes 1, 3, 5 and 7.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6-1-3-5-7.svg) + +```text + 4 + / \ + / \ + 2 6 + / \ / \ + 1 3 5 7 +``` + +## Credit + +The images were created by [habere-et-dispertire][habere-et-dispertire] using [PGF/TikZ][pgf-tikz] by Till Tantau. + +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[pgf-tikz]: https://en.wikipedia.org/wiki/PGF/TikZ diff --git a/exercises/practice/binary-search-tree/.meta/config.json b/exercises/practice/binary-search-tree/.meta/config.json new file mode 100644 index 0000000..f4f27ad --- /dev/null +++ b/exercises/practice/binary-search-tree/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "rmonnet" + ], + "files": { + "solution": [ + "binary_search_tree.odin" + ], + "test": [ + "binary_search_tree_test.odin" + ], + "example": [ + ".meta/example.odin" + ] + }, + "blurb": "Insert and search for numbers in a binary tree.", + "source": "Josh Cheek" +} diff --git a/exercises/practice/binary-search-tree/.meta/example.odin b/exercises/practice/binary-search-tree/.meta/example.odin new file mode 100644 index 0000000..ed3c445 --- /dev/null +++ b/exercises/practice/binary-search-tree/.meta/example.odin @@ -0,0 +1,48 @@ +package binary_search_tree + +Tree :: ^Node + +Node :: struct { + value: int, + left: Tree, + right: Tree, +} + +destroy_tree :: proc(t: Tree) { + if t == nil { return } + destroy_tree(t.left) + destroy_tree(t.right) + free(t) +} + +insert :: proc(t: ^Tree, value: int) { + + if t^ == nil { + t^ = new(Node) + t^.value = value + return + } + + if value <= t^.value { + insert(&t^.left, value) + } else { + insert(&t^.right, value) + } +} + +sorted_data :: proc(t: Tree) -> []int { + + if t == nil { return nil } + + acc: [dynamic]int + collect_data(t, &acc) + return acc[:] +} + +collect_data :: proc(t: Tree, acc: ^[dynamic]int) { + + if t == nil { return } + collect_data(t.left, acc) + append(acc, t.value) + collect_data(t.right, acc) +} diff --git a/exercises/practice/binary-search-tree/.meta/tests.toml b/exercises/practice/binary-search-tree/.meta/tests.toml new file mode 100644 index 0000000..c7d3202 --- /dev/null +++ b/exercises/practice/binary-search-tree/.meta/tests.toml @@ -0,0 +1,40 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[e9c93a78-c536-4750-a336-94583d23fafa] +description = "data is retained" + +[7a95c9e8-69f6-476a-b0c4-4170cb3f7c91] +description = "insert data at proper node -> smaller number at left node" + +[22b89499-9805-4703-a159-1a6e434c1585] +description = "insert data at proper node -> same number at left node" + +[2e85fdde-77b1-41ed-b6ac-26ce6b663e34] +description = "insert data at proper node -> greater number at right node" + +[dd898658-40ab-41d0-965e-7f145bf66e0b] +description = "can create complex tree" + +[9e0c06ef-aeca-4202-b8e4-97f1ed057d56] +description = "can sort data -> can sort single number" + +[425e6d07-fceb-4681-a4f4-e46920e380bb] +description = "can sort data -> can sort if second number is smaller than first" + +[bd7532cc-6988-4259-bac8-1d50140079ab] +description = "can sort data -> can sort if second number is same as first" + +[b6d1b3a5-9d79-44fd-9013-c83ca92ddd36] +description = "can sort data -> can sort if second number is greater than first" + +[d00ec9bd-1288-4171-b968-d44d0808c1c8] +description = "can sort data -> can sort complex tree" diff --git a/exercises/practice/binary-search-tree/binary_search_tree.odin b/exercises/practice/binary-search-tree/binary_search_tree.odin new file mode 100644 index 0000000..e007e85 --- /dev/null +++ b/exercises/practice/binary-search-tree/binary_search_tree.odin @@ -0,0 +1,22 @@ +package binary_search_tree + +Tree :: ^Node + +Node :: struct { + value: int, + left: Tree, + right: Tree, +} + +destroy_tree :: proc(t: Tree) { + // Implement this procedure. +} + +insert :: proc(t: ^Tree, value: int) { + // Implement this procedure. +} + +sorted_data :: proc(t: Tree) -> []int { + // Implement this procedure. + return nil +} diff --git a/exercises/practice/binary-search-tree/binary_search_tree_test.odin b/exercises/practice/binary-search-tree/binary_search_tree_test.odin new file mode 100644 index 0000000..6697791 --- /dev/null +++ b/exercises/practice/binary-search-tree/binary_search_tree_test.odin @@ -0,0 +1,229 @@ +package binary_search_tree + +import "core:fmt" +import tt "core:testing" + +@(test) +/// description = data is retained +test_data_is_retained :: proc(t: ^tt.T) { + + data := [?]int{4} + tree: Tree + for v in data[:] { + insert(&tree, v) + } + defer destroy_tree(tree) + + tt.expect(t, tree != nil) + if tree == nil { return } + tt.expect_value(t, tree.value, 4) + tt.expect(t, tree.left == nil) + tt.expect(t, tree.right == nil) +} + +@(test) +/// description = insert data at proper node -> smaller number at left node +test_insert_data_at_proper_node___smaller_number_at_left_node :: proc(t: ^tt.T) { + + data := [?]int{4, 2} + tree: Tree + for v in data[:] { + insert(&tree, v) + } + defer destroy_tree(tree) + + tt.expect(t, tree != nil) + if tree == nil { return } + tt.expect_value(t, tree.value, 4) + tt.expect(t, tree.left != nil) + if tree.left == nil { return } + tt.expect_value(t, tree.left.value, 2) + tt.expect(t, tree.left.left == nil) + tt.expect(t, tree.left.right == nil) + tt.expect(t, tree.right == nil) +} + +@(test) +/// description = insert data at proper node -> same number at left node +test_insert_data_at_proper_node___same_number_at_left_node :: proc(t: ^tt.T) { + + data := [?]int{4, 4} + tree: Tree + for v in data[:] { + insert(&tree, v) + } + defer destroy_tree(tree) + + tt.expect(t, tree != nil) + if tree == nil { return } + tt.expect_value(t, tree.value, 4) + tt.expect(t, tree.left != nil) + if tree.left == nil { return } + tt.expect_value(t, tree.left.value, 4) + tt.expect(t, tree.left.left == nil) + tt.expect(t, tree.left.right == nil) + tt.expect(t, tree.right == nil) +} + +@(test) +/// description = insert data at proper node -> greater number at right node +test_insert_data_at_proper_node___greater_number_at_right_node :: proc(t: ^tt.T) { + + data := [?]int{4, 5} + tree: Tree + for v in data[:] { + insert(&tree, v) + } + defer destroy_tree(tree) + + tt.expect(t, tree != nil) + if tree == nil { return } + tt.expect_value(t, tree.value, 4) + tt.expect(t, tree.left == nil) + tt.expect(t, tree.right != nil) + if tree.right == nil { return } + tt.expect_value(t, tree.right.value, 5) + tt.expect(t, tree.right.right == nil) + tt.expect(t, tree.right.right == nil) +} + +@(test) +/// description = can create complex tree +test_can_create_complex_tree :: proc(t: ^tt.T) { + + data := [?]int{4, 2, 6, 1, 3, 5, 7} + tree: Tree + for v in data[:] { + insert(&tree, v) + } + defer destroy_tree(tree) + + tt.expect(t, tree != nil) + if tree == nil { return } + tt.expect_value(t, tree.value, 4) + tt.expect(t, tree.left != nil) + if tree.left == nil { return } + tt.expect_value(t, tree.left.value, 2) + tt.expect(t, tree.left.left != nil) + if tree.left.left == nil { return } + tt.expect_value(t, tree.left.left.value, 1) + tt.expect(t, tree.left.left.left == nil) + tt.expect(t, tree.left.left.right == nil) + tt.expect(t, tree.left.right != nil) + if tree.left.right == nil { return } + tt.expect_value(t, tree.left.right.value, 3) + tt.expect(t, tree.left.right.left == nil) + tt.expect(t, tree.left.right.right == nil) + tt.expect(t, tree.right != nil) + if tree.right == nil { return } + tt.expect_value(t, tree.right.value, 6) + tt.expect(t, tree.right.left != nil) + if tree.right.left == nil { return } + tt.expect_value(t, tree.right.left.value, 5) + tt.expect(t, tree.right.left.left == nil) + tt.expect(t, tree.right.left.right == nil) + tt.expect(t, tree.right.right != nil) + if tree.right.right == nil { return } + tt.expect_value(t, tree.right.right.value, 7) + tt.expect(t, tree.right.right.left == nil) + tt.expect(t, tree.right.right.right == nil) +} + +@(test) +/// description = can sort data -> can sort single number +test_can_sort_data___can_sort_single_number :: proc(t: ^tt.T) { + + data := [?]int{2} + input: Tree + for v in data[:] { + insert(&input, v) + } + result := sorted_data(input) + defer { + destroy_tree(input) + delete(result) + } + expect_slices(t, result, []int{2}) + +} + +@(test) +/// description = can sort data -> can sort if second number is smaller than first +test_can_sort_data___can_sort_if_second_number_is_smaller_than_first :: proc(t: ^tt.T) { + + data := [?]int{2, 1} + input: Tree + for v in data[:] { + insert(&input, v) + } + result := sorted_data(input) + defer { + destroy_tree(input) + delete(result) + } + expect_slices(t, result, []int{1, 2}) +} + +@(test) +/// description = can sort data -> can sort if second number is same as first +test_can_sort_data___can_sort_if_second_number_is_same_as_first :: proc(t: ^tt.T) { + + data := [?]int{2, 2} + input: Tree + for v in data[:] { + insert(&input, v) + } + result := sorted_data(input) + defer { + destroy_tree(input) + delete(result) + } + expect_slices(t, result, []int{2, 2}) +} + +@(test) +/// description = can sort data -> can sort if second number is greater than first +test_can_sort_data___can_sort_if_second_number_is_greater_than_first :: proc(t: ^tt.T) { + + data := [?]int{2, 3} + input: Tree + for v in data[:] { + insert(&input, v) + } + result := sorted_data(input) + defer { + destroy_tree(input) + delete(result) + } + expect_slices(t, result, []int{2, 3}) +} + +@(test) +/// description = can sort data -> can sort complex tree +test_can_sort_data___can_sort_complex_tree :: proc(t: ^tt.T) { + + data := [?]int{4, 2, 1, 3, 6, 7, 5} + input: Tree + for v in data[:] { + insert(&input, v) + } + result := sorted_data(input) + defer { + destroy_tree(input) + delete(result) + } + expect_slices(t, result, []int{1, 2, 3, 4, 5, 6, 7}) +} + +// Helper function to compare two slices and provide meaningful error messages. +expect_slices :: proc(t: ^tt.T, actual, expected: []$E, loc := #caller_location) { + + result := fmt.aprintf("%v", actual) + exp_str := fmt.aprintf("%v", expected) + defer { + delete(result) + delete(exp_str) + } + + tt.expect_value(t, result, exp_str, loc = loc) +}