diff --git a/config.json b/config.json index d8a5c632..3f3c9462 100644 --- a/config.json +++ b/config.json @@ -348,6 +348,24 @@ "loops", "math" ] + }, + { + "slug": "binary-search-tree", + "uuid": "597f86bc-6717-4a4c-a2c6-58241ac3d6cb", + "core": true, + "unlocked_by": null, + "difficulty": 5, + "topics": [ + "algorithms", + "classes", + "generics", + "inheritance", + "logic", + "object_oriented_programming", + "searching", + "sorting", + "trees" + ] } ] } diff --git a/exercises/binary-search-tree/README.md b/exercises/binary-search-tree/README.md new file mode 100644 index 00000000..d1e7ae3f --- /dev/null +++ b/exercises/binary-search-tree/README.md @@ -0,0 +1,62 @@ +# Binary Search Tree + +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 + +## Resources + +- [Data structures: Binary Search Tree (YouTube)](https://youtu.be/pYT9F8_LFTM?t=9m39s) + +## Submitting Incomplete Solutions + +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/exercises/binary-search-tree/analysis_options.yaml b/exercises/binary-search-tree/analysis_options.yaml new file mode 100644 index 00000000..6752e841 --- /dev/null +++ b/exercises/binary-search-tree/analysis_options.yaml @@ -0,0 +1,18 @@ +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false + errors: + unused_element: error + unused_import: error + unused_local_variable: error + dead_code: error + +linter: + rules: + # Error Rules + - avoid_relative_lib_imports + - avoid_types_as_parameter_names + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - valid_regexps diff --git a/exercises/binary-search-tree/lib/binary_search_tree.dart b/exercises/binary-search-tree/lib/binary_search_tree.dart new file mode 100644 index 00000000..3c1df496 --- /dev/null +++ b/exercises/binary-search-tree/lib/binary_search_tree.dart @@ -0,0 +1,3 @@ +class BinarySearchTree { + // Put your code here +} diff --git a/exercises/binary-search-tree/lib/example.dart b/exercises/binary-search-tree/lib/example.dart new file mode 100644 index 00000000..8d41a85e --- /dev/null +++ b/exercises/binary-search-tree/lib/example.dart @@ -0,0 +1,61 @@ +class BinarySearchTree> { + /// Root node for the tree. + final Node root; + + BinarySearchTree(T rootData) : root = new Node(rootData) { + // Note: [assert] does not run in release mode + assert(rootData != null); + } + + /// Returns an empty `List` if [root] is `null`. + /// + /// Otherwise returns data traversed in ascending order. + Iterable get sortedData sync* { + for (var t in root?.ascendingOrder ?? []) yield t; + } + + /// Returns `true` on success, `false` on failure. + bool insert(T value) => root?.insert(value) ?? false; +} + +class Node> { + /// The actual Data this node holds which should be a [Comparable] + final T data; + + /// Left node of this node, can be `null` + Node left; + + /// Right node of this node, can be `null` + Node right; + + Node(this.data, [this.left, this.right]) { + // Note: [assert] does not run in release mode + assert(data != null); + } + + /// This is **inorder** traversal of the tree. + Iterable get ascendingOrder sync* { + if (data == null) return; + + /// Traverse left sub-tree if it's present + for (var t in left?.ascendingOrder ?? []) yield t; + + yield data; + + /// Traverse right sub-tree if it's present + for (var t in right?.ascendingOrder ?? []) yield t; + } + + /// Returns `true` on success, `false` on failure. + bool insert(T value) { + if (value == null) return false; + + /// Insert in left sub-tree if its a smaller or equal number. + if (value.compareTo(data) <= 0) { + return left?.insert(value) ?? (left = new Node(value)).data == value; + } + + /// Insert in right sub-tree if its a greater number. + return right?.insert(value) ?? (right = new Node(value)).data == value; + } +} diff --git a/exercises/binary-search-tree/pubspec.yaml b/exercises/binary-search-tree/pubspec.yaml new file mode 100644 index 00000000..5035d3e7 --- /dev/null +++ b/exercises/binary-search-tree/pubspec.yaml @@ -0,0 +1,5 @@ +name: 'binary_search_tree' +environment: + sdk: '>=1.24.0 <3.0.0' +dev_dependencies: + test: '<2.0.0' diff --git a/exercises/binary-search-tree/test/binary_search_tree_test.dart b/exercises/binary-search-tree/test/binary_search_tree_test.dart new file mode 100644 index 00000000..060608de --- /dev/null +++ b/exercises/binary-search-tree/test/binary_search_tree_test.dart @@ -0,0 +1,87 @@ +import 'package:binary_search_tree/binary_search_tree.dart'; +import 'package:test/test.dart'; + +void main() { + group('BinarySearchTree', () { + test('data is retained', () { + final bst = new BinarySearchTree('4'); + + expect(bst.root.data, equals('4')); + }, skip: false); + + group('insert data at proper node', () { + test('smaller number at left node', () { + final bst = new BinarySearchTree('4')..insert('2'); + + expect(bst.root.data, equals('4')); + expect(bst.root.left.data, equals('2')); + }, skip: true); + + test('same number at left node', () { + final bst = new BinarySearchTree('4')..insert('4'); + + expect(bst.root.data, equals('4')); + expect(bst.root.left.data, equals('4')); + }, skip: true); + + test('greater number at right node', () { + final bst = new BinarySearchTree('4')..insert('5'); + + expect(bst.root.data, equals('4')); + expect(bst.root.right.data, equals('5')); + }, skip: true); + + test('can create complex tree', () { + final bst = new BinarySearchTree('4') + ..insert('2') + ..insert("6") + ..insert("1") + ..insert("3") + ..insert("5") + ..insert("7"); + + expect(bst.root.data, equals('4')); + + expect(bst.root.left.data, equals('2')); + expect(bst.root.left.left.data, equals('1')); + expect(bst.root.left.right.data, equals('3')); + + expect(bst.root.right.data, equals('6')); + expect(bst.root.right.left.data, equals('5')); + expect(bst.root.right.right.data, equals('7')); + }, skip: true); + }); + + group('can sort data', () { + test('can sort single number', () { + final bst = new BinarySearchTree('2'); + + expect(bst.sortedData, equals(['2'])); + }, skip: true); + + test('can sort if second number is smaller than first', () { + final bst = new BinarySearchTree('2')..insert('1'); + + expect(bst.sortedData, equals(['1', '2'])); + }, skip: true); + + test('can sort if second number is same as first', () { + final bst = new BinarySearchTree('2')..insert('2'); + + expect(bst.sortedData, equals(['2', '2'])); + }, skip: true); + + test('can sort if second number is greater than first', () { + final bst = new BinarySearchTree('2')..insert('3'); + + expect(bst.sortedData, equals(['2', '3'])); + }, skip: true); + + test('can sort complex tree', () { + final bst = new BinarySearchTree('2')..insert("1")..insert("3")..insert("6")..insert("7")..insert("5"); + + expect(bst.sortedData, equals(['1', '2', '3', '5', '6', '7'])); + }, skip: true); + }); + }); +}