Tech Interview: Aftertaste
Interviews take a lot of energy. In general but this one in particular. And getting through it also deserves a little celebration. Whatever the reason, it is sort of a ritual to go to a nearby food place after. Raw protein wrapped in carbs, if it can be helped.
The place is quiet, well past the rush of a lunch time, well before the noise of an evening. Getting ready your linear utensils and squishing totally reasonable blobs of green stuff onto your units of sustenance, a thought keeps coming back to you:
Can we do any better?
You pull out a writing device of your choice and a generic white-label wire-bound notebook. You open the notebook on a fresh page and stare at the empty lines for a bit.
There was a class where someone derived a formula for the Fibonacci numbers. Or someone mentioned it. And it was exciting. What was it all about? Something around representing sequences of numbers as polynomials. Was it called generating functions?
Starting simple is usually a good idea. Maybe encoding a list of all ones? In polynomials, the various powers of the variable naturally separate their coefficients. So a sequence could be represented as a polynomial on with
That does not look very useful, but you won’t let that discourage you. This will need some poking around. A fun thing that can be done with polynomials is to multiply it by the variable, so you try that.
Fascinating: This way we shifted the sequence and prepended 0! This now represents . Things are emerging from murky depths of memory. Let’s subtract those…
Now this looks very promising. Suddenly the whole expression becomes very finite. No need to dance around the infinity with ellipsis… Just few small steps later we get
Somehow, this fraction captures the infinite list of ones. While possibly not very useful on its own, this feels like an important piece of a puzzle.
And the whole process reminded you of writing the infinite list of Fibonacci numbers in Haskell.
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)And so you try representing the sequence:
It takes a bit of squinting to realize that the right side is a Fibonacci sequence shifted by 2 positions with extra x (elements of which you can kinda see in the Haskell implementation!):
This is great, except, what do I do with this? How do I get back to elements of the sequence? Or even better, how do I get to an element at a given position?
Try deriving expression for .
Is that going to help…‽
You got almost to the end of that sentence before you realized, all at once, that the interviewer from earlier:
- is sitting right next to you;
- must have been peeking over your shoulder;
- is looking at you with mild interest;
- has mouth freshly stuffed with what looks suspiciously similar to your own food.
How long have you…
Just try.
Trying to not get too distracted by what just happened you repeat the steps, but with extra .
That is actually very useful! If we find an appropriately shaped expression, this formula allows us to find n-th element in the sequence on its own! And polynomials are easy to sum as we saw earlier. That is handy. So is the pattern for adding two fractions. Now if only we could express this generating function as a sum of appropriate forms…
Using quadratic formula you find the roots of the denominator.
Ugh… Do I even remember quadratic formula? Let me try to remember real quick.
This way we get the left side to shape of .
From there you reconstruct the individual factors.
Roots are only points where the parabola crosses the x-axis, we need to fit it with multiplicative component, and that is .Next goal is to find and such that
(arbitrarily putting minus on the side of ). It takes a bit of a paper real-estate, but eventually you get to
Even more paper is burned on re-shaping the denominator to the desired form of .
And from there n-th element (coefficient for ) is:
Great work! You can rationalize the denominators in those bases to make it prettier (and maybe a bit more useful) 👍.
That sounds reasonable.
Well, that’s a beautiful closed form. And strange one too: all the irrational numbers, yet it produces natural numbers.
However it still does not allow us to calculate n-th Fibonacci number in sub-log time! If it was possible there would be some great improvements to be made here:
Excellent insight. Now how would you turn this into a function that is able to calculate exact results on unbounded numbers?
The trick is that we don’t need exact value of . We can treat it as a special symbol for which . Similar-ish to introducing for .
Or we can see it as polynomials factored with .
All of it sounds reasonable. But let’s let the code speak.
Okay, here goes nothing, but I am a bit tired so I’ll do some heavy hand-waving…
import Data.Ratio
data S5 a = S !a !a deriving Show
s5 :: Num a => S5 a
s5 = S 0 1
instance Num a => Num (S5 a) where
S a b + S c d = S (a+c) (b+d)
S a b - S c d = S (a-c) (b-d)
S a b * S c d = S (a*c+5*b*d) (a*d+b*c)
fromInteger a = S (fromInteger a) 0
negate (S a b) = S (negate a) (negate b)
abs _ = error "leave me alone"
signum _ = error "meh"
instance (Eq a, Fractional a) => Fractional (S5 a) where
fromRational a = S (fromRational a) 0
S a b / S c 0 = S (a/c) (b/c) -- This is okay as we only divide by 2 anyway
type T = S5 Rational
fac :: Integer -> Integer
fac n = numerator x
where
-- okay, also by s5 but we know it will be like this
-- and even the last minus does technically not need
-- to do the rational part.
S 0 x = ((1 + s5)/2)^n - ((1 - s5)/2)^nImprovements might be possible but for now the matrix-inspired
version is still more efficient as S is holding two rational
numbers that are themselves tuples.
You feel a congratulatory tap on your back. Well deserved. You feel satisfied. But tired too. Actually really tired. You blink, look up at the food-place employee giving you the “we are about to close look”. There is nobody else in here, you are the last customer.
Confused, you stand up, stuff all your stuff into the backpack, say quick thank you and bye and disappear into the night.