require 'aqueductron'
require 'aqueductron'
The rabbits, see, they reproduce. Generation n is a function of the size of the previous two generations.
F(n) = F(n - 1) + k * F(n - 2)
The game is to predict future generation sizes. Send the known generation sizes down the duct, and it learns a value of k. It needs at least 3 generations. Send :unknown and the size of the next generation is predicted.
Custom duct pieces contain functions that construct the next duct piece out of other functions. Each function knows a little more than the one in the previous iteration.
class Fibonacci
The duct is short: one custom piece, and then a collector that outputs the last number it has seen. That is the last known value in the sequence of rabbit counts. The interesting parts are the custom function definitions.
def rabbit_predictor
Aqueductron::Duct.new.custom(empty_fib_function).last
end
First, we know nothing. The initial piece only knows how to accept some data and change the piece to incorporate that data.
def empty_fib_function
->(piece,msg) do
piece.pass_on(msg, one_data_fib_function(msg),"#{msg}..")
end
end
Second, we know one generation's size. That isn't enough to do anything except look for the second generation.
def one_data_fib_function(first)
->(piece,msg) do
piece.pass_on(msg, two_data_fib_function(first, msg),"#{first},#{msg}..")
end
end
Third, we know two generations. Once we get an additional piece of data, we can calculate an initial guess at k.
def two_data_fib_function(first,second)
->(piece,msg) do
k = (msg - second + 0.0) / first # initial guess at k
piece.pass_on(msg, learning_fib_function(msg, second, k, 1),
"..#{second},#{msg}.. starting k~#{k}")
end
end
Fourth, as long as we keep getting data, we can refine the value of k. Keep using this function, with different arguments, as long as we keep getting observed data points. As long as someone is still counting the rabbits. Once we get :unknown, it's time to start predicting.
def learning_fib_function(prev_number, prev_prev_number, k, data_points_in_k)
->(piece,msg) do
if (msg == :unknown)
fib_function(prev_number, prev_prev_number, k).call(piece,msg)
else
this_k = estimate_k(prev_prev_number, prev_number, msg)
average_k = add_data_point(k, data_points_in_k, this_k)
piece.pass_on(msg, learning_fib_function(msg, prev_number, average_k, data_points_in_k + 1),
"..#{prev_number},#{msg}.. learning k~#{average_k}")
end
end
end
Fifth and finally, predict future generations based on the value of k we've learned. This one keeps setting up a new version of itself, with different arguments, every time.
def fib_function(prev_number, prev_prev_number, k)
->(piece, _) do # msg is ignored
current_val = (prev_number + ( prev_prev_number * k )).round
piece.pass_on(current_val, fib_function(current_val, prev_number, k),
"..#{prev_number},#{current_val}.. k=#{k}")
end
end
helper methods do some calculation
def estimate_k(first, second, third) #consecutive terms
(third - second) / first
end
def add_data_point(average_so_far, data_points_included, new_data_point)
(average_so_far * data_points_included + new_data_point) / (data_points_included + 1)
end
end
rabbits = Fibonacci.new.rabbit_predictor
Seed it with the known data
seeded = rabbits.keep_flowing([2,5,8,11])
seeded.drip(:unknown).eof
seeded.drip(:unknown).drip(:unknown)
for added fun, drip the known generations in one at a time to watch the duct learn.