Основная проблема здесь заключается в том, как преобразовать строку, скажем, "aqqqq"
в список частоты появления символов в строке. т.е. мы хотим:
"aqqqq" => [1, 1, 2, 3, 4]
После того, как список частот создан, мы можем использовать zip3
для получения ожидаемого списка кортежей как:
[('a',1,100),('q',1,10),('q',2,40),('q',3,25),('q',4,25)]
Очевидно, что мы не можем использовать map
для получения желаемого списка частот, так как значение должно быть накоплено. Чтобы решить эту проблему, я рекомендую использовать Data.Map
, чтобы повысить вычислительную сложность с O(n)
до O(log n)
.
Частоту просто посчитать, используя insertWith
как:
countFreq c m = insertWith (+) c 1 m
и вернуть накопленное значение, используя lookup
как:
accumValue c m = fromMaybe 0 (Map.lookup c m) + 1
Теперь можно создать желаемый список следующим образом:
mkAccumList (c:cs) m = accumValue c m : mkAccumList cs (countFreq c m)
собрать все вместе:
import Data.Map as Map (empty, lookup, insertWith)
import Data.Maybe (fromMaybe)
countFreq c m = insertWith (+) c 1 m
accumValue c m = fromMaybe 0 (Map.lookup c m) + 1
split::String -> [a] -> [(Char, Int, a)]
split fmt row = zip3 fmt (mkAccumList fmt Map.empty) row
where mkAccumList (c:cs) m = accumValue c m : mkAccumList cs (countFreq c m)
mkAccumList [] _ = []
Для работы с бесконечным списком:
take 8 $ split (cycle "aqqqq") (cycle [100, 10, 40, 25, 25])
дает
[('a',1,100),('q',1,10),('q',2,40),('q',3,25),('q',4,25),('a',2,100),('q',5,10),
('q',6,40)]