Idiomatiques efficace Haskell ajouter?
Liste et les inconvénients de l'opérateur (:)
sont très fréquents en Haskell. Le contre est notre ami. Mais parfois, j'ai envie d'ajouter à la fin d'une liste à la place.
xs `append` x = xs ++ [x]
Cela, malheureusement, est pas un moyen efficace de mettre en œuvre.
J'ai écrit jusqu' Le triangle de Pascal en Haskell, mais j'ai dû utiliser la ++ [x]
anti-idiome:
ptri = [1] : mkptri ptri
mkptri (row:rows) = newRow : mkptri rows
where newRow = zipWith (+) row (0:row) ++ [1]
à mon humble avis, c'est une belle lisible le triangle de Pascal et de tous, mais l'anti-idiome ne m'irrite. Quelqu'un peut m'expliquer (et, idéalement, m'indiquer un bon tutoriel) sur ce que l'idiomatiques structure de données est pour les cas où vous voulez ajouter à la fin de manière efficace? J'espère que pour la proximité de la-liste-comme la beauté dans cette structure de données et de ses méthodes. Ou, alternativement, expliquez-moi pourquoi cet anti-idiome est en fait pas si mauvais que ça pour ce cas (si vous croyez que tel est le cas).
[modifier] La réponse que j'aime le plus Data.Sequence
, qui n'ont, en effet, "près de de la liste comme la beauté." Pas sûr de savoir comment je me sens sur la rigueur d'exploitation. D'autres suggestions et des idées différentes sont toujours les bienvenus.
import Data.Sequence ((|>), (<|), zipWith, singleton)
import Prelude hiding (zipWith)
ptri = singleton 1 : mkptri ptri
mkptri (seq:seqs) = newRow : mkptri seqs
where newRow = zipWith (+) seq (0 <| seq) |> 1
Maintenant nous avons juste besoin de la Liste à être une classe, alors que d'autres structures peuvent utiliser ses méthodes comme zipWith
sans le cacher de Prélude, ou en le qualifiant. 😛
- La liste n'est pas une classe, mais ListLike est. Il y a un Données.Séquence instance est disponible aussi. hackage.haskell.org/package/ListLike-3.0.1
- Aléatoire gêne: j'ai d'abord essayé d'écrire
newRow = 1 <| zipWith (+) seq (drop 1 seq) |> 1
, qui à mon humble avis est très beau pour exprimer le triangle de Pascal en montrant explicitement les 1 aux deux extrémités de chaque ligne. Malheureusement, j'ai eu cette erreur:cannot mix '<|' [infixr 5] and '|>' [infixl 5] in the same infix expression
- Cette seconde
newRow
est très belle. J'ai eu un non-idéalement-succès avec ZipLists (je m'attendais à quelque chose de plus général, mais c'était trop compliqué), hpaste.org/44613/pascals_ziplist. Avec l'idiome crochets de lashe
préprocesseur, et quelques maison combinators il ressemble à ceci:pascalsNextLine old = 1 <& (| tail' old + init' old |) &> 1
Il serait intéressant de savoir si il ya quelque chose à généraliser cette idée de la vôtre. - Il semble que les deux
|>
et<|
ont la même priorité, de sorte qu'ils ne peuvent pas passer à côté de l'autre. Je me demande si il existe un moyen de changer cela, sans se briser d'autres fichiers, sorta sur un fichier par fichier. - Je me demandais à propos de cette trop. Une idée que j'avais était d'utiliser un O(1)
cons
comme un ajout, mais de là à O(n)reverse
la liste résultante. Voir aussi: le contraste entre la mise en œuvre demap
par rapport àreverse
.
Vous devez vous connecter pour publier un commentaire.
Standard
Sequence
a O(1) pour plus de "les deux bouts" et O(log(min(n1,n2))) pour le général de concaténation:http://hackage.haskell.org/packages/archive/containers/latest/doc/html/Data-Sequence.html
La différence à partir de listes, c'est que
Sequence
est stricteGardez à l'esprit que ce qui semble pauvres asymptotique pourrait pas être, parce que vous travaillez dans un paresseux de la langue. Dans le cadre strict de la langue, en ajoutant à la fin d'une liste liée dans cette voie serait toujours O(n). Dans un paresseux de la langue, c'est O(n) seulement si vous traverse à la fin de la liste,auquel cas vous auriez dépensé O(n) effort de toute façon. Ainsi, dans de nombreux cas, la paresse vous sauve.
Ce n'est pas une garantie... par exemple, k ajoute suivie par un parcours va toujours s'exécuter en O(nk), où il pourrait avoir été O(n+k). Mais il n'changer l'image un peu. La réflexion sur la performance de simples opérations en fonction de leur complexité asymptotique lorsque le résultat est immédiatement forcé n'est pas toujours vous donner la bonne réponse à la fin.
Quelque chose comme cela explicite la récursivité évite de votre append "anti-idiome". Bien que, je ne pense pas que c'est aussi clair que votre exemple.
pZip _ _ = [1]
Dans votre code pour le Triangle de Pascal, ++ [x] n'est pas réellement un problème. Depuis que vous avez à produire une nouvelle liste sur le côté gauche de ++ de toute façon, votre algorithme est intrinsèquement quadratique; elle ne peut pas être asymptotiquement plus rapide simplement en évitant ++.
Aussi, dans ce cas particulier, lors de la compilation -O2, GHC la liste de fusion règles (devrait) éliminer la copie de la liste ++ normalement créer. C'est parce que zipWith est un bon producteur et ++ est un bon consommateur, c'est le premier argument. Vous pouvez lire au sujet de ces optimisations dans GHC Guide de l'Utilisateur.
Selon votre cas d'utilisation, les
ShowS
méthode (en ajoutant par composition de fonctions) peut être utile.ShowS
approche.. Mais ça va être une constante amélioration du coefficient d', de toute façon. La construction de la ligne en question est déjà en O(n). L'ajout d'un autre O(n) étape ne pas faire trop de mal.Si vous voulez juste bon marché append (concat) et snoc (contre à droite) un Hughes liste, aussi appelé DList sur Hackage, est la plus simple à mettre en œuvre. Si vous voulez savoir comment ils fonctionnent, regarder Andy Gill et Graham Hutton premier Travailleur Wrapper papier, John Hughes, le papier d'origine ne semble pas être en ligne. Comme d'autres l'ont dit ci-dessus Montre est une Chaîne spécialisée Hughes liste /DList.
Un JoinList est un peu plus de travail à mettre en œuvre. C'est un arbre binaire, mais avec une liste d'API - concat et snoc sont bon marché et vous pouvez raisonnablement fmap: le DList sur Hackage a un foncteur instance, mais à mon avis il ne devrait pas avoir le foncteur instance a pour metamorph dans et hors d'une liste ordinaire. Si vous voulez un JoinList ensuite, vous aurez besoin de rouler votre propre l'un sur le Hackage est le mien et il n'est pas efficace, ni bien écrit.
De données.La séquence est efficace contre et snoc, et il est bon pour les autres opérations - prend des gouttes, etc. qu'un JoinList est lent pour. Parce que l'intérieur du doigt de l'arbre de la mise en œuvre de Données.La séquence a pour l'équilibre de l'arbre, append est plus de travail que ses JoinList équivalent. Dans la pratique, car les Données.La séquence est mieux écrit, je m'attends toujours sur-effectue mon JoinList pour l'ajouter.
d'une autre manière à éviter de concaténation à tous en utilisant simplement infini listes:
ptri = zipWith take [1..] . iterate ((zipWith (+) <*> tail) . (0:)) $ 1 : repeat 0
(en supposant que vous avez importéControl.Applicative
). Une autre façon intéressante de le faire est:ptri = zipWith take [1..] . transpose . zipWith (++) (iterate (0 :) []) . iterate (scanl1 (+)) $ repeat 1
Je n'aurais pas forcément appeler votre code "anti-idomatic". Souvent, plus claire est mieux, même si cela signifie sacrifier quelques cycles d'horloge.
Et dans votre cas particulier, l'ajouter à la fin n'a pas vraiment changer le big-O temps de la complexité! L'évaluation de l'expression
prendra du temps proportionnel
length xs
et pas de fantaisie, la séquence de la structure de données va changer ça. Si quoi que ce soit, seul le facteur constant sera affectée.Data.Sequence
est tout aussi clair.Chris Okasaki a un design pour une file d'attente qui traite de cette question. Voir la page 15 de sa thèse
http://www.cs.cmu.edu/~cep/thèses/okasaki.pdf
Vous faudra peut-être adapter le code légèrement, mais certains l'utilisent de l'inversion et de garder les deux morceaux de la liste vous permet de travailler plus efficacement en moyenne.
Aussi, quelqu'un a mis en place certains de la liste de code dans la monade lecteur avec l'efficacité des opérations. Je l'avoue, je n'ai pas vraiment le suivre, mais j'ai pensé que je pourrais le comprendre si je me suis concentré. Il s'avère qu'il a été Douglas M. Auclair dans la Monade problème des lecteurs 17
http://themonadreader.files.wordpress.com/2011/01/issue17.pdf
J'ai réalisé la réponse ci-dessus ne permet pas de répondre directement à la question. Donc, pour rire, voici mon récursive réponse. Hésitez pas à déchirent -- elle n'est pas jolie.
Si vous êtes à la recherche d'un objectif général de solution, comment à ce sujet:
Cela donne une simple alternative définition de la carte:
Nous pouvons une définition similaire pour d'autres foldr fonctions, comme zipWith:
Nouveau dérivant zipWith et zip assez facilement:
Maintenant, si nous utilisons ces fonctions, votre mise en œuvre
devient assez facile:
J'ai écrit un exemple de @geekosaur de
ShowS
approche. Vous pouvez voir de nombreux exemples deShowS
dans le prélude.[modifier] Comme @Dan idée, je l'ai réécrit newRow avec zipWithS.
listS
et++
, compte tenu de la définition de++
c'est la même chose, juste moins pointfree: (++) :: [a] -> [a] -> [a] (++) [] ys = ys (++) (x:xs) ys = x : xs ++ yszipWithS
depuiszipWith
doit passer par chaque élément de toute façon.showString = (++)
dans prélude. Donc newRow peut êtrenewRow xs = ((zipWith (+) xs (0:xs)) ++). (1:)
Vous pouvez représenter une liste en fonction de construire une liste de []
Ensuite, vous pouvez ajouter facilement des listes et ajouter à la fin.
Vous pouvez réécrire zipWith le retour de ces listes partielles:
Et maintenant, vous pouvez écrire ptri comme:
Aller plus loin, voici un one-liner qui est plus symétrique:
Ou c'est plus simple encore:
Ou sans zipWith' (mapAccumR est dans les Données.Liste):