compsci 0.0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dc7af3ec0bc93ea91b485b329e66033cca137597
4
+ data.tar.gz: f62c24c3171a6534e92fa8f53dba942e009442c3
5
+ SHA512:
6
+ metadata.gz: fe64178808bef0a929882318ed08941de2179940bc332ee42ca9f48f0430e566ae6299f628aa4ad11573787c59fe9746cd8a23b5309fa1ecd92117fd1c0daf16
7
+ data.tar.gz: 0ce9187b7eb0d17add238c6344452e344685e00126927359f2d2d54f6e3fbc8ce5a389d153027c7f626b8039f86e8b7409d1950f2d14afbd5619ec8dd878ff4c
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ [![Build Status](https://travis-ci.org/rickhull/compsci.svg?branch=master)](https://travis-ci.org/rickhull/compsci)
2
+
3
+ # Introduction
4
+
5
+ Provided are some toy implementations for some basic computer science problems.
6
+
7
+ ## [`Tree`](/lib/compsci/tree.rb) data structures
8
+
9
+ * `Tree` - enforces number of children per node
10
+ * `Tree::Node` - references parent and children nodes
11
+ * `BinaryTree` - subclass of `Tree`; child_slots == 2
12
+ * `CompleteBinaryTree` - efficient Array implementation
13
+
14
+ ## [`Heap`](lib/compsci/heap.rb) data structure
15
+
16
+ Implemented with a `CompleteBinaryTree` for storage using simple arithmetic to
17
+ determine array indices for parent and children. See the
18
+ [heap example](https://github.com/rickhull/compsci/blob/master/eamples/heap.rb)
19
+ which can be executed (among other examples) via `rake examples`.
20
+
21
+ Both minheaps and maxheaps are supported. The primary operations are
22
+ `Heap#push` and `Heap#pop`. My basic Vagrant VM gets over 350k pushes per
23
+ second, constant up past 1M pushes.
24
+
25
+ ## [`Fibonacci`](lib/compsci/fib.rb) functions
26
+
27
+ * `Fibonacci.classic(n)` - naive, recursive
28
+ * `Fibonacci.cache_recursive(n)` - as above, caching already computed results
29
+ * `Fibonacci.cache_iterative(n)` - as above but iterative
30
+ * `Fibonacci.dynamic(n)` - as above but without a cache structure
31
+
32
+ ## [`Timer`](/lib/compsci/timer.rb) functions
33
+
34
+ * `Timer.now` - uses `Process::CLOCK_MONOTONIC` if available
35
+ * `Timer.elapsed` - provides the elapsed time to run a block
36
+ * `Timer.loop_average` - runs a block repeatedly and provides the mean elapsed
37
+ time
38
+ * `Timer.since` - provides the elapsed time since a prior time
39
+
40
+ ## [`Fit`](lib/compsci/fit.rb) functions
41
+
42
+ * `Fit.sigma` - sums the result of a block applied to array values
43
+ * `Fit.error` - returns a generic r^2 value, the coefficient of determination
44
+ * `Fit.constant` - fits `y = a + 0x`; returns the mean and variance
45
+ * `Fit.logarithmic` - fits `y = a + b*ln(x)`; returns a, b, r^2
46
+ * `Fit.linear` - fits `y = a + bx`; returns a, b, r^2
47
+ * `Fit.exponential` fits `y = ae^(bx)`; returns a, b, r^2
48
+ * `Fit.power` fits `y = ax^b`; returns a, b, r^2
data/Rakefile ADDED
@@ -0,0 +1,108 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new :test do |t|
4
+ t.pattern = "test/*.rb"
5
+ t.warning = true
6
+ end
7
+
8
+ Rake::TestTask.new bench: :test do |t|
9
+ t.pattern = "test/bench/*.rb"
10
+ t.warning = true
11
+ t.description = "Run benchmarks"
12
+ end
13
+
14
+ desc "Run example scripts"
15
+ task examples: :test do
16
+ Dir['examples/**/*.rb'].each { |filepath|
17
+ puts
18
+ sh "ruby -Ilib #{filepath}"
19
+ puts
20
+ }
21
+ end
22
+
23
+ task default: :examples
24
+
25
+ #
26
+ # METRICS
27
+ #
28
+
29
+ metrics_tasks = []
30
+
31
+ begin
32
+ require 'flog_task'
33
+ FlogTask.new do |t|
34
+ t.threshold = 420
35
+ t.dirs = ['lib']
36
+ t.verbose = true
37
+ end
38
+ metrics_tasks << :flog
39
+ rescue LoadError
40
+ warn 'flog_task unavailable'
41
+ end
42
+
43
+ begin
44
+ require 'flay_task'
45
+ FlayTask.new do |t|
46
+ t.dirs = ['lib']
47
+ t.verbose = true
48
+ end
49
+ metrics_tasks << :flay
50
+ rescue LoadError
51
+ warn 'flay_task unavailable'
52
+ end
53
+
54
+ begin
55
+ require 'roodi_task'
56
+ RoodiTask.new config: '.roodi.yml', patterns: ['lib/**/*.rb']
57
+ metrics_tasks << :roodi
58
+ rescue LoadError
59
+ warn "roodi_task unavailable"
60
+ end
61
+
62
+ desc "Generate code metrics reports"
63
+ task code_metrics: metrics_tasks
64
+
65
+ #
66
+ # PROFILING
67
+ #
68
+
69
+ desc "Show current system load"
70
+ task "loadavg" do
71
+ puts File.read "/proc/loadavg"
72
+ end
73
+
74
+ def lib_sh(cmd)
75
+ sh "RUBYLIB=lib #{cmd}"
76
+ end
77
+
78
+ def rprof_sh(script, args = '', rprof_args = '')
79
+ lib_sh ['ruby-prof', rprof_args, script, '--', args].join(' ')
80
+ end
81
+
82
+ scripts = ["examples/binary_tree.rb", "examples/heap.rb"]
83
+
84
+ desc "Run ruby-prof on examples/"
85
+ task "ruby-prof" => "loadavg" do
86
+ scripts.each { |script| rprof_sh script }
87
+ end
88
+
89
+ desc "Run ruby-prof on examples/ with --exclude-common-cycles"
90
+ task "ruby-prof-exclude" => "loadavg" do
91
+ scripts.each { |script| rprof_sh script, "", "--exclude-common-cycles" }
92
+ end
93
+
94
+ #
95
+ # GEM BUILD / PUBLISH
96
+ #
97
+
98
+ begin
99
+ require 'buildar'
100
+
101
+ Buildar.new do |b|
102
+ b.gemspec_file = 'compsci.gemspec'
103
+ b.version_file = 'VERSION'
104
+ b.use_git = true
105
+ end
106
+ rescue LoadError
107
+ warn "buildar tasks unavailable"
108
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1.1
data/compsci.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'compsci'
3
+ s.summary = "Toy implementations for some basic computer science problems"
4
+ s.description = "Trees, Heaps, Timers, Error fitting, etc"
5
+ s.authors = ["Rick Hull"]
6
+ s.homepage = "https://github.com/rickhull/compsci"
7
+ s.license = "LGPL-3.0"
8
+
9
+ s.required_ruby_version = "~> 2"
10
+
11
+ s.version = File.read(File.join(__dir__, 'VERSION')).chomp
12
+
13
+ s.files = %w[
14
+ compsci.gemspec
15
+ VERSION
16
+ README.md
17
+ Rakefile
18
+ lib/compsci.rb
19
+ lib/compsci/fib.rb
20
+ lib/compsci/fit.rb
21
+ lib/compsci/heap.rb
22
+ lib/compsci/timer.rb
23
+ lib/compsci/tree.rb
24
+ examples/binary_tree.rb
25
+ examples/heap.rb
26
+ examples/timer.rb
27
+ test/fib.rb
28
+ test/fit.rb
29
+ test/heap.rb
30
+ test/timer.rb
31
+ test/tree.rb
32
+ test/bench/fib.rb
33
+ test/bench/heap.rb
34
+ test/bench/tree.rb
35
+ ]
36
+
37
+ s.add_development_dependency "minitest", "~> 5.0"
38
+ end
@@ -0,0 +1,21 @@
1
+ require 'compsci/tree'
2
+
3
+ include CompSci
4
+
5
+ vals = []
6
+ 30.times { vals << rand(99) }
7
+ p vals
8
+
9
+ root_node = Tree::Node.new vals.shift
10
+ tree = BinaryTree.new(root_node)
11
+ tree.push vals.shift until vals.empty?
12
+
13
+ tree.bf_print
14
+
15
+ tree.df_search { |n|
16
+ puts "visited #{n}"
17
+ false # or n.value > 90
18
+ }
19
+ puts
20
+
21
+ p tree
data/examples/heap.rb ADDED
@@ -0,0 +1,65 @@
1
+ require 'compsci/heap'
2
+ require 'compsci/timer'
3
+
4
+ include CompSci
5
+
6
+ puts <<EOF
7
+ #
8
+ # 3 seconds worth of inserts
9
+ #
10
+
11
+ EOF
12
+
13
+ count = 0
14
+ start = Timer.now
15
+ h = Heap.new
16
+ elapsed = 0
17
+
18
+ while elapsed < 3
19
+ _answer, push_elapsed = Timer.elapsed { h.push rand 99999 }
20
+ count += 1
21
+ puts "%ith push: %0.8f s" % [count, push_elapsed] if count % 10000 == 0
22
+
23
+ if count % 100000 == 0
24
+ start_100k ||= start
25
+ push_100k_elapsed = Timer.now - start_100k
26
+ puts "-------------"
27
+ puts " 100k push: %0.8f s (%ik push / s)" %
28
+ [push_100k_elapsed, 100.to_f / push_100k_elapsed]
29
+ puts
30
+ start_100k = Timer.now
31
+ end
32
+ elapsed = Timer.now - start
33
+ end
34
+
35
+ puts "pushed %i items in %0.1f s" % [count, elapsed]
36
+ puts
37
+
38
+ print "still a heap with #{h.size} items? "
39
+ answer, elapsed = Timer.elapsed { h.heap? }
40
+ puts "%s - %0.3f sec" % [answer ? 'YES' : 'NO', elapsed]
41
+ puts
42
+
43
+ puts <<EOF
44
+ #
45
+ # 99 inserts; display the internal array
46
+ #
47
+
48
+ EOF
49
+
50
+ h = Heap.new
51
+
52
+ puts "push: %s" % Array.new(99) { rand(99).tap { |i| h.push i } }.join(' ')
53
+ puts "heap store: #{h.store.inspect}"
54
+ puts "heap: #{h.heap?}"
55
+ puts
56
+
57
+ puts "pop: %i" % h.pop
58
+ puts "heap store: #{h.store.inspect}"
59
+ puts "heap: #{h.heap?}"
60
+ puts
61
+
62
+ puts "pop: %s" % Array.new(9) { h.pop }.join(' ')
63
+ puts "heap store: #{h.store.inspect}"
64
+ puts "heap: #{h.heap?}"
65
+ puts
data/examples/timer.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'compsci/timer'
2
+
3
+ include CompSci
4
+
5
+ overall_start = Timer.now
6
+
7
+ start = Timer.now
8
+ print "running sleep 0.01 (50x): "
9
+ _answer, each_et = Timer.loop_average(count: 50) {
10
+ print '.'
11
+ sleep 0.01
12
+ }
13
+ puts
14
+ puts "each: %0.3f" % each_et
15
+ puts "elapsed: %0.3f" % Timer.since(start)
16
+ puts "cumulative: %0.3f" % Timer.since(overall_start)
17
+ puts
18
+
19
+
20
+ start = Timer.now
21
+ print "running sleep 0.02 (0.5 s): "
22
+ _answer, each_et = Timer.loop_average(seconds: 0.5) {
23
+ print '.'
24
+ sleep 0.02
25
+ }
26
+ puts
27
+ puts "each: %0.3f" % each_et
28
+ puts "elapsed: %0.3f" % Timer.since(start)
29
+ puts "cumulative: %0.3f" % Timer.since(overall_start)
30
+ puts
31
+
32
+
33
+ start = Timer.now
34
+ print "running sleep 2 (1 s): "
35
+ _answer, each_et = Timer.loop_average(seconds: 1) {
36
+ print '.'
37
+ sleep 2
38
+ }
39
+ puts
40
+ puts "each: %0.3f" % each_et
41
+ puts "elapsed: %0.3f" % Timer.since(start)
42
+ puts "cumulative: %0.3f" % Timer.since(overall_start)
@@ -0,0 +1,24 @@
1
+ require 'compsci'
2
+
3
+ module CompSci::Fibonacci
4
+ def self.classic(n)
5
+ n < 2 ? n : classic(n-1) + classic(n-2)
6
+ end
7
+
8
+ def self.cache_recursive(n, cache = {})
9
+ return n if n < 2
10
+ cache[n] ||= cache_recursive(n-1, cache) + cache_recursive(n-2, cache)
11
+ end
12
+
13
+ def self.cache_iterative(n)
14
+ cache = [0, 1]
15
+ 2.upto(n) { |i| cache[i] = cache[i-1] + cache[i-2] }
16
+ cache[n]
17
+ end
18
+
19
+ def self.dynamic(n)
20
+ a, b = 0, 1
21
+ (n-1).times { a, b = b, a+b }
22
+ b
23
+ end
24
+ end
@@ -0,0 +1,137 @@
1
+ require 'compsci'
2
+
3
+ module CompSci::Fit
4
+ #
5
+ # functions below originally copied from https://github.com/seattlrb/minitest
6
+ #
7
+
8
+ ##
9
+ # Enumerates over +enum+ mapping +block+ if given, returning the
10
+ # sum of the result. Eg:
11
+ #
12
+ # sigma([1, 2, 3]) # => 1 + 2 + 3 => 7
13
+ # sigma([1, 2, 3]) { |n| n ** 2 } # => 1 + 4 + 9 => 14
14
+
15
+ def self.sigma enum, &block
16
+ enum = enum.map(&block) if block
17
+ enum.inject { |sum, n| sum + n }
18
+ end
19
+
20
+ ##
21
+ # Takes an array of x/y pairs and calculates the general R^2 value to
22
+ # measure fit against a predictive function, which is the block supplied
23
+ # to error:
24
+ #
25
+ # e.g. error(xys) { |x| 5 + 2 * x }
26
+ #
27
+ # See: http://en.wikipedia.org/wiki/Coefficient_of_determination
28
+ #
29
+
30
+ def self.error xys, &blk
31
+ y_bar = sigma(xys) { |_, y| y } / xys.size.to_f
32
+ ss_tot = sigma(xys) { |_, y| (y - y_bar) ** 2 }
33
+ ss_res = sigma(xys) { |x, y| (yield(x) - y) ** 2 }
34
+
35
+ 1 - (ss_res / ss_tot)
36
+ end
37
+
38
+ ##
39
+ # Fits the functional form: a (+ 0x)
40
+ #
41
+ # Takes x and y values and returns [a, variance]
42
+ #
43
+
44
+ def self.constant xs, ys
45
+ # written by Rick
46
+ y_bar = sigma(ys) / ys.size.to_f
47
+ variance = sigma(ys) { |y| (y - y_bar) ** 2 }
48
+ [y_bar, variance]
49
+ end
50
+
51
+ ##
52
+ # To fit a functional form: y = a + b*ln(x).
53
+ #
54
+ # Takes x and y values and returns [a, b, r^2].
55
+ #
56
+ # See: http://mathworld.wolfram.com/LeastSquaresFittingLogarithmic.html
57
+
58
+ def self.logarithmic xs, ys
59
+ n = xs.size
60
+ xys = xs.zip(ys)
61
+ slnx2 = sigma(xys) { |x, _| Math.log(x) ** 2 }
62
+ slnx = sigma(xys) { |x, _| Math.log(x) }
63
+ sylnx = sigma(xys) { |x, y| y * Math.log(x) }
64
+ sy = sigma(xys) { |_, y| y }
65
+
66
+ c = n * slnx2 - slnx ** 2
67
+ b = ( n * sylnx - sy * slnx ) / c
68
+ a = (sy - b * slnx) / n
69
+
70
+ return a, b, self.error(xys) { |x| a + b * Math.log(x) }
71
+ end
72
+
73
+ ##
74
+ # Fits the functional form: a + bx.
75
+ #
76
+ # Takes x and y values and returns [a, b, r^2].
77
+ #
78
+ # See: http://mathworld.wolfram.com/LeastSquaresFitting.html
79
+
80
+ def self.linear xs, ys
81
+ n = xs.size
82
+ xys = xs.zip(ys)
83
+ sx = sigma xs
84
+ sy = sigma ys
85
+ sx2 = sigma(xs) { |x| x ** 2 }
86
+ sxy = sigma(xys) { |x, y| x * y }
87
+
88
+ c = n * sx2 - sx**2
89
+ a = (sy * sx2 - sx * sxy) / c
90
+ b = ( n * sxy - sx * sy ) / c
91
+
92
+ return a, b, self.error(xys) { |x| a + b * x }
93
+ end
94
+
95
+ ##
96
+ # To fit a functional form: y = ae^(bx).
97
+ #
98
+ # Takes x and y values and returns [a, b, r^2].
99
+ #
100
+ # See: http://mathworld.wolfram.com/LeastSquaresFittingExponential.html
101
+
102
+ def self.exponential xs, ys
103
+ n = xs.size
104
+ xys = xs.zip(ys)
105
+ sxlny = sigma(xys) { |x, y| x * Math.log(y) }
106
+ slny = sigma(xys) { |_, y| Math.log(y) }
107
+ sx2 = sigma(xys) { |x, _| x * x }
108
+ sx = sigma xs
109
+
110
+ c = n * sx2 - sx ** 2
111
+ a = (slny * sx2 - sx * sxlny) / c
112
+ b = ( n * sxlny - sx * slny ) / c
113
+
114
+ return Math.exp(a), b, self.error(xys) { |x| Math.exp(a + b * x) }
115
+ end
116
+
117
+ ##
118
+ # To fit a functional form: y = ax^b.
119
+ #
120
+ # Takes x and y values and returns [a, b, r^2].
121
+ #
122
+ # See: http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
123
+
124
+ def self.power xs, ys
125
+ n = xs.size
126
+ xys = xs.zip(ys)
127
+ slnxlny = sigma(xys) { |x, y| Math.log(x) * Math.log(y) }
128
+ slnx = sigma(xs) { |x | Math.log(x) }
129
+ slny = sigma(ys) { | y| Math.log(y) }
130
+ slnx2 = sigma(xs) { |x | Math.log(x) ** 2 }
131
+
132
+ b = (n * slnxlny - slnx * slny) / (n * slnx2 - slnx ** 2)
133
+ a = (slny - b * slnx) / n
134
+
135
+ return Math.exp(a), b, self.error(xys) { |x| (Math.exp(a) * (x ** b)) }
136
+ end
137
+ end
@@ -0,0 +1,100 @@
1
+ require 'compsci/tree'
2
+
3
+ # A Heap is a partially sorted, complete binary tree with the property:
4
+ # * Every node has a value larger (or smaller) than that of its children.
5
+ #
6
+ # This class implements a heap using a simple array for storage.
7
+ # Array index math is used to find:
8
+ # * The root node (idx 0)
9
+ # * The "bottom-most" leaf node (last idx)
10
+ # * Parent idx (idx-1 / 2)
11
+ # * Child idx (2*idx + 1, 2*idx + 2)
12
+ #
13
+ # Any Comparable may be used for node values.
14
+ # Initialize a heap with a cmp_val, either 1 for a MaxHeap or -1 for a MinHeap.
15
+ # The heap property is satisfied when a parent value equals a child value.
16
+ # Insertion (push) and removal (pop) are O(log n) where n is the heap size.
17
+ # Nodes are inserted at the end of the array, and sift_up is called to
18
+ # reestablish the heap property.
19
+ # Nodes are removed from the start of the array, and sift_down is called to
20
+ # reestablish the heap property.
21
+ # Sift_up and sift_down are O(log n) because they only have to check and swap
22
+ # nodes at each layer of the tree, and there are log n layers to the tree.
23
+ #
24
+ class CompSci::Heap < CompSci::CompleteBinaryTree
25
+ # defaults to a MaxHeap, with the largest node at the root
26
+ # specify a minheap with minheap: true or cmp_val: -1
27
+ #
28
+ def initialize(cmp_val: 1, minheap: false)
29
+ super()
30
+ cmp_val = -1 if minheap
31
+ case cmp_val
32
+ when -1, 1
33
+ @cmp_val = cmp_val
34
+ else
35
+ raise(ArgumentError, "unknown comparison value: #{cmp_val}")
36
+ end
37
+ end
38
+
39
+ # append to the array; sift_up
40
+ def push(node)
41
+ @store << node
42
+ self.sift_up(@store.size - 1)
43
+ end
44
+
45
+ # remove from the front of the array; move last node to root; sift_down
46
+ def pop
47
+ node = @store.shift
48
+ replacement = @store.pop
49
+ @store.unshift replacement if replacement
50
+ self.sift_down(0)
51
+ node
52
+ end
53
+
54
+ # return what pop would return (avoid sifting)
55
+ def peek
56
+ @store.first
57
+ end
58
+
59
+ # called recursively; idx represents the node suspected to violate the heap
60
+ def sift_up(idx)
61
+ return self if idx <= 0
62
+ pidx = self.class.parent_idx(idx)
63
+ if !self.heapish?(pidx, idx)
64
+ @store[idx], @store[pidx] = @store[pidx], @store[idx] # swap
65
+ self.sift_up(pidx)
66
+ end
67
+ self
68
+ end
69
+
70
+ # called recursively; idx represents the node suspected to violate the heap
71
+ def sift_down(idx)
72
+ return self if idx >= @store.size
73
+ lidx, ridx = self.class.children_idx(idx)
74
+ # take the child most likely to be a good parent
75
+ cidx = self.heapish?(lidx, ridx) ? lidx : ridx
76
+ if !self.heapish?(idx, cidx)
77
+ @store[idx], @store[cidx] = @store[cidx], @store[idx] # swap
78
+ self.sift_down(cidx)
79
+ end
80
+ self
81
+ end
82
+
83
+ # are values of parent and child (by index) in accordance with heap property?
84
+ def heapish?(pidx, cidx)
85
+ (@store[pidx] <=> @store[cidx]) != (@cmp_val * -1)
86
+ end
87
+
88
+ # not used internally; checks that every node satisfies the heap property
89
+ def heap?(idx: 0)
90
+ check_children = []
91
+ self.class.children_idx(idx).each { |cidx|
92
+ if cidx < @store.size
93
+ return false unless self.heapish?(idx, cidx)
94
+ check_children << cidx
95
+ end
96
+ }
97
+ check_children.each { |cidx| return false unless self.heap?(idx: cidx) }
98
+ true
99
+ end
100
+ end
@@ -0,0 +1,36 @@
1
+ require 'compsci'
2
+
3
+ module CompSci::Timer
4
+ # lifted from seattlerb/minitest
5
+ if defined? Process::CLOCK_MONOTONIC
6
+ def self.now
7
+ Process.clock_gettime Process::CLOCK_MONOTONIC
8
+ end
9
+ else
10
+ def self.now
11
+ Time.now
12
+ end
13
+ end
14
+
15
+ def self.since(t)
16
+ self.now - t
17
+ end
18
+
19
+ def self.elapsed(&work)
20
+ t = self.now
21
+ return yield, self.since(t)
22
+ end
23
+
24
+ def self.loop_average(count: 999, seconds: 1, &work)
25
+ i = 0
26
+ start = self.now
27
+ val = nil
28
+ loop {
29
+ val = yield
30
+ i += 1
31
+ break if i >= count
32
+ break if self.since(start) > seconds
33
+ }
34
+ return val, self.since(start) / i.to_f
35
+ end
36
+ end