Я новичок в Scheme (через Racket) и (в меньшей степени) функциональном программировании, и мог бы воспользоваться некоторыми советами о плюсах и минусах накопления через переменные по сравнению с рекурсией. Для целей этого примера я пытаюсь вычислить скользящее среднее. Таким образом, для списка '(1 2 3 4 5)
скользящая средняя за 3 периода будет '(1 2 2 3 4)
. Идея состоит в том, что любые числа перед периодом еще не являются частью вычисления, и как только мы достигнем длины периода в наборе, мы начинаем усреднять подмножество списка в соответствии с выбранным периодом.
Итак, моя первая попытка выглядела примерно так:
(define (avg lst)
(cond
[(null? lst) '()]
[(/ (apply + lst) (length lst))]))
(define (make-averager period)
(let ([prev '()])
(lambda (i)
(set! prev (cons i prev))
(cond
[(< (length prev) period) i]
[else (avg (take prev period))]))))
(map (make-averager 3) '(1 2 3 4 5))
> '(1 2 2 3 4)
Это работает. И мне нравится использование карты. Это кажется сложным и открытым для рефакторинга. Я мог видеть в будущем двоюродных братьев, таких как:
(map (make-bollinger 5) '(1 2 3 4 5))
(map (make-std-deviation 2) '(1 2 3 4 5))
и т.д.
Но это не в духе Схемы (верно?), Потому что я накапливаю побочные эффекты. Поэтому я переписал это так:
(define (moving-average l period)
(let loop ([l l] [acc '()])
(if (null? l)
l
(let* ([acc (cons (car l) acc)]
[next
(cond
[(< (length acc) period) (car acc)]
[else (avg (take acc period))])])
(cons next (loop (cdr l) acc))))))
(moving-average '(1 2 3 4 5) 3)
> '(1 2 2 3 4)
Теперь эту версию сложнее понять с первого взгляда. Итак, у меня есть пара вопросов:
Есть ли более элегантный способ выражения рекурсивной версии с использованием некоторых встроенных итерационных конструкций ракетки (например, for/fold
)? Это даже хвост рекурсивно, как написано?
Можно ли написать первую версию без использования переменной аккумулятора?
Является ли этот тип проблемы частью более широкой схемы, для которой существуют принятые передовые практики, особенно в Схеме?