import qualified Data.Map as Map
import Data.List (foldl') -- ' (to fix SO syntax highlighting)
histogram :: (Ord a) => [a] -> Map.Map a Int
histogram = foldl' (\m x -> Map.insertWith' (+) x 1 m) Map.empty
Объяснение того, почему это работает и почему оно превосходит решение Трэвиса Брауна, довольно техническое, и для полного понимания потребуется некоторое терпение.
Если в списке может быть только конечное число значений, это выполняется в постоянной памяти. В решении Трэвиса есть небольшая ошибка, из-за которой получающиеся записи на карте будут выглядеть так:
(4, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)
Очень неэффективное представление числа 19. Только когда вы попросите этот элемент на карте, будет вычислена гигантская сумма. Эти "thunks" (выражения отложенной оценки) будут расти линейно с размером входных данных.
Чтобы предотвратить это, мы используем insertWith'
, который применяет функцию строго , то есть он оценивает результат перед тем, как поместить его на карту. Итак, если вы вставите 4 в карту выше, он оценит Thunk и вы получите хорошую приборку:
(4, 20)
И еще один оценит это перед добавлением, так что вы получите:
(4, 21)
Так что теперь, по крайней мере, значения карты являются постоянным пространством.
Последнее, что нам нужно сделать, это изменить правый сгиб на левый, потому что Map.insert строг в своем втором аргументе. Следующее демонстрирует значение правой складки.
iw x m = Map.insertWith' (+) x 1 m -- '
foldr iw Map.empty [1,2,1,3,2,1]
= iw 1 (iw 2 (iw 1 (iw 3 (iw 2 (iw 1 Map.empty)))))
Использование iw
в качестве простого сокращения. Map.insert
строгость второго аргумента означает, что вам нужно оценить карту, в которую вы вставляете, прежде чем вставка сможет выполнить какую-либо работу. Я буду использовать обозначение { k1 -> v1, k2 -> v2, ... }
как сокращение для карт. Ваша последовательность оценки выглядит следующим образом:
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
foldr iw {} [1,2,1,3,2,1]
iw 1 (foldr iw {} [2,1,3,2,1])
iw 1 (iw 2 (foldr iw {} [1,3,2,1]))
iw 1 (iw 2 (iw 1 (foldr iw {} [3,2,1])))
iw 1 (iw 2 (iw 1 (iw 3 (foldr iw {} [2,1]))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 (foldr iw {} [1])))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 (iw 1 (foldr iw {} []))))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 (iw 1 {}))))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 {1 -> 1}))))
iw 1 (iw 2 (iw 1 (iw 3 {1 -> 1, 2 -> 1})))
iw 1 (iw 2 (iw 1 {1 -> 1, 2 -> 1, 3 -> 1}))
iw 1 (iw 2 {1 -> 2, 2 -> 1, 3 -> 1})
iw 1 {1 -> 2, 2 -> 2, 3 -> 1}
{1 -> 3, 2 -> 2, 3 -> 1}
Итак, если у вас есть массив из 1 000 000 элементов, нам нужно пройти весь процесс до 1 000 000 элементов, чтобы начать вставку, поэтому нам нужно сохранить в памяти предыдущие 999 999 элементов, чтобы мы знали, что делать дальше. Левый сгиб решает это:
-- definition of left fold
foldl' f z xs = go z xs -- '
where
go accum [] = z
go accum (x:xs) = accum `seq` go (f accum x) xs
foldl' (flip iw) Map.empty [1,2,1,3,2,1] -- needed to flip arg order to appease foldl'
go {} [1,2,1,3,2,1]
go (iw 1 {}) [2,1,3,2,1]
go (iw 2 {1 -> 1}) [1,3,2,1]
go (iw 1 {1 -> 1, 2 -> 1}) [3,2,1]
go (iw 3 {1 -> 2, 2 -> 1}) [2,1]
go (iw 2 {1 -> 2, 2 -> 1, 3 -> 1}) [1]
go (iw 1 {1 -> 2, 2 -> 2, 3 -> 1}) []
iw 1 {1 -> 2, 2 -> 2, 3 -> 1}
{1 -> 3, 2 -> 2, 3 -> 1}
Теперь мы можем видеть, что, наконец, если количество записей на карте ограничено, то это выполняется в постоянном пространстве и линейном времени.