binary_decision_tree 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +1 -0
- data/Guardfile +5 -0
- data/README.md +6 -0
- data/Rakefile +8 -0
- data/binary_decision_tree.gemspec +3 -0
- data/lib/binary_decision_tree/marshalled_tree.rb +37 -35
- data/lib/binary_decision_tree/node.rb +50 -33
- data/lib/binary_decision_tree/tree.rb +18 -12
- data/lib/binary_decision_tree/version.rb +1 -1
- data/test/test_helper.rb +24 -0
- data/test/unit/binary_decision_tree/marshalled_tree_test.rb +28 -0
- data/test/unit/binary_decision_tree/node_test.rb +169 -0
- metadata +54 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd430f35feb7cf78297db1208f4678ad0cb192f3
|
4
|
+
data.tar.gz: 4777835269c7eca78d13d3694e11070c2b8f5729
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7998bc2ad37b1d2b0652ac4b4117e199679a64b2594cafa3a2594e2c3b92c71e8b4c2b1a8b330f60c21d60b40d75cf546517d27d3f9c89aacd2bc370773fd81e
|
7
|
+
data.tar.gz: 000651f02ad554ab4ab1350936e16bba13e54f47cb8a417301c70bd9fbfcfd209f2fbf6a326c97db9162e2e9cc4fb6bb775c80607fe77377d281ac2d0bb582db
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# BinaryDecisionTree
|
2
2
|
|
3
|
+
[](https://travis-ci.org/haruska/binary_decision_tree)
|
4
|
+
[](https://coveralls.io/r/haruska/binary_decision_tree?branch=master)
|
5
|
+
[](http://badge.fury.io/rb/binary_decision_tree)
|
6
|
+
[](https://gemnasium.com/haruska/binary_decision_tree)
|
7
|
+
[](https://codeclimate.com/github/haruska/binary_decision_tree)
|
8
|
+
|
3
9
|
A binary tree designed to record decisions based on child nodes. This data structure is useful
|
4
10
|
in things like single elimination tournaments. They can be marshalled and unmarshalled to two
|
5
11
|
numbers.
|
data/Rakefile
CHANGED
@@ -20,4 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.5"
|
22
22
|
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "coveralls"
|
24
|
+
spec.add_development_dependency "guard"
|
25
|
+
spec.add_development_dependency "guard-minitest"
|
23
26
|
end
|
@@ -1,45 +1,47 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
1
|
+
module BinaryDecisionTree
|
2
|
+
class MarshalledTree
|
3
|
+
attr_reader :depth
|
4
|
+
attr_reader :decisions
|
5
|
+
attr_reader :mask
|
6
|
+
|
7
|
+
def initialize(depth, decisions, mask)
|
8
|
+
@depth = depth
|
9
|
+
@decisions = decisions
|
10
|
+
@mask = mask
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_tree(tree)
|
14
|
+
depth = tree.depth
|
15
|
+
decisions = 0
|
16
|
+
mask = 0
|
11
17
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
node = tree.at(i)
|
20
|
-
if !node.decision.nil?
|
21
|
-
mask |= 1 << i
|
22
|
-
decisions |= node.decision << i
|
18
|
+
(2**tree.depth).times do |i|
|
19
|
+
next if i == 0
|
20
|
+
node = tree.at(i)
|
21
|
+
if !node.decision.nil?
|
22
|
+
mask |= 1 << i
|
23
|
+
decisions |= node.decision << i
|
24
|
+
end
|
23
25
|
end
|
24
|
-
end
|
25
26
|
|
26
|
-
|
27
|
-
|
27
|
+
new(depth, decisions, mask)
|
28
|
+
end
|
28
29
|
|
29
|
-
|
30
|
-
|
30
|
+
def to_tree
|
31
|
+
tree = Tree.new(depth)
|
31
32
|
|
32
|
-
|
33
|
-
|
33
|
+
(2**tree.depth).times do |i|
|
34
|
+
next if i == 0
|
34
35
|
|
35
|
-
|
36
|
+
current_position = 1 << i
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
if (mask & current_position) != 0
|
39
|
+
node = tree.at(i)
|
40
|
+
node.decision = (decisions & current_position) == 0 ? 0 : 1
|
41
|
+
end
|
40
42
|
end
|
41
|
-
end
|
42
43
|
|
43
|
-
|
44
|
+
tree
|
45
|
+
end
|
44
46
|
end
|
45
|
-
end
|
47
|
+
end
|
@@ -1,43 +1,60 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module BinaryDecisionTree
|
2
|
+
class Node
|
3
|
+
LEFT = 0
|
4
|
+
RIGHT = 1
|
3
5
|
|
4
|
-
|
5
|
-
attr_reader :tree
|
6
|
+
attr_accessor :decision #nil, 0, or 1
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
@slot = slot
|
10
|
-
@decision = decision
|
11
|
-
end
|
8
|
+
attr_reader :slot #bit position
|
9
|
+
attr_reader :tree
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
when 1
|
18
|
-
right.nil? ? right_position : right.value
|
19
|
-
else
|
20
|
-
nil
|
11
|
+
def initialize(tree, slot)
|
12
|
+
@tree = tree
|
13
|
+
@slot = slot
|
14
|
+
@decision = nil
|
21
15
|
end
|
22
|
-
end
|
23
16
|
|
24
|
-
|
25
|
-
|
26
|
-
|
17
|
+
def value
|
18
|
+
case decision
|
19
|
+
when LEFT
|
20
|
+
left.nil? ? left_position : left.value
|
21
|
+
when RIGHT
|
22
|
+
right.nil? ? right_position : right.value
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
def leaf?
|
29
|
+
left.nil? && right.nil?
|
30
|
+
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
def current_depth
|
33
|
+
Math.log2(slot).floor + 1
|
34
|
+
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
def parent_position
|
37
|
+
(slot % 2 == 0 ? slot + 1 : slot) / 2
|
38
|
+
end
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
def left_position
|
41
|
+
slot * 2
|
42
|
+
end
|
43
|
+
|
44
|
+
def right_position
|
45
|
+
left_position + 1
|
46
|
+
end
|
47
|
+
|
48
|
+
def parent
|
49
|
+
tree.at(parent_position)
|
50
|
+
end
|
51
|
+
|
52
|
+
def left
|
53
|
+
tree.at(left_position)
|
54
|
+
end
|
55
|
+
|
56
|
+
def right
|
57
|
+
tree.at(right_position)
|
58
|
+
end
|
42
59
|
end
|
43
|
-
end
|
60
|
+
end
|
@@ -1,16 +1,22 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module BinaryDecisionTree
|
2
|
+
class Tree
|
3
|
+
attr_reader :depth
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
def initialize(depth)
|
6
|
+
@depth = depth
|
7
|
+
@nodes = Array.new(size) {|i| i == 0 ? nil : Node.new(self, i)}
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def root
|
11
|
+
@nodes[1]
|
12
|
+
end
|
13
|
+
|
14
|
+
def at(position)
|
15
|
+
@nodes[position]
|
16
|
+
end
|
12
17
|
|
13
|
-
|
14
|
-
|
18
|
+
def size
|
19
|
+
2**depth
|
20
|
+
end
|
15
21
|
end
|
16
|
-
end
|
22
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'coveralls'
|
2
|
+
Coveralls.wear!
|
3
|
+
|
4
|
+
require 'binary_decision_tree'
|
5
|
+
require 'minitest/autorun'
|
6
|
+
|
7
|
+
class MiniTest::Spec
|
8
|
+
def assert_same_node(exp, act, msg = nil)
|
9
|
+
assert_equal exp.decision, act.decision, msg
|
10
|
+
assert_equal exp.slot, act.slot, msg
|
11
|
+
end
|
12
|
+
|
13
|
+
def assert_same_tree(exp, act, msg = nil)
|
14
|
+
assert_equal exp.depth, act.depth, msg
|
15
|
+
exp.size.times do |i|
|
16
|
+
next if i == 0
|
17
|
+
exp_node = exp.at(i)
|
18
|
+
act_node = act.at(i)
|
19
|
+
assert_same_node exp_node, act_node, msg
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../../../test/test_helper'
|
2
|
+
|
3
|
+
module BinaryDecisionTree
|
4
|
+
class MarshalledTreeTest < MiniTest::Spec
|
5
|
+
before do
|
6
|
+
@tree = Tree.new(6)
|
7
|
+
@tree.size.times do |i|
|
8
|
+
next if i == 0
|
9
|
+
@tree.at(i).decision = rand(2)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "marshalling a tree" do
|
14
|
+
before do
|
15
|
+
@marshalled_tree = MarshalledTree.from_tree(@tree)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should marshal and unmarshal to the same tree" do
|
19
|
+
assert_same_tree @tree, @marshalled_tree.to_tree
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be able to unmarshal from numeric values" do
|
23
|
+
another_tree = MarshalledTree.new(@tree.depth, @marshalled_tree.decisions, @marshalled_tree.mask).to_tree
|
24
|
+
assert_same_tree @tree, another_tree
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require_relative '../../../test/test_helper'
|
2
|
+
|
3
|
+
module BinaryDecisionTree
|
4
|
+
class NodeTest < MiniTest::Spec
|
5
|
+
describe "creating a new node" do
|
6
|
+
before do
|
7
|
+
@tree = Tree.new(1)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should take both a tree and slot number and expose them" do
|
11
|
+
node = Node.new(@tree, 1)
|
12
|
+
|
13
|
+
assert_equal @tree, node.tree
|
14
|
+
assert_equal 1, node.slot
|
15
|
+
assert_nil node.decision
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "calculating a value" do
|
20
|
+
before do
|
21
|
+
@tree = Tree.new(2)
|
22
|
+
@nodes = @tree.size.times.collect {|i| @tree.at(i)}
|
23
|
+
@nodes.each {|n| n.decision = rand(2) unless n.nil?}
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "when decision is left" do
|
27
|
+
before do
|
28
|
+
@nodes.each {|n| n.decision = Node::LEFT unless n.nil?}
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return the slot id of next left position on a leaf" do
|
32
|
+
node = @nodes.last
|
33
|
+
assert node.leaf?
|
34
|
+
|
35
|
+
assert_equal node.left_position, node.value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "when decision is right" do
|
40
|
+
before do
|
41
|
+
@nodes.each {|n| n.decision = Node::RIGHT unless n.nil?}
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return the slot id of next right position on a leaf" do
|
45
|
+
node = @nodes.last
|
46
|
+
assert node.leaf?
|
47
|
+
|
48
|
+
assert_equal node.right_position, node.value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should traverse the tree until at a leaf and return that value" do
|
53
|
+
node = @tree.root
|
54
|
+
node.decision = Node::LEFT
|
55
|
+
|
56
|
+
assert_equal node.left.value, node.value
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should be nil if a decision is not set" do
|
60
|
+
node = Tree.new(1).root
|
61
|
+
assert_nil node.decision
|
62
|
+
assert_nil node.value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "a leaf node" do
|
67
|
+
before do
|
68
|
+
@node = Tree.new(2).at(2)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should know it is a leaf node" do
|
72
|
+
assert @node.leaf?
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should not have a left or right child in the tree" do
|
76
|
+
assert_nil @node.left
|
77
|
+
assert_nil @node.right
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should still have left and right positions" do
|
81
|
+
assert !@node.left_position.nil?
|
82
|
+
assert !@node.right_position.nil?
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should have a parent" do
|
86
|
+
assert !@node.parent.nil?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "current depth calculation" do
|
91
|
+
before do
|
92
|
+
@depth_hash = {}
|
93
|
+
|
94
|
+
#bfs
|
95
|
+
tree = Tree.new(6) #63 nodes
|
96
|
+
tree.depth.times do |i|
|
97
|
+
current_depth = i + 1
|
98
|
+
@depth_hash[current_depth] = []
|
99
|
+
if current_depth == 1
|
100
|
+
@depth_hash[current_depth] << tree.root
|
101
|
+
else
|
102
|
+
@depth_hash[current_depth - 1].each do |parent_node|
|
103
|
+
@depth_hash[current_depth] << parent_node.left
|
104
|
+
@depth_hash[current_depth] << parent_node.right
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should return the correct depth" do
|
111
|
+
@depth_hash.each do |depth, nodes|
|
112
|
+
nodes.each do |node|
|
113
|
+
assert_equal depth, node.current_depth
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "parent position calculation" do
|
120
|
+
before do
|
121
|
+
@tree = Tree.new(6)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should be half of the current slot value" do
|
125
|
+
i = 1
|
126
|
+
while !@tree.at(i).nil? && !@tree.at(i).left.nil?
|
127
|
+
node = @tree.at(i)
|
128
|
+
assert_same node, node.left.parent
|
129
|
+
assert_same node, node.right.parent
|
130
|
+
i += 1
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should have a get node helper" do
|
135
|
+
node = @tree.at(3)
|
136
|
+
assert_equal 1, node.parent_position
|
137
|
+
assert_same @tree.at(1), node.parent
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "child position calculations" do
|
142
|
+
before do
|
143
|
+
@tree = Tree.new(3)
|
144
|
+
@node = @tree.at(2)
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "left position calculation" do
|
148
|
+
it "should be twice the current slot value" do
|
149
|
+
assert_equal 4, @node.left_position
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should have a get node helper" do
|
153
|
+
assert_same @tree.at(4), @node.left
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "right position calculation" do
|
158
|
+
it "should be next to the left child (one greater)" do
|
159
|
+
assert_equal 5, @node.right_position
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should have a get node helper" do
|
163
|
+
assert_same @tree.at(5), @node.right
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: binary_decision_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Haruska
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-04-
|
11
|
+
date: 2014-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,6 +38,48 @@ dependencies:
|
|
38
38
|
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: coveralls
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
41
83
|
description: A binary decision tree useful for brackets of single elimination tournaments.
|
42
84
|
email:
|
43
85
|
- jason@haruska.com
|
@@ -45,8 +87,11 @@ executables: []
|
|
45
87
|
extensions: []
|
46
88
|
extra_rdoc_files: []
|
47
89
|
files:
|
90
|
+
- .coveralls.yml
|
48
91
|
- .gitignore
|
92
|
+
- .travis.yml
|
49
93
|
- Gemfile
|
94
|
+
- Guardfile
|
50
95
|
- LICENSE.txt
|
51
96
|
- README.md
|
52
97
|
- Rakefile
|
@@ -56,6 +101,9 @@ files:
|
|
56
101
|
- lib/binary_decision_tree/node.rb
|
57
102
|
- lib/binary_decision_tree/tree.rb
|
58
103
|
- lib/binary_decision_tree/version.rb
|
104
|
+
- test/test_helper.rb
|
105
|
+
- test/unit/binary_decision_tree/marshalled_tree_test.rb
|
106
|
+
- test/unit/binary_decision_tree/node_test.rb
|
59
107
|
homepage: http://github.com/haruska/binary_decision_tree
|
60
108
|
licenses:
|
61
109
|
- MIT
|
@@ -80,4 +128,7 @@ rubygems_version: 2.0.14
|
|
80
128
|
signing_key:
|
81
129
|
specification_version: 4
|
82
130
|
summary: A binary decision tree
|
83
|
-
test_files:
|
131
|
+
test_files:
|
132
|
+
- test/test_helper.rb
|
133
|
+
- test/unit/binary_decision_tree/marshalled_tree_test.rb
|
134
|
+
- test/unit/binary_decision_tree/node_test.rb
|