Another one of those "Aha" interview questions: Long-running averages
invisibleblocks.wordpress.com
invisibleblocks.wordpress.com
It is not, however, an "averager". The output depends on the order in which the inputs are delivered, it seeks to an average only given constant input. Think of the output for a continuously alternating set of 1's and 0's, vs giving all the zeros first then all the ones.
Let this be a lesson to all those who believe a CS degree should amount to nothing more than "Learn Blub in 21 Days Vocational Training" :-)
What leads you to believe that the order of the inputs is significant? The derivation makes sense, the final form makes sense, and I tried a few inputs.
(also, a high pass filter does not necessarily settle to its input over time)
IMHO, a more interesting interview question is:
"How would you efficiently compute a sliding-window average, where each number has an associated time and each time a number comes in, you want to quickly compute the average of all numbers whose timestamps fall within the last 5 minutes?"
This type of problem comes up all the time in the financial industry - I had to implement it for my last employer. The best algorithm I can think of takes amortized constant time and linear (proportional to window size) space. Anyone able to think of a better one? Wall Street wants you. ;-)
It's easy to prove that linear space is optimal -- you can't store N bits of information using less than N bits of space.
Sounds about right for what we do to compute Geometric Brownian Motion.
function create_sliding_averager (window_span, debug)
local window_span = window_span -- seconds
local tally = 0
local fifo = { tail = 1, head = 0 }
local debug = debug
return function (amount, current_time)
local head = fifo.head + 1
local tail = fifo.tail
fifo[head] = { timestamp = current_time, amount = amount }
tally = tally + amount
local cutoff, old_time = current_time - window_span, nil
if fifo[tail] then
old_time = fifo[tail].timestamp
end
if debug then
print ('cutoff: ', cutoff, 'old time:',
old_time, 'current:', current_time)
end
while old_time ~= nil and old_time < cutoff do
tally = tally - fifo[tail].amount
if debug then
print ('expiring: ', fifo[tail].timestamp, fifo[tail].amount)
end
fifo[tail] = nil
tail = tail + 1
old_time = fifo[tail].timestamp
end
fifo.tail = tail
fifo.head = head
if debug then print(tail, head) end
return 'avg: '..(tally / (head - tail + 1)), 'size: '..(head - tail + 1)
end
end
averager = create_sliding_averager(10) -- 10 second average
while true do
local rand = math.random()
local time = os.time()
print (averager(rand, time))
end
I would usually use a small library to implement (push(), pop(), peek()) on the fifo but this example will run in a stock interpreter. On my system the average for rand() hovers around .5 so it passes my initial smell test. It's hovering around 80k entries in a busyloop with a 10 second window.This is a pretty brute-force solution. I'll see if I can think of something more elegant.
Actually depending on the values you give it it might preform a lot worse than the running sum version.
average += (x-average)/count
As soon as count becomes large enough, (x-average)/count will equal zero in your system and you "averager" will be broken.
Seems to me you've just moved your representation bits from one side of a decimal to the other.
But that doesn't mean that you won't run into someone out there asking a question like this.
nums = [5, 7, 11, 9, 6, 6, 9]
ravg = 1.0
for i, num in enumerate(nums):
ravg = (ravg * i + num) / (i + 1)
print n, ravg