Comment représenter un graphe en Haskell?
Il est assez facile de représenter un arbre ou une liste dans haskell en utilisant des types de données algébriques. Mais comment vous y prendriez-vous à la typographie représentant un graphique? Il semble que vous avez besoin d'avoir des pointeurs. Je devine que vous pourriez avoir quelque chose comme
type Nodetag = String
type Neighbours = [Nodetag]
data Node a = Node a Nodetag Neighbours
Et qui serait réalisable. Cependant, il se sent un peu découplé; les liens entre Les différents nœuds de la structure n'est pas vraiment "sentir" aussi solide que les liens entre le précédent et suivant des éléments dans une liste, ou les parents et les enfants d'un nœud dans un arbre. J'ai un pressentiment que faire des manipulations algébriques sur le graphe que j'ai défini, il serait quelque peu entravée par le niveau d'indirection introduit par le système de tag.
C'est surtout ce sentiment de doute et de la perception de inelegance qui me fait poser cette question. Est-il mieux/plus mathématiquement élégante manière de définir les graphiques en Haskell? Ou ai-je tombé sur quelque chose d'intrinsèquement dur/fondamental? Récursive structures de données sont doux, mais cela semble être quelque chose d'autre. Un auto-référentielle de la structure des données dans un sens différent de la façon dont les arbres et les listes sont auto référentielle. C'est comme les listes et les arbres sont autoréférentiels au niveau du type, mais les graphiques sont auto-référentielle à la valeur de niveau.
Donc ce qui se passe vraiment?
- Vous pourriez être intéressé par Martin Erwig du papier sur les algorithmes sur les graphes: web.engr.oregonstate.edu/~erwig/documents/abstracts.html#JFP01. Le
fgl
package développé à partir de ce. - Le 99 Haskell problèmes de la page montre quelques exemples de graphiques utilisés dans un contexte de résolution de problème. Il a également une courte introduction sur les différentes représentations.
Vous devez vous connecter pour publier un commentaire.
Je trouve aussi qu'il est très difficile d'essayer de représenter des structures de données avec des cycles dans une langue pure. C'est les cycles qui sont vraiment le problème, car les valeurs peuvent être partagés en toute ADT qui peut contenir un membre du type (y compris les listes et les arbres) est vraiment un DAG (Graphe Dirigé Acyclique). Le problème de fond est que si vous avez les valeurs de A et B, avec Un contenant B et B contenant de l'Un, puis l'un ne peut être créée avant que l'autre existe. Parce que Haskell est paresseux, vous pouvez utiliser une astuce connue comme D'attacher le Noeud pour contourner ce problème, mais ce qui rend mon cerveau mal (parce que je n'ai pas fait grand chose encore). J'ai fait plus de mon substantielle de la programmation dans le Mercure de Haskell jusqu'à présent, et le Mercure est de rigueur afin de nouage ne l'aide pas.
Généralement quand j'ai couru dans cette avant, j'ai juste eu recours à d'autres indirection, comme vous êtes suggérant; souvent par l'utilisation d'une carte à partir des identifiants pour les éléments réels, et d'avoir des éléments de contenir des références à l'id plutôt qu'à d'autres éléments. La principale chose que je n'aime pas cela (à part l'évidente inefficacité), c'est qu'il se sentait plus fragiles, présentant les erreurs possibles de la recherche d'un id qui n'existe pas ou qui tentent d'assigner le même id à plus d'un élément. Vous pouvez écrire du code pour que ces erreurs ne se produisent pas, bien sûr, et même de le cacher derrière des abstractions, de sorte que les seuls endroits où de telles erreurs pourrait se sont bornées. Mais c'est encore une chose de plus à se tromper.
Cependant, un rapide google pour "Haskell graphique" m'a conduit à http://www.haskell.org/haskellwiki/The_Monad.Reader/Issue5/Practical_Graph_Handling, qui ressemble à une la peine d'être lu.
Shang de réponse, vous pouvez voir comment représenter un graphe à l'aide de la paresse. Le problème avec ces représentations, c'est qu'ils sont très difficiles à changer. Le nouage astuce est utile seulement si vous avez l'intention de construire un graphe une fois, et par la suite, il ne change jamais.
Dans la pratique, dois-je réellement envie de ne quelque chose avec mon graphique, j'utilise le plus de piétons représentations:
Si vous allez être en train de changer ou de modifier le graphique fréquemment, je recommande d'utiliser une représentation basée sur Huet de la fermeture à glissière. C'est la représentation utilisée en interne dans GHC pour le contrôle de flux de graphiques. Vous pouvez lire à ce sujet ici:
Un Applicatif de Contrôle Graphique de Flux basé sur Huet de la fermeture à Glissière
Hoopl: Modulaire, Bibliothèque Réutilisable pour des Flux de données, l'Analyse et la Transformation
Ben mentionné, les données cyclique en Haskell est construit par un mécanisme appelé "d'attacher le noeud". Dans la pratique, cela signifie que nous écrivons mutuellement récursives déclarations à l'aide de
let
ouwhere
clauses, qui fonctionne parce que l'mutuellement récursives pièces sont paresseusement évalué.Voici un exemple du type de graphique:
Comme vous pouvez le voir, nous utilisons réelle
Node
références au lieu d'indirection. Voici comment implémenter une fonction qui construit le graphe à partir d'une liste de label associations.Nous prendre dans une liste de
(nodeLabel, [adjacentLabel])
paires et de construire le réelNode
valeurs par le biais d'un intermédiaire pour la recherche de la liste (qui ne le réel de nœuds). Le truc, c'est quenodeLookupList
(qui a le type[(a, Node a)]
) est construit à l'aidemkNode
, qui, à son tour, renvoie à l'nodeLookupList
de trouver les nœuds adjacents.C'est vrai, les graphiques ne sont pas algébriques. Pour résoudre ce problème, vous avez deux options:
Int
s) et, se référant à eux indirectement plutôt que algébriquement. Cela peut être fait beaucoup plus pratique en rendant le type abstrait et une interface qui jongle avec les indirection pour vous. C'est l'approche adoptée par la, par exemple, fgl et d'autres pratiques graphique bibliothèques sur le Hackage.Il y a donc des avantages et des inconvénients à chacun des choix ci-dessus. Choisissez celle qui vous semble la meilleure pour vous.
J'ai toujours aimé Martin Erwig dans une démarche "Inductive Graphiques et Fonctionnelles Algorithmes de graphes", dont vous pouvez lire ici. FWIW, une fois, j'ai écrit un Scala de mise en œuvre aussi bien, voir https://github.com/nicolast/scalagraphs.
Quelques autres ont brièvement mentionné
fgl
et Martin Erwig de Inductive Graphes et Algorithmes sur les graphes Fonctionnels, mais c'est probablement la peine de rédiger une réponse qui effectivement donne une idée des types de données derrière le inductive de la représentation de l'approche.Dans son papier, Erwig présente les types suivants:
(La représentation dans
fgl
est légèrement différente, et fait bon usage de typeclasses - mais l'idée est essentiellement la même.)Erwig décrit un multigraph dans lequel les nœuds et les arêtes ont des étiquettes, et dont toutes les arêtes sont orientées. Un
Node
a une étiquette de certains type dea
; une arête a une étiquette de certains type deb
. UnContext
est tout simplement (1) une liste de étiquetés bords de pointage à un nœud particulier, (2) le nœud en question, (3) le nœud de l'étiquette, et (4) la liste de étiquetés bords de pointage de le nœud. UnGraph
peut alors être conçu de façon inductive commeEmpty
, ou comme unContext
fusionné (avec&
) dans unGraph
.Comme Erwig notes, nous ne pouvons pas librement générer un
Graph
avecEmpty
et&
, comme on pourrait générer une liste avec lesCons
etNil
les constructeurs, ou unTree
avecLeaf
etBranch
. Aussi, contrairement aux listes (comme d'autres l'ont mentionné), il n'y a pas de représentation canonique d'uneGraph
. Ce sont des différences cruciales.Néanmoins, ce qui fait de cette représentation si puissant et si semblable à la moyenne de Haskell représentations de listes et les arbres, c'est que le
Graph
type de données est ici inductif défini. Le fait qu'une liste est défini inductivement est ce qui nous permet donc de façon succincte, en correspondance du modèle sur elle, processus d'un seul élément, et de manière récursive processus le reste de la liste; de même, Erwig inductive de la représentation nous permet de nous traitez de manière récursive un graphique unContext
à la fois. Cette représentation d'un graphe se prête à une simple définition d'un moyen de la carte sur un graphique (gmap
), ainsi que d'un moyen pour effectuer non ordonnée plis sur les graphes (ufold
).Les autres commentaires sur cette page sont grands. La principale raison que j'ai écrit cette réponse, cependant, est que quand je lis des phrases telles que "les graphiques ne sont pas algébriques," j'ai peur que certains lecteurs vont inévitablement venir de loin avec la (fausse) impression que personne n'est trouvé une belle manière de représenter des graphes en Haskell d'une façon qui permet le filtrage sur eux, de la cartographie sur eux, les plier et, plus généralement, faire le tri de cool fonctionnel des choses que nous sommes habitués avec les listes et les arbres.
Toute discussion de représentation des graphes en Haskell a besoin d'une mention d'Andy Gill est données-réification de la bibliothèque (ici est le papier).
Le "liant-le-noeud" le style de représentation peuvent être utilisés pour faire très élégant DSLs (voir exemple ci-dessous). Cependant, la structure des données est d'une utilité limitée. Gill de la bibliothèque vous offre le meilleur des deux mondes. Vous pouvez utiliser un "d'attacher le noeud" DSL, mais ensuite convertir le pointeur de la courbe dans une étiquette à base de graphe de sorte que vous pouvez exécuter votre algorithmes de choix sur celui-ci.
Voici un exemple simple:
Pour exécuter le code ci-dessus, vous devrez les définitions suivantes:
Je tiens à souligner que c'est simpliste DSL, mais le ciel est la limite! J'ai conçu un plein de fonctionnalités très DSL, y compris une belle forme d'arbre de syntaxe pour avoir un nœud de diffusion d'une valeur initiale de certains de ses enfants, et beaucoup de fonctions de confort pour la construction spécifique des types de nœuds. Bien sûr, le Nœud de type de données et mapDeRef définitions sont beaucoup plus impliqués.
J'aime cette mise en œuvre d'un graphe prises de ici