compsci 0.0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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