О числе e…

Jul 23, 2014 01:29



Четыре года назад, я радовался работе с бесконечными списками и вычислял e суммирую ряд Тейлора так. Случайно найдя предыдущий сниппет и смахнув слезу умиления, я переписал суммирование уже со знанием стандартной библиотеки.

e = sum $ takeWhile (/=0) $ map (\v -> 1 / product [1..v]) [0..]



Проблема в том, что этот вариант аллоцировал в 10 раз больше памяти чем первый. Стало как-то неудобно и я заменил вычисление факториала для каждого нового члена ряда Тейлора на вычисление непрерывного списка факториалов, где каждый следующий элемент умножение номера члена ряда на значение предыдущего элемента.

e = sum $ takeWhile (/=0) $ map (1/) $ scanl (*) 1 [1..]

Количество аллоцированной памяти вышло на уровень первого варианта. Однако в первом варианте у нас используется наивный факториал и он всё равно работает замечательно. Разница состоит в том, что первый вариант суммирует ряд до тех пор пока сумма изменяется, а третий - до тех пор пока n-ый член ряда не станет нулём. Любому кто знаком с числами с плавающей запятой понятно что первое условие выполнится быстрее второго. Попробуем использовать именно первое условие. Для этого из бесконечного списка факториалов получим бесконечный список сумм, выделим пары текущая сумма/следующая сумма и будем разматывать список до тех пор пока суммы не равны. Последняя сумма и есть наша.

e = fst $ last $ takeWhile (uncurry (/=)) $ (\v -> zip v (tail v)) $ scanl1 (\v0 v1 -> v0 + 1 / v1) $ scanl (*) 1 [1..]

Мы наконец заметно превзошли по выделенной памяти первый вариант. Но не в 10 раз, а в полтора и ценой дополнительного времени на сборку мусора. Это скажем так себе результат.

восторг, жемчужные руны, geek, житие

Previous post Next post
Up