raganwald / homoiconic
- Source
- Commits
- Network (7)
- Issues (0)
- Downloads (1)
- Wiki (1)
- Graphs
-
Branch:
master
homoiconic / 2008-12-12 / fibonacci.md
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 1 | A program to compute the nth Fibonacci number | |
| 2 | === | ||||
| 3 | |||||
| a4bfc714 » | raganwald | 2009-09-23 | 4 | Since [I'm looking for a job](http://reginald.braythwayt.com/RegBraithwaiteGH0909_en_US.pdf "Reginald Braithwaite's Resume") and people often like to ask for a [fizzbuzz](http://weblog.raganwald.com/2007/01/dont-overthink-fizzbuzz.html "Don't Overthink FizzBuzz") program to weed out the folks who can't string together a few lines of code, I thought I'd write up a program to compute the nth Fibonacci number. There's an intriguing bit of matrix math involved, so I learned something while implementing it. | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 5 | ||
| 899d451c » | Reginald Braithwaite | 2008-12-13 | 6 | Recently I was having lunch with [some of Toronto's most interesting Ruby developers](http://unspace.ca), and the subject of interview questions came up. Specifically, writing a program to compute the nth Fibonacci number. Unsurprisingly, we agreed it might be useful as a screener for weeding out the people who were completely delusional about their prospects as a professional programmer. You know, the type of person who stares blankly at the screen and has no idea where to start, even when told that all we wanted was a program that correctly prints an answer. No tests, no specs, no shouldas, no passengers, no CSS, just prove you actually can write something, anything. | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 7 | ||
| 1a3e1d75 » | raganwald | 2008-12-12 | 8 | We also agreed that any [Guess the answer I'm thinking of](http://www.nomachetejuggling.com/2008/12/11/my-least-favorite-interview-question/ "My Least Favorite Interview Question » Absolutely No Machete Juggling") problem is terrible, including Fibonacci. Should it iterate? Recurse? Memoize? Is it overkill to use advanced math to be really fast? Or will you get tossed out of the interview for writing a naive recursive solution? Unless the interviewer is very specific that they just want you to prove that you actually have written ten or more lines of working Ruby code in your life, there could be any number of reasons to disqualify any solution. | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 9 | ||
| 10 | But we batted around a few ideas for how to gild the lily, and since I have just found myself contemplating the prospect of being asked to write a program to compute the nth Fibonacci number, I thought I'd get some practice and write one in advance. Since I'm doing it for myself, I'm going to optimize for maximum personal growth. | ||||
| 11 | |||||
| 12 | Here we go... | ||||
| 13 | |||||
| 14 | Enter The Matrix | ||||
| 15 | --- | ||||
| 16 | |||||
| e6c44800 » | Reginald Braithwaite | 2008-12-13 | 17 | One problem with calculating a Finonacci number is that naive algorithms require _n_ addition operations. There are some interesting things we can do to improve on this. One way is to transform _n_ additions into raising something to the power of *n*. This turns _n_ additions into _n_ multiplications. That seems retrograde, but hold on to your disbelief. | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 18 | ||
| e6c44800 » | Reginald Braithwaite | 2008-12-13 | 19 | This is actually nice, because there is a trick about raising something to a power that we can exploit. But first things first. As explained in [Sum of Fibonacci numbers?](http://expertvoices.nsdl.org/cornell-cs322/2008/03/25/sum-of-fibonacci-numbers/), we can express the Fibonacci number `F(n)` using a 2x2 matrix: | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 20 | ||
| 21 | [ 1 1 ] n [ F(n+1) F(n) ] | ||||
| 22 | [ 1 0 ] = [ F(n) F(n-1) ] | ||||
| 23 | |||||
| a94dd2da » | raganwald | 2008-12-14 | 24 | [Multiplying two matrices](http://www.maths.surrey.ac.uk/explore/emmaspages/option1.html "Matrices and Determinants") is a little interesting if you have never seen it before: | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 25 | ||
| a94dd2da » | raganwald | 2008-12-14 | 26 | [ a b ] [ e f ] [ ae + bg af + bh ] | |
| 27 | [ c d ] times [ g h ] = [ ce + dg cf + dh ] | ||||
| 28 | |||||
| 29 | Our matrices always have diagonal symmetry, so we can simplify the calculation because _c_ is always equal to _b_: | ||||
| 30 | |||||
| 31 | [ a b ] [ d e ] [ ad + be ae + bf ] | ||||
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 32 | [ b c ] times [ e f ] = [ bd + ce be + cf ] | |
| 33 | |||||
| ee1fac1f » | raganwald | 2008-12-14 | 34 | Now we are given that we are multiplying two matrices with diagonal symmetry. Will the result have diagonal symmetry? In other words, will `ae + bf` always be equal to `bd + ce`? Remember that `a = b + c` at all times and `d = e + f` provided that each is a power of `[[1,1], [1,0]]`. Therefore: | |
| 35 | |||||
| 36 | ae + bf = (b + c)e + bf | ||||
| 37 | = be + ce + bf | ||||
| 38 | bd + ce = b(e + f) + ce | ||||
| 39 | = be + bf + ce | ||||
| 40 | |||||
| 41 | That simplifies things for us, we can say: | ||||
| 42 | |||||
| 33808d75 » | raganwald | 2008-12-14 | 43 | [ a b ] [ d e ] [ ad + be ae + bf ] | |
| 44 | [ b c ] times [ e f ] = [ ae + bf be + cf ] | ||||
| ee1fac1f » | raganwald | 2008-12-14 | 45 | ||
| 46 | And thus, we can always work with three elements instead of four. Let's express this as operations on arrays: | ||||
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 47 | ||
| a94dd2da » | raganwald | 2008-12-14 | 48 | [a, b, c] times [d, e, f] = [ad + be, ae + bf, be + cf] | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 49 | ||
| ee1fac1f » | raganwald | 2008-12-14 | 50 | Which we can code in Ruby: | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 51 | ||
| 52 | def times(*ems) | ||||
| 53 | ems.inject do |product, matrix| | ||||
| 54 | a,b,c = product; d,e,f = matrix | ||||
| a94dd2da » | raganwald | 2008-12-14 | 55 | [a*d + b*e, a*e + b*f, b*e + c*f] | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 56 | end | |
| 57 | end | ||||
| 58 | |||||
| 59 | times([1,1,0]) # => [1, 1, 0] | ||||
| 60 | times([1,1,0], [1,1,0]) # => [2, 1, 1] | ||||
| 61 | times([1,1,0], [1,1,0], [1,1,0]) # => [3, 2, 1] | ||||
| 62 | times([1,1,0], [1,1,0], [1,1,0], [1,1,0]) # => [5, 3, 2] | ||||
| 63 | times([1,1,0], [1,1,0], [1,1,0], [1,1,0], [1,1,0]) # => [8, 5, 3] | ||||
| 64 | |||||
| 65 | Very interesting. We could write out a naive implementation that constructs a long array of copies of `[1,1,0]` and then calls `times`: | ||||
| 66 | |||||
| 67 | def naive_power(m, n) | ||||
| 68 | times(*(1..n).map { |n| m }) | ||||
| 69 | end | ||||
| 70 | |||||
| 71 | naive_power([1,1,0], 1) # => [1, 1, 0] | ||||
| 72 | naive_power([1,1,0], 2) # => [2, 1, 1] | ||||
| 73 | naive_power([1,1,0], 3) # => [3, 2, 1] | ||||
| 74 | naive_power([1,1,0], 4) # => [5, 3, 2] | ||||
| 75 | naive_power([1,1,0], 5) # => [8, 5, 3] | ||||
| 76 | |||||
| 49ae2542 » | Reginald Braithwaite | 2008-12-12 | 77 | Now let's make an observation: instead of accumulating a product by iterating over the list, let's [Divide and Conquer](http://www.cs.berkeley.edu/~vazirani/algorithms/chap2.pdf). Let's take the easy case: Don't you agree that `times([1,1,0], [1,1,0], [1,1,0], [1,1,0])` is equal to `times(times([1,1,0], [1,1,0]), times([1,1,0], [1,1,0]))`? And that this saves us an operation, since `times([1,1,0], [1,1,0], [1,1,0], [1,1,0])` is implemented as: | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 78 | ||
| 79 | times([1,1,0], | ||||
| 80 | times([1,1,0], | ||||
| 81 | times([1,1,0],[1,1,0])) | ||||
| 82 | |||||
| 83 | Whereas `times(times([1,1,0], [1,1,0]), times([1,1,0], [1,1,0]))` can be implemented as: | ||||
| 84 | |||||
| 85 | begin | ||||
| 86 | double = times([1,1,0], [1,1,0]) | ||||
| 87 | times(double, double) | ||||
| 88 | end | ||||
| 89 | |||||
| 90 | This only requires two operations rather than three. Furthermore, it is recursive. `naive_power([1,1,0], 8)` requires seven operations. However, it can be formulated as: | ||||
| 91 | |||||
| 92 | begin | ||||
| 93 | quadruple = begin | ||||
| 94 | double = times([1,1,0], [1,1,0]) | ||||
| 95 | times(double, double) | ||||
| 96 | end | ||||
| 97 | times(quadruple, quadruple) | ||||
| 98 | end | ||||
| 99 | |||||
| 100 | Now we only need three operations compared to seven. Of course, we left out how to deal with odd numbers. Fixing that also fixes how to deal with even numbers that aren't neat powers of two: | ||||
| 101 | |||||
| 102 | def power(m, n) | ||||
| 103 | if n == 1 | ||||
| 104 | m | ||||
| 105 | else | ||||
| 106 | halves = power(m, n / 2) | ||||
| 107 | if n % 2 == 0 | ||||
| 108 | times(halves, halves) | ||||
| 109 | else | ||||
| 110 | times(halves, halves, m) | ||||
| 111 | end | ||||
| 112 | end | ||||
| 113 | end | ||||
| 114 | |||||
| 115 | power([1,1,0], 1) # => [1, 1, 0] | ||||
| 116 | power([1,1,0], 2) # => [2, 1, 1] | ||||
| 117 | power([1,1,0], 3) # => [3, 2, 1] | ||||
| 118 | power([1,1,0], 4) # => [5, 3, 2] | ||||
| 119 | power([1,1,0], 5) # => [8, 5, 3] | ||||
| 120 | |||||
| 121 | Now we can write our complete fibonacci function: | ||||
| 122 | |||||
| 123 | def fib(n) | ||||
| d85aab9c » | raganwald | 2008-12-12 | 124 | return n if n < 2 | |
| 125 | power([1,1,0], n - 1).first | ||||
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 126 | end | |
| 127 | |||||
| 128 | And dress things up in idiomatic Ruby using the anonymous module pattern: | ||||
| 129 | |||||
| 130 | class Integer | ||||
| 131 | |||||
| 132 | include(Module.new do | ||||
| 133 | |||||
| 134 | times = lambda do |*ems| | ||||
| 135 | ems.inject do |product, matrix| | ||||
| 136 | a,b,c = product; d,e,f = matrix | ||||
| a94dd2da » | raganwald | 2008-12-14 | 137 | [a*d + b*e, a*e + b*f, b*e + c*f] | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 138 | end | |
| 139 | end | ||||
| 140 | |||||
| 141 | power = lambda do |m, n| | ||||
| 142 | if n == 1 | ||||
| 143 | m | ||||
| 144 | else | ||||
| 145 | halves = power.call(m, n / 2) | ||||
| 146 | if n % 2 == 0 | ||||
| 147 | times.call(halves, halves) | ||||
| 148 | else | ||||
| 149 | times.call(halves, halves, m) | ||||
| 150 | end | ||||
| 151 | end | ||||
| 152 | end | ||||
| 153 | |||||
| 154 | define_method :matrix_fib do | ||||
| 155 | return self if self < 2 | ||||
| 156 | power.call([1,1,0], self - 1).first | ||||
| 157 | end | ||||
| 158 | |||||
| 159 | end) | ||||
| 160 | |||||
| 161 | end | ||||
| 162 | |||||
| 49ae2542 » | Reginald Braithwaite | 2008-12-12 | 163 | (0..20).map { |n| n.matrix_fib } | |
| 164 | # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765] | ||||
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 165 | ||
| 166 | We're done! | ||||
| 167 | |||||
| 94aa33b7 » | raganwald | 2009-02-02 | 168 | p.s. No we're not done: [Another program to compute the nth Fibonacci number](http://github.com/raganwald/homoiconic/tree/master/2008-12-17/another_fibonacci.md#readme). | |
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 169 | ||
| 6c25f36b » | raganwald | 2008-12-17 | 170 | p.p.s. No, this isn't [the fastest implementation](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/194815 "Fast Fibonacci method") by far. But it beats the pants off of a naive iterative implementation. See [fibonacci.rb](http:fibonacci.rb) for details. | |
| c017b61d » | Reginald Braithwaite | 2008-12-13 | 171 | ||
| 6c25f36b » | raganwald | 2008-12-17 | 172 | --- | |
| 173 | |||||
| 174 | Christoph Temmel [forked homoiconic](http://github.com/kar8nga/homoiconic/tree "kar8nga's homoiconic at master — GitHub") and added these observations: | ||||
| 175 | |||||
| 176 | *Another option would be to decompose * | ||||
| 88cb589b » | Christoph Temmel | 2008-12-13 | 177 | ||
| 178 | A = [ 1 1 ] | ||||
| 179 | [ 1 0 ] | ||||
| 180 | |||||
| 6c25f36b » | raganwald | 2008-12-17 | 181 | *into along its eigenspace (eigenvalues \lambda\_{1,2}=-\frac{1\pm\sqrt{5}}{2} = -\frac{1}{2}\mp\frac{\sqrt{5}{2}}) to get* | |
| 88cb589b » | Christoph Temmel | 2008-12-13 | 182 | ||
| 72b150a8 » | Christoph Temmel | 2008-12-13 | 183 | A = Q^t D | |
| 88cb589b » | Christoph Temmel | 2008-12-13 | 184 | ||
| 6c25f36b » | raganwald | 2008-12-17 | 185 | *and* | |
| 72b150a8 » | Christoph Temmel | 2008-12-13 | 186 | ||
| 187 | D = [ \lambda_1 0 ] | ||||
| 188 | [ 0 \lambda_2 ] | ||||
| 189 | |||||
| 6c25f36b » | raganwald | 2008-12-17 | 190 | *where Q is the orthonormal matrix of the normated eigenvectors with QQ^t = I and D is the above diagonal matrix with the eigenvalues \lambda\_{1,2} (see also [Wikipedia](http://en.wikipedia.org/wiki/Symmetric_matrix#Properties)). Now it's easy to take powers of A* | |
| 88cb589b » | Christoph Temmel | 2008-12-13 | 191 | ||
| 192 | A^n = Q^t D^n D | ||||
| 193 | |||||
| 6c25f36b » | raganwald | 2008-12-17 | 194 | *The only difficulty is, that in order to avoid floating point arithmetic one would have to do the powers in the field \mathbb{Q}[\sqrt{5}] - this would ask for a custom datatype with overloading of addition and multiplication (the part needed for this exercise). * | |
| 88cb589b » | Christoph Temmel | 2008-12-13 | 195 | ||
| 196 | |||||
| 197 | ---- | ||||
| 0e6f843e » | Reginald Braithwaite | 2008-12-12 | 198 | ||
| 199 | Subscribe to [new posts and daily links](http://feeds.feedburner.com/raganwald "raganwald's rss feed"): <a href="http://feeds.feedburner.com/raganwald"><img src="http://feeds.feedburner.com/~fc/raganwald?bg=&fg=&anim=" height="26" width="88" style="border:0" alt="" align="top"/></a> | ||||
| 200 | |||||
| a4bfc714 » | raganwald | 2009-09-23 | 201 | Reg Braithwaite: [Home Page](http://reginald.braythwayt.com), [CV](http://reginald.braythwayt.com/RegBraithwaiteGH0909_en_US.pdf ""), [Twitter](http://twitter.com/raganwald) | |

