2011-12-18 38 views
5

Hay una función estándar para sumar todos los valores en un mapa Haskell. Mi mapa dice algo como [(a, 2), (b, 4), (c, 6)]?Sum over Haskell Map

Esencialmente, lo que estoy tratando de hacer es una distribución de frecuencia normalizada. Entonces, los valores de las claves en el mapa de arriba son cuentas para a, b, c. Necesito normalizarlos como [(a, 1/6), (b, 1/3), (c, 1/2)]

+0

Buena pregunta. La solución obvia de 'foldl' es terriblemente no canónica para sumar sobre un árbol. – leftaroundabout

+0

En realidad, 'foldl'' es la mejor manera de hacer esto que se me ocurre; 'Data.Foldable.sum' sumará cada rama por separado y luego combinará el resultado, pero no es paralelo ni nada, por lo que no hay un beneficio real al hacerlo (y tiene los problemas de rigor que mencioné en mi respuesta). Una solución paralela puede ser interesante, pero probablemente solo resulte rentable para Mapas suficientemente grandes (en cuyo punto probablemente debería usar un HashMap de [contenedores no ordenados] (http://hackage.haskell.org/package/unordered-containers) o similar, Data.Map no es una estructura particularmente eficiente). – ehird

+0

Er ... el mío es un conjunto de datos bastante grande. De hecho, decidí no usar Hashtables desde que leí sobre problemas de rendimiento con la estructura en Haskell. ¿Es la estructura HashMap que mencionas similar en el uso? – atlantis

Respuesta

4

Puede simplemente hacer Map.foldl' (+) 0 (o M.foldl', si importó Data.Map como M).

Esto es como foldl' (+) 0 . Map.elems, pero un poco más eficiente. (No olvides el apóstrofo - usar foldl o foldr para hacer sumas con los tipos numéricos estándar (Int, Integer, Float, Double, etc.) generará enormes thunks, que consumirán mucha memoria y posiblemente causen tu programa para desbordar la pila.)

versiones sin embargo, sólo lo suficientemente recientes de containers (> = 0.4.2.0) contienen Data.Map.foldl', y no debe actualizar con cabal install, ya que viene con GHC. A menos que esté en GHC 7.2 o superior, foldl' (+) 0 . Map.elems es la mejor manera de lograr esto.

También es posible usar Data.Foldable.sum, que funciona en cualquier instancia de la clase de tipos Foldable, pero seguirá siendo acumular grandes procesadores de los tipos numéricos comunes.

Aquí está un ejemplo completo:

normalize :: (Fractional a) => Map k a -> Map k a 
normalize m = Map.map (/ total) m 
    where total = foldl' (+) 0 $ Map.elems m 

Tendrá que importar Data.List utilizar foldl'.

3
let 
    total = foldr (\(_, n) r -> r + n) 0 l 
in map (\(x, y) -> (x, y/total) l 

Dónde l es su mapa.

3

simple:

import qualified Data.Map as M 

sumMap = M.foldl' (+) 0 

normalizeMap m = 
    let s = sumMap m in 
    M.map (/ s) m 

main = do 
    let m = M.fromList [("foo", 1), ("bar", 2), ("baz", 6)] 
    (print . sumMap) m 
    (print . normalizeMap) m 

impresiones:

9.0 
fromList [("bar",0.2222222222222222),("baz",0.6666666666666666),("foo",0.1111111111111111)] 
+0

¿Alguna razón me podría dar un error 'No en el alcance: Map.foldl' '? Mis importaciones parecen estar bien. – atlantis

+0

@atlantis, eso será porque está utilizando una versión anterior de la biblioteca de contenedores. –