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 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