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