2011-12-18 15 views
5

Esiste una funzione standard per sommare tutti i valori in una mappa Haskell. My Map legge qualcosa come [(a, 2), (b, 4), (c, 6)]?Sum su Haskell Map

In sostanza, quello che sto cercando di fare è una distribuzione di frequenza normalizzata. Quindi i valori delle chiavi nella mappa sopra sono conteggi per a, b, c. Devo normalizzarli come [(a, 1/6), (b, 1/3), (c, 1/2)]

+0

Buona domanda. L'ovvia soluzione 'foldl' è abbastanza orribilmente non canonica da sommare su un albero. – leftaroundabout

+0

In realtà, 'foldl'' è il modo migliore per farlo a cui posso pensare; 'Data.Foldable.sum' somma ogni ramo separatamente e quindi combina il risultato, ma non è parallelo o nulla, quindi non c'è alcun beneficio reale a fare questo (e ha i problemi di severità che ho citato nella mia risposta). Una soluzione parallela potrebbe essere interessante, ma probabilmente pagherebbe solo per mappe sufficientemente grandi (a quel punto dovresti probabilmente usare una HashMap da [contenitori non ordinati] (http://hackage.haskell.org/package/unordered-containers) o simili; Data.Map non è una struttura particolarmente efficiente). – ehird

+0

Er ... il mio è un set di dati piuttosto grande. In effetti, ho deciso contro Hashtables da quando ho letto dei problemi di performance con la struttura di Haskell. La struttura HashMap di cui parli è simile nell'uso? – atlantis

risposta

4

È sufficiente fare Map.foldl' (+) 0 (o M.foldl', se hai importato Data.Mappa come M).

Questo è proprio come foldl' (+) 0 . Map.elems, ma leggermente più efficiente. (Non dimenticare l'apostrofo: usare foldl o foldr per fare somme con i tipi numerici standard (Int, Integer, Float, Double, ecc.) Creerà enormi thunk, che consumeranno molta memoria e potrebbero causare il tuo programma traboccare la pila.)

versioni Tuttavia, solo sufficientemente recenti di containers (> = 0.4.2.0) contenere Data.Map.foldl', e non si dovrebbe aggiornarlo con cabal install, dal momento che si tratta con GHC. Pertanto, a meno che non si utilizzi GHC 7.2 o versione successiva, foldl' (+) 0 . Map.elems è il modo migliore per farlo.

È inoltre possibile utilizzare Data.Foldable.sum, che funziona su qualsiasi istanza della classe di caratteri Foldable, ma continuerà comunque a generare grossi volumi sui tipi numerici comuni.

Ecco un esempio completo:

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

Avrai bisogno di importare Data.List da usare foldl'.

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

Dove l è la vostra mappa.

3

Semplice:

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 

stampe:

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

Qualsiasi motivo potrebbe darmi un errore "Not in scope: Map.foldl"? Le mie importazioni sembrano buone. – atlantis

+0

@atlantis, sarà perché si sta utilizzando una versione precedente della libreria contenitori. –