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 CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 dgoldhirsch
1
+ Copyright (c) 2009 David C. Goldhirsch
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -1,7 +1,69 @@
1
1
  = cs
2
2
 
3
- Description goes here.
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 dgoldhirsch. See LICENSE for details.
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
- class CS
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
- private
9
+ ########################################## Fibonacci Algorithms
21
10
 
22
- # The class 'new' method is privatized, because users should not be
23
- # instantiating this class.
24
- def self.new
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 generating class, each of whose instances represents F(2 + k)
32
- # for some given k. To obtain the integer value of F(w), use the 'result' method, e.g.:
33
- # - f = FibonacciSecondOrHigher.new
34
- # - f.result # the integer 1 = F(2)
35
- # - f.advance
36
- # - f.result # the integer 2 = F(3)
37
- # - etc.
38
- class FibonacciSecondOrHigher
39
- attr_accessor :result
40
- attr_accessor :previous
41
- attr_accessor :second_previous # non-nil only for F(3) and higher
42
-
43
- # This is the proper way to create an instance
44
- # representing F(2 + k). Please use this instead of 'new'.
45
- # Return an instance representing F(2 + k) for given k.
46
- # E.g., if k is 0, the result is F(2); if k is 1, the
47
- # result is F(3); if k is 4 the result is F(6)
48
- def self.advanced_by k
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
- # Change internal state such that if the receiver currently
55
- # represents F(k) it will be advanced to represent F(k + 1).
56
- def advance
57
- self.second_previous = self.previous
58
- self.previous = self.result
59
- self.result += self.second_previous # i.e., second_previous + previous
60
- return self
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
- def to_s
64
- return second_previous.to_s + "," + previous.to_s + "," + "=>" + result.to_s
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
- # Instances represent F(2), unless/until they are advanced
70
- def initialize
71
- self.previous = 0 # F(<= 1)
72
- self.result = 1 # F(2)
73
- end
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
@@ -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 FibonacciTest < Test::Unit::TestCase
4
-
5
- # Basic test of CS::fibonacci. Verify correctness of F(1, ...)
6
- # as (0, 1, 1, 2, 3, 5, 8...).
7
- def test_basic
8
- assert_equal 0, CS::fibonacci(1)
9
- assert_equal 1, CS::fibonacci(2)
10
- assert_equal 1, CS::fibonacci(3)
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
- # The assertion in this test is meaningless. The point of running this test
18
- # is to be sure that Fibonacci numbers up to some large number can be computed
19
- # within a reasonable CPU/memory footprint. For example, if a recursive rather
20
- # than a linear algorithm were used, this test case would require 2**10,000 recursive
21
- # calls, and the test would probably run out of memory.
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
- x = CS::fibonacci(10000);
24
- assert x > 0
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
- # Seemingly innocent, recursive algorithm that computes the nth Fibonacci number.
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
@@ -3,7 +3,6 @@ require 'test/unit'
3
3
 
4
4
  $LOAD_PATH.unshift(File.dirname(__FILE__))
5
5
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
- require 'cs'
7
6
 
8
7
  class Test::Unit::TestCase
9
8
  end
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.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-06 00:00:00 -07:00
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