dgoldhirsch-cs 0.0.0 → 0.0.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.
- data/LICENSE +1 -1
- data/README.rdoc +64 -2
- data/lib/cs.rb +86 -57
- data/lib/cs_fibonacci.rb +21 -0
- data/lib/cs_matrix.rb +23 -0
- data/test/cs_benchmark_test.rb +22 -0
- data/test/cs_mixin_test.rb +24 -0
- data/test/cs_test.rb +48 -23
- data/test/test_helper.rb +0 -1
- metadata +6 -2
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -1,7 +1,69 @@
|
|
1
1
|
= cs
|
2
2
|
|
3
|
-
|
3
|
+
Computer Science Module for Ruby
|
4
|
+
|
5
|
+
This is an ambitious gem in which we hope to provide useful computer science algorithms.
|
6
|
+
The first version is very small, offering two algorithms to compute Fibonacci numbers.
|
7
|
+
We did this as an exercise in writing good Ruby code, as well as learning how to provide
|
8
|
+
real, useful libraries as gems.
|
9
|
+
|
10
|
+
Here are the two Fibonacci algorithms currently supported:
|
11
|
+
|
12
|
+
[Matrix exponentiation] This is the fastest way we have found to compute Fibonacci numbers.
|
13
|
+
It makes use of the fact that if M = Matrix[[0, 1], [1, 1]], then the lower right element of
|
14
|
+
M**k contains the (k + 1)'th Fibonacci number. This is very fast if the Matrix.** operation
|
15
|
+
is optimized to use successive squaring rather than individual multiplications. Ruby's
|
16
|
+
implementation of Matrix.** does exactly this, fortunately.
|
17
|
+
|
18
|
+
[While loop] This is a simple loop that computes F(n) by adding F(0) + F(1) + ... + F(n - 1).
|
19
|
+
For n < 5000, this seems to perform about as well as the matrix exponentiation. But, thereafter
|
20
|
+
it is MUCH slower (at least, in the benchmarks we performed).
|
21
|
+
|
22
|
+
= Using the library
|
23
|
+
|
24
|
+
The algorithms are available in two forms. The basic form is a module method:
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'cs'
|
28
|
+
y = CS::fibonacci(6) # returns F(6) which is 8 = 5 + 3, using default algorithm
|
29
|
+
|
30
|
+
An optional parameter selects the algorithm to be used. By default, the matrix
|
31
|
+
exponentiation algorithm is used. However, you can choose one or the other as follows:
|
32
|
+
|
33
|
+
y_more_slowly = CS::fibonacci(6, CS::FIBONACCI_WHILE_LOOP)
|
34
|
+
|
35
|
+
As a convenience, the method is also available as an instance mixin for the class Integer:
|
36
|
+
|
37
|
+
require 'rubygems'
|
38
|
+
require 'cs_fibonacci'
|
39
|
+
y = 6.fibonacci # use default algorithm
|
40
|
+
y_slower = 6.fibonacci(CS::FIBONACCI_WHILE_LOOP)
|
41
|
+
|
42
|
+
== Obtaining the gem from github
|
43
|
+
|
44
|
+
gem sources -a http://gems.github.com
|
45
|
+
gem install dgoldhirsch-cs
|
46
|
+
|
47
|
+
== Extensions to Standard Library
|
48
|
+
|
49
|
+
While coding these algorithms, we found it useful to code an extension to the
|
50
|
+
standard Ruby Matrix class, to obtain the lower right element of a matrix.
|
51
|
+
It may be that this is already available in one of the existing Ruby extension
|
52
|
+
gems--but, for now, we included it in the CS module on the chance that
|
53
|
+
someone else might want to make use of it:
|
54
|
+
|
55
|
+
require 'rubygems'
|
56
|
+
require 'cs'
|
57
|
+
y = CS::lower_right(Matrix[[1, 2], [3, 4]]) # y is 4
|
58
|
+
|
59
|
+
== This is Still Half-Baked
|
60
|
+
|
61
|
+
Obviously, this is a beginner's gem which is in an infant state. Comments and
|
62
|
+
suggestions are very welcome. As a companion to the gem, we'll try to create
|
63
|
+
a Web site that demonstrates the algorithms and which solicits additional algorithm
|
64
|
+
contributions and suggestions. We'll update this document when/if we are able
|
65
|
+
to finish that.
|
4
66
|
|
5
67
|
== Copyright
|
6
68
|
|
7
|
-
Copyright (c) 2009
|
69
|
+
Copyright (c) 2009 David C. Goldhirsch. See LICENSE for details.
|
data/lib/cs.rb
CHANGED
@@ -1,74 +1,103 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'matrix'
|
2
3
|
|
3
4
|
# Title:: Computer Science Library for Ruby
|
4
5
|
# Author:: David C. Goldhirsch
|
5
6
|
# Copyright (c) 2009, David C. Goldhirsch
|
6
|
-
|
7
|
-
# Fibonacci number generator.
|
8
|
-
# Return integer value of F(n) for any integer n >= 1,
|
9
|
-
# where F(n) is defined as F(n - 1) + F(n - 2) for n > 2, F(2) = 1
|
10
|
-
# and F(1) = 0.
|
11
|
-
#
|
12
|
-
# E.g., the sequence (F(1), ..., F(7)) is (0, 1, 1, 2, 3, 5, 8).
|
13
|
-
#
|
14
|
-
# Values of n less than 1 are equivalent to n = 1.
|
15
|
-
def self.fibonacci n
|
16
|
-
return 0 if n <= 1
|
17
|
-
return (FibonacciSecondOrHigher.advanced_by n - 2).result
|
18
|
-
end
|
7
|
+
module CS
|
19
8
|
|
20
|
-
|
9
|
+
########################################## Fibonacci Algorithms
|
21
10
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
super.new
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
11
|
+
MATRIX = :matrix
|
12
|
+
ADDITION = :addition
|
13
|
+
Fibonacci_algorithms = [MATRIX, ADDITION]
|
30
14
|
|
31
|
-
# Fibonacci
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# -
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
return (1..k).inject(FibonacciSecondOrHigher.new) do |f, i|
|
50
|
-
f.advance
|
51
|
-
end
|
15
|
+
# Generalized Fibonacci generator.
|
16
|
+
# Return the integer value of F(n) for any integer n, where
|
17
|
+
# F(0) = 0
|
18
|
+
# F(1) = 1
|
19
|
+
# F(k > 1) = F(k - 1) + F(k - 2)
|
20
|
+
# Starting with F(0), the first few Fibonacci values are 0, 1, 1, 2, 3, 5, 8, ...
|
21
|
+
#
|
22
|
+
# Usage: CS::fibonacci(anInteger, aSymbol = CS::MATRIX)
|
23
|
+
# in which anInteger is any integer (but anything less than 0 will be
|
24
|
+
# interpreted as 0), and aSymbol optionally specifies the algorithm to be
|
25
|
+
# used. See this module's Fibonacc_algorithms constant for the possible
|
26
|
+
# algorithm names. By default, the fastest known algorithm will be used.
|
27
|
+
def self.fibonacci(n, algorithm = MATRIX)
|
28
|
+
# The following seems to be the fastest algorithm, perhaps because
|
29
|
+
# Ruby's Matrix.** operation is smart enough to use squares of squares
|
30
|
+
# to minimize the number of multiplications.
|
31
|
+
return matrix_fibonacci(n) if algorithm == MATRIX
|
32
|
+
return additive_fibonacci(n) if algorithm == ADDITION
|
52
33
|
end
|
53
34
|
|
54
|
-
#
|
55
|
-
#
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
35
|
+
# Matrix utility to return
|
36
|
+
# the lower, right-hand element of a given matrix.
|
37
|
+
def self.lower_right matrix
|
38
|
+
return nil if matrix.row_size == 0
|
39
|
+
return matrix[matrix.row_size - 1, matrix.column_size - 1]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Matrix exponentiation algorithm to compute Fibonacci numbers.
|
43
|
+
# Let M be Matrix [[0, 1], [1, 1]]. Then, the lower right element of M**k is
|
44
|
+
# F(k + 1). In other words, the lower right element of M is F(2) which is 1, and the
|
45
|
+
# lower right element of M**2 is F(3) which is 2, and the lower right element
|
46
|
+
# of M**3 is F(4) which is 3, etc.
|
47
|
+
#
|
48
|
+
# This is a good way to compute F(n) because the Ruby implementation of Matrix.**
|
49
|
+
# uses an O(log n) optimized algorithm (*). Computing M**(n-1) is actually
|
50
|
+
# faster (**) than using a simple while/for loop to compute F(0) + F(1) + ... + F(n-1).
|
51
|
+
#
|
52
|
+
# We found this algorithm in <I>Introduction To Algorithms
|
53
|
+
# (Second Edition)</I>, by Cormen, Leiserson, Rivest, and Stein, MIT Press
|
54
|
+
# (http://mitpress.mit.edu), in exercise 3-31 on pages 902, 903.
|
55
|
+
#
|
56
|
+
# (*) Ruby's Matrix.**(k) works by computing partial = ((m**2)**2)... as far as possible,
|
57
|
+
# and then multiplying partial by M**(the remaining number of times). E.g., to compute
|
58
|
+
# M**19, compute partial = ((M**2)**2) = M**16, and then compute partial*(M**3) = M**19.
|
59
|
+
# That's only 3 matrix multiplications of M to compute M*19.
|
60
|
+
#
|
61
|
+
# (**) "Faster" means on the workstations we tried, the matrix algorithm takes less total time
|
62
|
+
# (see Ruby's Benchmark::bmbm) than a simple, additive loop. But the space complexity
|
63
|
+
# may well be larger for the matrix algorithm, which in turn may affect the likelihood
|
64
|
+
# of garbage collection in large cases. Therefore, as with almost everything else,
|
65
|
+
# a different algorithm may be better suited to any particular environment or situation.
|
66
|
+
# This is why we provide the optional, alternative algorithms.
|
67
|
+
#
|
68
|
+
def self.matrix_fibonacci(n)
|
69
|
+
return 0 if n <= 0 # F(0)
|
70
|
+
return 1 if n == 1 # F(1)
|
71
|
+
# To get F(n >= 2), compute M**(n - 1) and extract the lower right element.
|
72
|
+
return CS::lower_right(M**(n - 1))
|
61
73
|
end
|
62
74
|
|
63
|
-
|
64
|
-
|
75
|
+
# This is a simple, additive loop to compute F(n) by computing
|
76
|
+
# F(0) + F(1) + ... + F(n-1). Our spotty benchmark/profiling suggests that
|
77
|
+
# this is always worse in CPU time and general response time than
|
78
|
+
# the linear_matrix_fibonacci algorithm. (And, surprisingly, this seems to be true even
|
79
|
+
# for very low values of n.) We provide it for purposes of information, and as
|
80
|
+
# a point of comparison for performance.
|
81
|
+
def self.additive_fibonacci(n)
|
82
|
+
return 0 if n <= 0
|
83
|
+
b = 0 # F(0)
|
84
|
+
result = 1 # F(1)
|
85
|
+
(n - 1).times do
|
86
|
+
a = b
|
87
|
+
b = result
|
88
|
+
result += a # i.e., result = a + b
|
89
|
+
end
|
90
|
+
return result
|
65
91
|
end
|
66
92
|
|
67
93
|
private
|
68
94
|
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
95
|
+
# To understand why this matrix is useful for Fibonacci numbers, remember
|
96
|
+
# that the definition of Matrix.** for any Matrix[[a, b], [c, d]] is
|
97
|
+
# is [[a*a + b*c, a*b + b*d], [c*a + d*b, c*b + d*d]]. In other words, the
|
98
|
+
# lower right element is computing F(k - 2) + F(k - 1) every time M is multiplied
|
99
|
+
# by itself (it is perhaps easier to understand this by computing M**2, 3, etc, and
|
100
|
+
# watching the result march up the sequence of Fibonacci numbers).
|
101
|
+
M = Matrix[[0, 1], [1,1]]
|
102
|
+
|
74
103
|
end
|
data/lib/cs_fibonacci.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'cs.rb'
|
3
|
+
|
4
|
+
# Integer mixin providing Integer.fibonacci() instance method
|
5
|
+
# that delegates to CS::fibonacci. Type the following:
|
6
|
+
#
|
7
|
+
# <code>
|
8
|
+
# require 'cs' # get the gem
|
9
|
+
# require 'cs_fibonacci.rb' # get the Integer mixin
|
10
|
+
# </code>
|
11
|
+
#
|
12
|
+
# And now you can do this:
|
13
|
+
#
|
14
|
+
# <code>
|
15
|
+
# puts 7.fibonacci() # prints 8
|
16
|
+
# </code>
|
17
|
+
class Integer
|
18
|
+
def fibonacci algorithm = CS::MATRIX
|
19
|
+
return CS::fibonacci(self, algorithm)
|
20
|
+
end
|
21
|
+
end
|
data/lib/cs_matrix.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'cs.rb'
|
3
|
+
require 'matrix'
|
4
|
+
|
5
|
+
# Useful Class Matrix mixins.
|
6
|
+
#
|
7
|
+
# <code>
|
8
|
+
# require 'cs' # get the CS gem
|
9
|
+
# require 'cs_matrix.rb' # get the CS Matrix mixin
|
10
|
+
# </code>
|
11
|
+
#
|
12
|
+
# And now you can do this:
|
13
|
+
#
|
14
|
+
# <code>
|
15
|
+
# puts 7.fibonacci() # prints 8
|
16
|
+
# </code>
|
17
|
+
class Matrix
|
18
|
+
# Useful for dealing with matrices.
|
19
|
+
# Return the lower, right-hand element of a given matrix.
|
20
|
+
def lower_right
|
21
|
+
return CS::lower_right(self)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'cs.rb')
|
3
|
+
require "benchmark"
|
4
|
+
require "ruby-prof"
|
5
|
+
|
6
|
+
class CS_benchmark < Test::Unit::TestCase
|
7
|
+
|
8
|
+
N = 50000 # large enough to show a difference
|
9
|
+
|
10
|
+
# This isn't a test per se, but rather a very simple performance profiler.
|
11
|
+
def test_performance
|
12
|
+
y = Benchmark.bmbm do |x|
|
13
|
+
x.report("matrix_fibonacci") { CS::matrix_fibonacci(N) }
|
14
|
+
x.report("additive_fibonacci") { CS::additive_fibonacci(N) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_rubyprof
|
19
|
+
RubyProf::GraphPrinter.new(RubyProf.profile {CS::matrix_fibonacci(N)}).print(STDOUT, 0)
|
20
|
+
RubyProf::GraphPrinter.new(RubyProf.profile {CS::additive_fibonacci(N)}).print(STDOUT, 0)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'cs_fibonacci.rb')
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'cs_matrix.rb')
|
4
|
+
|
5
|
+
|
6
|
+
# Test mixin wrappers for the CS library. This is just
|
7
|
+
# for the mixin wrappers--the hard core tests for the
|
8
|
+
# CS library itself should be written against the CS module
|
9
|
+
# directly.
|
10
|
+
class CsMixinTest < Test::Unit::TestCase
|
11
|
+
|
12
|
+
def test_fibonacci_mixin
|
13
|
+
assert_equal 13, 7.fibonacci # ensure works with Fixnum
|
14
|
+
assert_equal 13, 7.fibonacci(CS::ADDITION) # ensure optional algorithm works
|
15
|
+
# Use reflection to ensure that the method has been mixed
|
16
|
+
# into Bignum as well as Fixnum.
|
17
|
+
y = 0xfffffffffe # Bignum on any 32-bit system
|
18
|
+
assert y.method(:fibonacci)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_matrix_mixin
|
22
|
+
assert_equal 6, Matrix[[1,2,3], [4,5,6]].lower_right
|
23
|
+
end
|
24
|
+
end
|
data/test/cs_test.rb
CHANGED
@@ -1,27 +1,49 @@
|
|
1
1
|
require "test_helper"
|
2
|
+
require "matrix"
|
3
|
+
require "timeout"
|
2
4
|
|
3
|
-
class
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
assert_equal
|
11
|
-
assert_equal 2, CS::fibonacci(4)
|
12
|
-
assert_equal 3, CS::fibonacci(5)
|
13
|
-
assert_equal 5, CS::fibonacci(6)
|
14
|
-
assert_equal 8, CS::fibonacci(7)
|
5
|
+
class CsFibonacciTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_matrix
|
8
|
+
(0..F.size - 1).each { | i | assert_equal F[i], CS::matrix_fibonacci(i)}
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_addititive
|
12
|
+
(0..F.size - 1).each { | i | assert_equal F[i], CS::additive_fibonacci(i)}
|
15
13
|
end
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
def test_algorithm_parameter
|
16
|
+
assert_equal 8, CS::fibonacci(6, CS::MATRIX)
|
17
|
+
assert_equal 8, CS::fibonacci(6, CS::ADDITION)
|
18
|
+
assert_nil CS::fibonacci(3, :foobar)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Test for gross performance problems, using a time-out thread.
|
22
|
+
# It's possible--at least theoretically--for this test
|
23
|
+
# to fail because of system issues rather than the fibonacci
|
24
|
+
# generator. So, if it fails, try it again and/or take its
|
25
|
+
# failure with a grain of salt.
|
22
26
|
def test_raw_performance
|
23
|
-
|
24
|
-
|
27
|
+
assert_nothing_raised do
|
28
|
+
# 20 seconds would seem to be far more than is necessary for this...
|
29
|
+
Timeout::timeout(20) {CS::matrix_fibonacci(10000)}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Test our understanding of the Ruby matrix module
|
34
|
+
def test_matrix
|
35
|
+
m = Matrix[[0, 1],[1, 1]]
|
36
|
+
m2 = m*m
|
37
|
+
m3 = m2*m
|
38
|
+
m4 = m3*m
|
39
|
+
m2squared = m2**2
|
40
|
+
assert_equal 1, CS::lower_right(m)
|
41
|
+
assert_equal 2, CS::lower_right(m2)
|
42
|
+
assert_equal 3, CS::lower_right(m3)
|
43
|
+
assert_equal 5, CS::lower_right(m4)
|
44
|
+
assert_equal m4, m2squared
|
45
|
+
e = Matrix[]
|
46
|
+
assert_nil CS::lower_right(e)
|
25
47
|
end
|
26
48
|
|
27
49
|
#The following function will take a massive amount of CPU time and an impossible amount of memory,
|
@@ -41,15 +63,18 @@ class FibonacciTest < Test::Unit::TestCase
|
|
41
63
|
end
|
42
64
|
|
43
65
|
private
|
44
|
-
|
45
|
-
#
|
66
|
+
|
67
|
+
# Fhe first few Fibonacci numbers, starting with F(0), for assertion comparisons
|
68
|
+
F = [0, 1, 1, 2, 3, 5, 8]
|
69
|
+
|
70
|
+
# Seemingly innocent, recursive algorithm that computes the nth Fibonacci number.
|
46
71
|
# Requires 2**n recursive calls, requiring an obscene amount of memory and CPU time.
|
47
72
|
# Try it for n = 1000, and then twiddle your thumbs while waiting for the "out of memory"
|
48
73
|
# exception...
|
49
74
|
def recursive_fibonacci(n)
|
50
|
-
if(n == 1)
|
75
|
+
if (n == 1)
|
51
76
|
return 0
|
52
|
-
elsif(n == 2 || n == 3)
|
77
|
+
elsif (n == 2 || n == 3)
|
53
78
|
return 1
|
54
79
|
end
|
55
80
|
return recursive_fibonacci(n - 2) + recursive_fibonacci(n - 1)
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dgoldhirsch-cs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- dgoldhirsch
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-09 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -24,6 +24,8 @@ extra_rdoc_files:
|
|
24
24
|
- README.rdoc
|
25
25
|
files:
|
26
26
|
- lib/cs.rb
|
27
|
+
- lib/cs_fibonacci.rb
|
28
|
+
- lib/cs_matrix.rb
|
27
29
|
- LICENSE
|
28
30
|
- README.rdoc
|
29
31
|
has_rdoc: true
|
@@ -53,5 +55,7 @@ signing_key:
|
|
53
55
|
specification_version: 2
|
54
56
|
summary: Computer Science Library for Ruby
|
55
57
|
test_files:
|
58
|
+
- test/cs_benchmark_test.rb
|
59
|
+
- test/cs_mixin_test.rb
|
56
60
|
- test/cs_test.rb
|
57
61
|
- test/test_helper.rb
|