web-dev-qa-db-fra.com

La mise en œuvre de cette fonction de mots est-elle possible sans étape de post-traitement après le pliage?

Real World Haskell, chapitre 4, page 98 de l'impression demande si words peut être implémenté en utilisant des replis, et c'est aussi ma question:

Est-ce possible? Si non, pourquoi? Si c'est le cas, comment?

J'ai proposé ce qui suit, basé sur l'idée que chaque non-espace doit être ajouté au dernier mot de la liste de sortie (cela se produit dans la garde otherwise), et qu'un espace devrait déclencher le ajout d'un mot vide à la liste de sortie s'il n'y en a pas déjà un (ceci est géré dans le if-then-else).

myWords :: String -> [String]
myWords = foldr step [[]]
  where
    step x yss@(y:ys)
      | x == ' ' = if y == "" then yss else "":yss
      | otherwise = (x:y):ys

Il est clair que cette solution est erronée, car les espaces de début dans la chaîne d'entrée entraînent une chaîne vide de début dans la liste de chaînes de sortie.

Au lien ci-dessus, j'ai examiné plusieurs des solutions proposées pour d'autres lecteurs, et beaucoup d'entre elles fonctionnent de la même manière que ma solution, mais elles "post-traitent" généralement la sortie du pli, par exemple par tailing it s'il y a une chaîne de début vide.

D'autres approches utilisent des tuples (en fait juste des paires), de sorte que le pli traite la paire et puisse bien gérer les espaces de début/de fin.

Dans toutes ces approches, foldr (ou un autre repli, fwiw) n'est pas la fonction qui fournit la sortie finale prête à l'emploi; il y a toujours quelque chose d'autre à ajuster en quelque sorte la sortie.

Par conséquent, je reviens à la question initiale et demande s'il est réellement possible d'implémenter words (de manière à gérer correctement les espaces de fin/de début/répétés) en utilisant des plis. Par en utilisant des plis je veux dire que la fonction de pliage doit être la fonction la plus externe:

myWords :: String -> [String]
myWords input = foldr step seed input
21

Oui. Même si c'est un peu délicat, vous pouvez toujours faire ce travail correctement en utilisant un seul foldr et rien d'autre si vous vous attardez dans CPS ( Continuation Passing Style ). J'avais montré un type spécial de chunksOf fonction précédemment.

Dans ce genre de plis, notre accumulateur, donc le résultat du pli est une fonction et nous devons l'appliquer à un type d'entrée d'identité pour avoir le résultat final. Cela peut donc compter comme une étape de traitement finale ou non, car nous utilisons ici un seul pli et le type de celui-ci comprend la fonction. Ouvert au débat :)

ws :: String -> [String]
ws str = foldr go sf str $ ""
         where
         sf :: String -> [String]
         sf s = if s == " " then [""] else [s]
         go :: Char -> (String -> [String]) -> (String -> [String])
         go c f = \pc -> let (s:ss) = f [c]
                         in case pc of
                            ""        -> dropWhile (== "") (s:ss)
                            otherwise -> case (pc == " ", s == "") of
                                         (True, False)  -> "":s:ss
                                         (True, True)   -> s:ss
                                         otherwise      -> (pc++s):ss

λ> ws "   a  b    c   "
["a","b","c"]

sf: La valeur de la fonction initiale avec laquelle commencer.

go: La fonction itérateur

En fait, nous n'utilisons pas pleinement la puissance du CPS ici puisque nous avons à la fois le caractère précédent pc et le caractère courant c à portée de main à chaque tour. C'était très utile dans la fonction chunksOf mentionnée ci-dessus lors de la segmentation d'un [Int] dans [[Int]] chaque fois qu'une séquence ascendante d'éléments a été interrompue.

1
Redu