binary_decision_tree 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/haruska/binary_decision_tree.svg?branch=master)](https://travis-ci.org/haruska/binary_decision_tree)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/haruska/binary_decision_tree/badge.png?branch=master)](https://coveralls.io/r/haruska/binary_decision_tree?branch=master)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/binary_decision_tree.svg)](http://badge.fury.io/rb/binary_decision_tree)
|
6
|
+
[![Dependency Status](https://gemnasium.com/haruska/binary_decision_tree.svg)](https://gemnasium.com/haruska/binary_decision_tree)
|
7
|
+
[![Code Climate](https://codeclimate.com/github/haruska/binary_decision_tree.png)](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
|