From e50f3e414346fd803b4b0244f337a54305f8a2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 24 Oct 2024 13:29:14 +1300 Subject: [PATCH] Add binary-search-tree exercise (#161) --- config.json | 8 + .../binary-search-tree/.docs/instructions.md | 47 ++++++ .../binary-search-tree/.meta/Example.roc | 24 +++ .../binary-search-tree/.meta/config.json | 18 ++ .../binary-search-tree/.meta/template.j2 | 50 ++++++ .../binary-search-tree/.meta/tests.toml | 40 +++++ .../binary-search-tree/BinarySearchTree.roc | 11 ++ .../binary-search-tree-test.roc | 157 ++++++++++++++++++ 8 files changed, 355 insertions(+) create mode 100644 exercises/practice/binary-search-tree/.docs/instructions.md create mode 100644 exercises/practice/binary-search-tree/.meta/Example.roc create mode 100644 exercises/practice/binary-search-tree/.meta/config.json create mode 100644 exercises/practice/binary-search-tree/.meta/template.j2 create mode 100644 exercises/practice/binary-search-tree/.meta/tests.toml create mode 100644 exercises/practice/binary-search-tree/BinarySearchTree.roc create mode 100644 exercises/practice/binary-search-tree/binary-search-tree-test.roc diff --git a/config.json b/config.json index 7498001..847a454 100644 --- a/config.json +++ b/config.json @@ -571,6 +571,14 @@ "prerequisites": [], "difficulty": 4 }, + { + "slug": "binary-search-tree", + "name": "Binary Search Tree", + "uuid": "c0c96f3d-833e-4d15-b4ae-ccc711a0e165", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "custom-set", "name": "Custom Set", 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..c9bbba5 --- /dev/null +++ b/exercises/practice/binary-search-tree/.docs/instructions.md @@ -0,0 +1,47 @@ +# 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: + + 4 + / + 2 + +If we then added 6, it would look like this: + + 4 + / \ + 2 6 + +If we then added 3, it would look like this + + 4 + / \ + 2 6 + \ + 3 + +And if we then added 1, 5, and 7, it would look like this + + 4 + / \ + / \ + 2 6 + / \ / \ + 1 3 5 7 diff --git a/exercises/practice/binary-search-tree/.meta/Example.roc b/exercises/practice/binary-search-tree/.meta/Example.roc new file mode 100644 index 0000000..e471cb3 --- /dev/null +++ b/exercises/practice/binary-search-tree/.meta/Example.roc @@ -0,0 +1,24 @@ +module [fromList, toList] + +BinaryTree : [Nil, Node { value : U64, left : BinaryTree, right : BinaryTree }] + +fromList : List U64 -> BinaryTree +fromList = \data -> + data |> List.walk Nil insert + +insert : BinaryTree, U64 -> BinaryTree +insert = \tree, value -> + when tree is + Nil -> Node { value, left: Nil, right: Nil } + Node node -> + if value <= node.value then + Node { node & left: (node.left |> insert value) } + else + Node { node & right: (node.right |> insert value) } + +toList : BinaryTree -> List U64 +toList = \tree -> + when tree is + Nil -> [] + Node node -> + node.left |> toList |> List.append node.value |> List.concat (node.right |> toList) 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..cda401f --- /dev/null +++ b/exercises/practice/binary-search-tree/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "ageron" + ], + "files": { + "solution": [ + "BinarySearchTree.roc" + ], + "test": [ + "binary-search-tree-test.roc" + ], + "example": [ + ".meta/Example.roc" + ] + }, + "blurb": "Insert and search for numbers in a binary tree.", + "source": "Josh Cheek" +} diff --git a/exercises/practice/binary-search-tree/.meta/template.j2 b/exercises/practice/binary-search-tree/.meta/template.j2 new file mode 100644 index 0000000..0b5a60e --- /dev/null +++ b/exercises/practice/binary-search-tree/.meta/template.j2 @@ -0,0 +1,50 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} +{{ macros.header() }} + +import {{ exercise | to_pascal }} exposing [fromList, toList] + +{% macro to_int_list(list) -%} +[{% for item in list %}{{ item }},{% endfor %}] +{%- endmacro %} + +{% macro to_tree(tree) -%} +{% if tree is none %}Nil{% else %}Node { + value: {{ tree["data"] }}, + left: {{ to_tree(tree["left"]) }}, + right: {{ to_tree(tree["right"]) }}, +}{% endif %} +{%- endmacro %} + +{% for supercase in cases %} +## +## {{ supercase["description"] }} +## +{%- if supercase["cases"] %} +{%- set subcases = supercase["cases"] %} +{%- else %} +{%- set subcases = [supercase] %} +{%- endif %} + +{% for subcase in subcases -%} +{%- if subcase["description"] != supercase["description"] %} +# {{ subcase["description"] }} +{%- endif %} +expect + data = {{ to_int_list(subcase["input"]["treeData"]) }} +{%- if subcase["property"] == "data" %} + result = data |> fromList + expected = {{ to_tree(subcase["expected"]) }} + result == expected +{%- else %} + tree = data |> fromList + result = tree |> toList + expected = {{ to_int_list(subcase["expected"]) }} + result == expected +{%- endif %} + +{% endfor %} + +{% endfor %} + + 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/BinarySearchTree.roc b/exercises/practice/binary-search-tree/BinarySearchTree.roc new file mode 100644 index 0000000..277d4e7 --- /dev/null +++ b/exercises/practice/binary-search-tree/BinarySearchTree.roc @@ -0,0 +1,11 @@ +module [fromList, toList] + +BinaryTree : [Nil, Node { value : U64, left : BinaryTree, right : BinaryTree }] + +fromList : List U64 -> BinaryTree +fromList = \data -> + crash "Please implement the 'fromList' function" + +toList : BinaryTree -> List U64 +toList = \tree -> + crash "Please implement the 'toList' function" diff --git a/exercises/practice/binary-search-tree/binary-search-tree-test.roc b/exercises/practice/binary-search-tree/binary-search-tree-test.roc new file mode 100644 index 0000000..fed2b2a --- /dev/null +++ b/exercises/practice/binary-search-tree/binary-search-tree-test.roc @@ -0,0 +1,157 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search-tree/canonical-data.json +# File last updated on 2024-10-21 +app [main] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", +} + +main = + Task.ok {} + +import BinarySearchTree exposing [fromList, toList] + +## +## data is retained +## + +expect + data = [4] + result = data |> fromList + expected = Node { + value: 4, + left: Nil, + right: Nil, + } + result == expected + +## +## insert data at proper node +## + +# smaller number at left node +expect + data = [4, 2] + result = data |> fromList + expected = Node { + value: 4, + left: Node { + value: 2, + left: Nil, + right: Nil, + }, + right: Nil, + } + result == expected + +# same number at left node +expect + data = [4, 4] + result = data |> fromList + expected = Node { + value: 4, + left: Node { + value: 4, + left: Nil, + right: Nil, + }, + right: Nil, + } + result == expected + +# greater number at right node +expect + data = [4, 5] + result = data |> fromList + expected = Node { + value: 4, + left: Nil, + right: Node { + value: 5, + left: Nil, + right: Nil, + }, + } + result == expected + +## +## can create complex tree +## + +expect + data = [4, 2, 6, 1, 3, 5, 7] + result = data |> fromList + expected = Node { + value: 4, + left: Node { + value: 2, + left: Node { + value: 1, + left: Nil, + right: Nil, + }, + right: Node { + value: 3, + left: Nil, + right: Nil, + }, + }, + right: Node { + value: 6, + left: Node { + value: 5, + left: Nil, + right: Nil, + }, + right: Node { + value: 7, + left: Nil, + right: Nil, + }, + }, + } + result == expected + +## +## can sort data +## + +# can sort single number +expect + data = [2] + tree = data |> fromList + result = tree |> toList + expected = [2] + result == expected + +# can sort if second number is smaller than first +expect + data = [2, 1] + tree = data |> fromList + result = tree |> toList + expected = [1, 2] + result == expected + +# can sort if second number is same as first +expect + data = [2, 2] + tree = data |> fromList + result = tree |> toList + expected = [2, 2] + result == expected + +# can sort if second number is greater than first +expect + data = [2, 3] + tree = data |> fromList + result = tree |> toList + expected = [2, 3] + result == expected + +# can sort complex tree +expect + data = [2, 1, 3, 6, 7, 5] + tree = data |> fromList + result = tree |> toList + expected = [1, 2, 3, 5, 6, 7] + result == expected +