If you need weighted moving averages, this is a tad ugly but seems to
work ..
(defn weighted-moving-average*
"Generate a lazy sequence consisting of weighted moving averages
over the pre-partitioned input sequence. The weights parameter
should be a sequence of weights the same length as the partition
size."
[weights input]
(lazy-seq
(when-let [input (seq input)]
(let [weight-sum (apply + weights)
weighted-input-sum (apply + (map * weights (first input)))]
(cons (/ weighted-input-sum weight-sum)
(weighted-moving-average* weights (rest input)))))))
(defn weighted-moving-average
"Generate a lazy sequence consisting of weighted moving averages
over the input sequence. The weighting is given by weight-fn, having
a domain 0..1 which will be scaled across win-size elements to
calculate the weighting."
[weight-fn win-size input]
(let [weight-samples (reverse (for [t (range 1 (inc win-size))]
(/ t win-size)))
weights (map weight-fn weight-samples)]
(weighted-moving-average*
weights (partition win-size 1 input))))
-Kyle
Okay it is still a tad ugly but hopefully now it won't be because
the blasted mail client mutilated it :)
> I think you could simplify your code by using map twice. What about:
> (untested)
>
> (defn weighted-moving-average
> "Generate a lazy sequence consisting of weighted moving averages
> over the input sequence. The weighting is given by weight-fn,
> having a domain 0..1 which will be scaled across win-size elements to
> calculate the weighting."
> [weight-fn win-size input]
> (let [weights (reverse (for [t (range 1 (inc win-size))]
> (weight-fn (/ t win-size))))
> weight-sum (reduce + weights)
> normalized-weights (map #(/ % weight-sum) weights)
> avg #(reduce + (map * normalized-weights %))]
> (map avg (partition win-size 1 input))))
Hmm, yes that is certainly simpler.
This actually reminds me of something that I've "felt" for awhile but
haven't been able to articulate: A lot of functional programming
literature takes great pains to explain how higher-order functions,
lambda expressions, closures, etc work, and so I feel like I have a
fairly solid grasp of the mechanics of many functional concepts. I could
probably even give you a passable explanation for how to implement
lexical scoping.
But, I still sometimes find it challenging to spot patterns that can be
replaced and simplified with HOFs and other functional idioms, and I
haven't come across any books/guides that help hone one's intuition for
doing this. This is a good example because, while I have no trouble at
all understanding your implementation, I doubt I would have ever thought
of it myself!
I wonder if anyone has a guide or tips or rules of thumb to help with
this, or if it just takes experience...
As an aside, I also notice you prefer 'reduce to 'apply when using
arithmetic functions, yet I've seen both in the wild. I'm just guessing
you prefer to make it explicit that you're doing a reduction, but I
wonder if one is better than another?
>
> btw, why the 'reverse in weights?
>
I found it makes it easier to think about the weighting function if
(weight-fn 0) evaluates to the weight of the most recent sample (the
latest in the sequence), but the 'for comprehension generates them with
(weight-fn 0) as the first element in the sequence.
I suppose, it would be more efficient to have 'range generate the series
in reverse to begin with, wouldn't it? :)
-Kyle
> As an aside, I also notice you prefer 'reduce to 'apply when using
> arithmetic functions, yet I've seen both in the wild. I'm just
> guessing
> you prefer to make it explicit that you're doing a reduction, but I
> wonder if one is better than another?
That's an interesting question. I just benchmarked 'em and found this:
user> (time (reduce + (range 100000000)))
"Elapsed time: 4724.192 msecs"
4999999950000000
user> (time (apply + (range 100000000)))
"Elapsed time: 3018.139 msecs"
4999999950000000
I would have expected that 'apply would eventually blow up, because in
Common Lisp there is a maximum number of arguments. My second
intuition was that if '+/2 is inlined then the reduce should be
faster, taking advantage of the inlined code. Both my intuitions were
wrong and it looks like 'apply is notably faster.
—
Daniel Lyons
That's an interesting question. I just benchmarked 'em and found this:
On Jun 23, 2009, at 11:37 AM, Kyle Schaffrick wrote:
> As an aside, I also notice you prefer 'reduce to 'apply when using
> arithmetic functions, yet I've seen both in the wild. I'm just
> guessing
> you prefer to make it explicit that you're doing a reduction, but I
> wonder if one is better than another?
user> (time (reduce + (range 100000000)))
"Elapsed time: 4724.192 msecs"
4999999950000000
user> (time (apply + (range 100000000)))
"Elapsed time: 3018.139 msecs"
4999999950000000
I would have expected that 'apply would eventually blow up, because in
Common Lisp there is a maximum number of arguments.
My second
intuition was that if '+/2 is inlined then the reduce should be
faster, taking advantage of the inlined code.
But, I still sometimes find it challenging to spot patterns that can be
replaced and simplified with HOFs and other functional idioms, [...]
I wonder if anyone has a guide or tips or rules of thumb to help with
this, or if it just takes experience...
Wish it were so, because I like your theory better than my reality: :)
But if I try it with your original number (10x bigger than mine) I run out of Java heap space. Which didn't happen before either, because the lists weren't materialized.So range is cheating here? How's that?
apply retains the head because it calls through Java (IFn.applyTo)
where it is not possible to clear locals before tail calling, so the
head is on the stack.
I'm afraid everyone's gotten spoiled :), but apply does have the
general limitation that any args consumed must fit in memory.
Rich