2010-03-01 2 views

ответ

6

без учета эффективности:

(defn average [lst] (/ (reduce + lst) (count lst))) 

(defn moving-average [window lst] (map average (partition window 1 lst))) 


user> (moving-average 5 '(1 2 3 4 5 6 7 8)) 
(3 4 5 6) 

Если вам это нужно, чтобы быть быстрым, есть некоторые довольно очевидные улучшения, которые будут сделаны! Но он станет менее изящным.

+1

Я ценю краткость и ясность, и в этом случае эффективность просто не вызывает беспокойства, так как это нужно только запустить один раз. –

+1

На самом деле речь идет о самой эффективной ленивой версии для более коротких периодов, поэтому, если вы не ожидаете превысить длину периода около 8, вы должны использовать ее в качестве оптимизации. Если вы собираетесь делать периоды продолжительностью 20, 50 или более, то это станет ужасным по производительности, и вы должны пойти с моей версией. ;-) Кроме того, ознакомьтесь с ведущим ответом Clojure (в целом, на данный момент Джеймсом Каннингемом) в этом старом Q, к которому я привязался к очень изящной однострочной версии этого. :-) –

4

Существует очень похожий вопрос о SO: Calculating the Moving Average of a List. Это более общий - представлен ряд FP-дружественных языков, с принятым ответом с помощью Scala, но есть несколько хороших решений Clojure.

Я разместил my own solution over there. Обратите внимание, что он использует lazy-seq, но это потому, что я хотел, чтобы он хорошо работал в течение больших периодов (что означает корректировку среднего значения на каждом шаге, а не вычисление отдельного среднего для каждого окна размера = период во входном списке). Посмотрите вокруг этого Q на хорошие решения, которые сделали другой компромисс, в результате чего более короткий код с несколько более декларативным ощущением, который на самом деле работает лучше за очень короткие периоды (хотя и страдает значительным замедлением на более длительные периоды, как и следовало ожидать).

+0

Хорошая идея с ссылкой, спасибо! –

4

Эта версия немного быстрее, особенно для длинных окон, поскольку она удерживает скользящую сумму и позволяет неоднократно добавлять одни и те же вещи.

Из-за ленивого-SEQ, это также совершенно общий характер и не будет дуть стопка

(defn partialsums [start lst] 
    (lazy-seq 
    (if-let [lst (seq lst)] 
      (cons start (partialsums (+ start (first lst)) (rest lst))) 
      (list start)))) 

(defn sliding-window-moving-average [window lst] 
    (map #(/ % window) 
     (let [start (apply + (take window lst)) 
      diffseq (map - (drop window lst) lst)] 
     (partialsums start diffseq)))) 

;; Чтобы узнать, что он делает:

(sliding-window-moving-average 5 '(1 2 3 4 5 6 7 8 9 10 11)) 

start = (+ 1 2 3 4 5) = 15 

diffseq = - (6 7 8 9 10 11) 
      (1 2 3 4 5 6 7 8 9 10 11) 

     = (5 5 5 5 5 5) 

(partialsums 15 '(5 5 5 5 5 5)) = (15 20 25 30 35 40 45) 

(map #(/ % 5) (20 25 30 35 40 45)) = (3 4 5 6 7 8 9) 

;; Пример

(take 20 (sliding-window-moving-average 5 (iterate inc 0))) 
+2

О, это очень элегантная идея ... Выполняет примерно то же самое, что и моя скользящая версия окна, но, безусловно, более эстетично. Не могли бы вы разместить его в качестве ответа на этот другой вопрос SO (см. Ссылку в моем ответе здесь)? +1 в любом случае! –

+1

Спасибо за ваши добрые слова. Я разместил его в другом потоке. –

0

Вместо partialsums п (что полезно, чтобы увидеть, что происходит), вы можете использовать reductions в clojure.core:

(defn sliding-window-moving-average [window lst] (map #(/ % window) (let [start (apply + (take window lst)) diffseq (map - (drop window lst) lst)] (reductions + start diffseq))))

Смежные вопросы