J'essaie d'analyser une liste en utilisant une chaîne de modèle qui indique les types de valeurs (annuel et trimestriel). J'ai besoin d'accumuler les numéros de trimestre dans la sortie résultante. Jusqu'à présent, j'ai trouvé ceci:
row = [100, 10, 40, 25, 25] fmt = "aqqqq" expected = [('a',1,100),('q',1,10),('q',2,40),('q',3,25),('q',4,25)] count :: Char -> String -> Int count letter str = length $ filter (== letter) str split :: String -> [a] -> [(Char, Int, a)] split fmt row = [(freq, count freq (fmt' i), x) | (freq, x, i) <- zip3 fmt row [0..]] where fmt' i = take (i+1) fmt -- split "aqqqq" [100, 10, 40, 25, 25] -- [('a',1,100),('q',1,10),('q',2,40),('q',3,25),('q',4,25)]
J'imagine qu'il devrait y avoir quelque chose de plus lisible et plus performant que ce code, ou même une super ligne unique.
J'ai aussi expérimenté en développant "aqqqq"
en liste de tuples [('a', 1), ('q', 1), ('q', 2), ('q', 3 ), ('q', 4)]
et plus tard en ajoutant des valeurs; c'est peut-être une meilleure façon car j'aurais besoin de spécifier le format une fois pour plusieurs lignes.
3 Réponses :
Si vous avez déjà une fonction expand
pour développer "aqqqq"
en liste de tuples, vous pouvez accomplir le reste avec zipWith
: < pre> XXX
La fonction expand
produit des tuples de type Num t => (Char, t)
. J'ai appelé les valeurs à l'intérieur de ce tuple p
(pour période ) et ix
(pour index ). La compression de cette liste de tuples avec row
produit également des valeurs, que je, dans l'expression lambda, appelle simplement x
.
Merci pour la suggestion de zipWith
et l'explication approfondie! Je vais rassembler expand
et zipWith
dans une publication distincte pour référence.
Basé sur la suggestion de @Mark Seemann, voici une liste complète avec une solution. J'ai changé lambda en fonction nommée pour un peu plus de lisibilité et j'ai introduit un type pour le format de ligne.
[('a',1,100),('q',1,10),('q',2,40),('q',3,25),('q',4,25)]
Le résultat est comme prévu:
makeSplitter fmt = \values -> zipWith merge pos values where merge (freq, period) value = (freq, period, value) pos = expand fmt splitRow = makeSplitter "aqqqq" a = splitRow [100, 10, 40, 25, 25]
Une réflexion après coup - J'étend toujours la chaîne de format chaque fois que j'analyse la ligne, probablement même le curry parse = split '"aqqqq"
retardera simplement le calcul.
Voici mon essai pour créer une fonction de lecture dédiée:
*Main> split' "aqqqq" [100, 10, 40, 25, 25] [('a',1,100),('q',1,10),('q',2,40),('q',3,25),('q',4,25)]
un
est le résultat attendu, comme ci-dessus
count :: Char -> String -> Int count letter str = length $ filter (== letter) str type RowFormat = [Char] expand :: RowFormat -> [(Char, Int)] expand pat = [(c, count c (take (i+1) pat)) | (c, i) <- zip pat [0..]] split' :: RowFormat -> [a] -> [(Char, Int, a)] split' fmt values = zipWith merge (expand fmt) values where merge (freq, period) value = (freq, period, value)
Comme vous le dites, une application partielle de votre solution originale ne rendrait pas les choses plus rapides. Votre dernière définition de makeSplitter
est bonne. Si vous le souhaitez, vous pouvez rendre implicites les valeurs
lamdba ici: makeSplitter fmt = zipWith merge pos where ...
. Je pense que ce qui se passe est également plus clair si vous vous débarrassez simplement de la liaison pos
: makeSplitter fmt = zipWith merge (expand fmt) where ...
Merci, @amalloy. Je pense que les liaisons excessives que j'ai utilisées sont dues à un contexte de programmation impératif, je les ai supprimées du code.
Le problème principal ici est de savoir comment convertir la chaîne, disons "aqqqq"
en la liste de fréquence des caractères apparaissant dans la chaîne. c'est-à-dire que nous voulons:
[('a',1,100),('q',1,10),('q',2,40),('q',3,25),('q',4,25),('a',2,100),('q',5,10), ('q',6,40)]
Une fois la liste de fréquences construite, nous pouvons utiliser zip3
pour produire la liste attendue des tuples comme:
take 8 $ split (cycle "aqqqq") (cycle [100, 10, 40, 25, 25])
De toute évidence, nous ne pouvons pas utiliser map
pour produire la liste de fréquences souhaitée car la valeur doit être accumulée. Pour le résoudre, je recommande d'utiliser Data.Map
afin d'améliorer la complexité de calcul de O (n)
à O (log n)
.
Il est simple de compter la fréquence en utilisant insertWith
comme:
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 [] _ = []
et de récupérer la valeur accumulée en utilisant chercher
comme:
mkAccumList (c:cs) m = accumValue c m : mkAccumList cs (countFreq c m)
maintenant, il est simple de créer la liste souhaitée comme:
accumValue c m = fromMaybe 0 (Map.lookup c m) + 1
put tous ensemble:
countFreq c m = insertWith (+) c 1 m
Pour travailler avec une liste infinie:
[('a',1,100),('q',1,10),('q',2,40),('q',3,25),('q',4,25)]
donne
"aqqqq" => [1, 1, 2, 3, 4]