Haskell'de bir grafiği nasıl temsil ediyorsunuz?


125

Haskell'de bir ağacı veya listeyi cebirsel veri türlerini kullanarak temsil etmek yeterince kolaydır. Ama bir grafiği tipografik olarak nasıl temsil edersiniz? Görünüşe göre işaretçilerin olması gerekiyor. Sanırım böyle bir şeye sahip olabilirsin

type Nodetag = String
type Neighbours = [Nodetag]
data Node a = Node a Nodetag Neighbours

Ve bu uygulanabilir olacaktır. Ancak biraz ayrışmış hissediyor; Yapıdaki farklı düğümler arasındaki bağlantılar, bir listedeki mevcut önceki ve sonraki öğeler veya bir ağaçtaki bir düğümün üst ve alt öğeleri arasındaki bağlantılar kadar sağlam "hissettirmiyor". Tanımladığım gibi grafik üzerinde cebirsel manipülasyonlar yapmanın, etiket sistemi aracılığıyla sunulan dolaylı yönlendirme seviyesi tarafından bir şekilde engelleneceğine dair bir önsezim var.

Bu soruyu sormama neden olan şey öncelikle bu şüphe duygusu ve hoşgörüsüzlük algısıdır. Haskell'de grafikleri tanımlamanın daha iyi / matematiksel olarak daha zarif bir yolu var mı? Yoksa doğası gereği sert / temel bir şeye rastladım mı? Özyinelemeli veri yapıları tatlıdır, ancak bu başka bir şey gibi görünüyor. Ağaçların ve listelerin kendine referanslı olduklarından farklı bir anlamda kendine referanslı veri yapısı. Tıpkı listeler ve ağaçların tür düzeyinde kendine referans vermesi gibi, ancak grafikler değer düzeyinde kendi kendine referanslıdır.

Peki gerçekten neler oluyor?


12
Martin Erwig'in işlevsel grafik algoritmaları hakkındaki makalesi ilginizi çekebilir: web.engr.oregonstate.edu/~erwig/papers/abstracts.html#JFP01 . fglPaket bu işten geliştirdi.
John L

99 Haskell sorunlar sayfa gösterileri problem çözme bağlamında kullanılan grafikler bazı örnekler. Ayrıca farklı temsiller hakkında kısa bir giriş vardır.
dopamane

Yanıtlar:


47

Veri yapılarını döngülerle saf bir dilde göstermeyi de garip buluyorum. Gerçekte sorun olan döngülerdir; çünkü değerler paylaşılabilir, türün bir üyesini (listeler ve ağaçlar dahil) içerebilen herhangi bir ADT gerçekten bir DAG'dir (Yönlendirilmiş Asiklik Grafik). Temel sorun, A ve B değerlerine sahipseniz, A'yı içeren B ve B'yi içeren A değerlerine sahipseniz, o zaman ikisi de var olmadan önce yaratılamaz. Haskell tembel olduğu için, bunu aşmak için Düğüm Bağlama olarak bilinen bir numara kullanabilirsiniz , ancak bu beynimi incitir (çünkü henüz çoğunu yapmadım). Şimdiye kadar Mercury'de Haskell'den daha fazla önemli programlamayı yaptım ve Mercury katıdır, bu yüzden düğüm atmanın bir faydası yok.

Sizin de önerdiğiniz gibi, genellikle daha önce bu konuyla karşılaştığımda, ek bir yönlendirmeye başvurdum; genellikle kimliklerden gerçek öğelere bir harita kullanarak ve öğelere sahip olmak, diğer öğeler yerine kimliklere referanslar içerir. Bunu yapmaktan hoşlanmadığım en önemli şey (bariz verimsizliğin yanı sıra), daha kırılgan hissettirmesi, var olmayan bir kimliği aramanın olası hatalarını ortaya çıkarması veya aynı kimliği birden fazla kişiye atamaya çalışmasıydı. öğesi. Bu hatalar oluşmaz, böylece elbette, kod yazmak ve hatta bu yüzden bu tür hatalar tek yer olduğunu soyutlamaların arkasına gizleyebilirsiniz olabilir meydana sınırlandırılır. Ama yine de yanlış yapılması gereken bir şey daha var.

Ancak, "Haskell grafiği" için hızlı bir google beni http://www.haskell.org/haskellwiki/The_Monad.Reader/Issue5/Practical_Graph_Handling'e yönlendirdi , bu okumaya değer bir şey gibi görünüyor.


62

Shang'ın cevabında, tembelliği kullanarak bir grafiğin nasıl temsil edileceğini görebilirsiniz. Bu temsillerle ilgili sorun, değiştirilmelerinin çok zor olmasıdır. Düğüm atma hilesi, yalnızca bir kez bir grafik oluşturacaksanız yararlıdır ve daha sonra asla değişmez.

Uygulamada, grafiğimle gerçekten bir şeyler yapmak istersem, daha yaya temsillerini kullanırım:

  • Kenar listesi
  • Komşuluk listesi
  • Her düğüme benzersiz bir etiket verin, işaretçi yerine etiketi kullanın ve etiketlerden düğümlere kadar sonlu bir harita tutun

Grafiği sık sık değiştirecek veya düzenleyecekseniz, Huet'in fermuarına dayalı bir sunum kullanmanızı tavsiye ederim. Bu, kontrol akış grafikleri için GHC'de dahili olarak kullanılan temsildir. Buradan okuyabilirsiniz:


2
Düğümü bağlamanın bir başka sorunu da, yanlışlıkla çözmenin ve çok fazla yer israf etmenin çok kolay olmasıdır.
hugomg

Tuft'un web sitesinde (en azından şu anda) bir şeyler yanlış görünüyor ve bu bağlantıların hiçbiri şu anda çalışmıyor. Bunlar için bazı alternatif aynalar bulmayı başardım: Huet'in Fermuarına Dayalı Bir Uygulanabilir Kontrol Akışı Grafiği , Hoopl: Veri Akışı Analizi ve Dönüşümü için Modüler, Yeniden Kullanılabilir Bir Kitaplık
gntskn

37

Ben'in bahsettiği gibi, Haskell'deki döngüsel veriler "düğümü bağlama" adlı bir mekanizma tarafından oluşturulur. Uygulamada, bu , karşılıklı olarak yinelemeli kısımlar tembel olarak değerlendirildiği için işe yarayan letveya wherecümleciklerini kullanarak karşılıklı olarak yinelemeli bildirimler yazdığımız anlamına gelir .

Örnek bir grafik türü:

import Data.Maybe (fromJust)

data Node a = Node
    { label    :: a
    , adjacent :: [Node a]
    }

data Graph a = Graph [Node a]

Gördüğünüz gibi Node, dolaylı yoldan yönlendirme yerine gerçek referanslar kullanıyoruz. Bir etiket ilişkilendirmeleri listesinden grafiği oluşturan bir işlevi nasıl uygulayacağınız aşağıda açıklanmıştır.

mkGraph :: Eq a => [(a, [a])] -> Graph a
mkGraph links = Graph $ map snd nodeLookupList where

    mkNode (lbl, adj) = (lbl, Node lbl $ map lookupNode adj)

    nodeLookupList = map mkNode links

    lookupNode lbl = fromJust $ lookup lbl nodeLookupList

Bir (nodeLabel, [adjacentLabel])çift listesi alırız ve gerçek Nodedeğerleri bir ara arama listesi aracılığıyla oluştururuz (bu, gerçek düğüm atmayı yapar). İşin püf noktası, nodeLookupList(türüne sahip olan [(a, Node a)]) kullanılarak inşa edilir mkNode, bu nodeLookupListda bitişik düğümleri bulmak için geri döner .


20
Ayrıca bu veri yapısının grafikleri tanımlayamayacağını da belirtmelisiniz. Sadece onların gelişmelerini anlatıyor. (sonlu uzayda sonsuz açılımlar, ama yine de ...)
açılımlar

1
Vay. Tüm cevapları ayrıntılı olarak inceleyecek vaktim olmadı, ancak bu şekilde tembel değerlendirmeden yararlanmanın ince buz üzerinde kayıyormuşsunuz gibi göründüğünü söyleyeceğim. Sonsuz özyinelemeye geçmek ne kadar kolay olurdu? Hala harika şeyler ve soruda önerdiğim veri türünden çok daha iyi hissettiriyor.
TheIronKnuckle

@TheIronKnuckle, Haskellers'ın her zaman kullandığı sonsuz listelerden çok da farklı değil :)
Justin L.

37

Doğru, grafikler cebirsel değildir. Bu sorunu çözmek için birkaç seçeneğiniz var:

  1. Grafikler yerine sonsuz ağaçları düşünün. Grafikteki döngüleri sonsuz açılımları olarak temsil edin. Bazı durumlarda, yığın içinde bir döngü oluşturarak bu sonsuz ağaçları sonlu uzayda temsil etmek için bile "düğümü bağlama" (buradaki diğer cevapların bazılarında iyi açıklanmıştır) olarak bilinen hileyi kullanabilirsiniz; ancak, bu döngüleri Haskell içinden gözlemleyemez veya tespit edemezsiniz, bu da çeşitli grafik işlemlerini zorlaştırır veya imkansız hale getirir.
  2. Literatürde çeşitli grafik cebirleri mevcuttur. İlk akla gelen, İki Yönlü Grafik Dönüşümlerinin ikinci bölümünde açıklanan grafik yapıcılarının koleksiyonudur . Bu cebirler tarafından garanti edilen olağan özellik, herhangi bir grafiğin cebirsel olarak temsil edilebilmesidir; ancak kritik olarak, birçok grafiğin kanonik bir temsili olmayacaktır . Dolayısıyla eşitliği yapısal olarak kontrol etmek yeterli değildir; bunu doğru bir şekilde yapmak, zor bir problem olduğu bilinen grafik izomorfizmini bulmaya indirgenir.
  3. Cebirsel veri türlerinden vazgeçin; Her bir benzersiz değeri (örneğin, Ints) vererek ve bunlara cebirsel olarak değil dolaylı olarak atıfta bulunarak düğüm kimliğini açıkça temsil edin . Bu, yazıyı soyutlayarak ve sizin için indirmeyi dengeleyen bir arayüz sağlayarak önemli ölçüde daha uygun hale getirilebilir. Bu, örneğin fgl ve Hackage'daki diğer pratik grafik kitaplıkları tarafından benimsenen yaklaşımdır.
  4. Kullanım durumunuza tam olarak uyan yepyeni bir yaklaşım bulun. Bu yapılması çok zor bir şey. =)

Dolayısıyla, yukarıdaki seçeneklerin her birinin artıları ve eksileri vardır. Size en uygun olanı seçin.


"Haskell'in içinden bu döngüleri gözlemleyemeyecek veya saptayamayacaksınız" tam olarak doğru değil - tam da bunu yapmanıza izin veren bir kütüphane var! Cevabımı gör.
Artelius

grafikler artık cebirseldir! hackage.haskell.org/package/algebraic-graphs
Josh.F

17

Birkaç kişi kısaca fglve Martin Erwig'in Endüktif Grafikler ve Fonksiyonel Grafik Algoritmalarından bahsetti. , ancak muhtemelen tümevarımsal temsil yaklaşımının arkasındaki veri türlerine dair bir fikir veren bir cevap yazmaya değer.

Erwig makalesinde aşağıdaki türleri sunar:

type Node = Int
type Adj b = [(b, Node)]
type Context a b = (Adj b, Node, a, Adj b)
data Graph a b = Empty | Context a b & Graph a b

(İçindeki temsil fgl biraz farklıdır ve tip sınıflarını iyi kullanır - ancak fikir temelde aynıdır.)

Erwig, düğümlerin ve kenarların etiketlere sahip olduğu ve tüm kenarların yönlendirildiği bir çoklu grafiğini tanımlıyor. A'nın Nodebir tür etiketi vardır a; bir kenarın bir tür etiketi vardır b. Bir Contextsadece (1) işaret etiketli kenarları bir listesi için , belirli bir düğüm (2), söz konusu düğümün (3) düğümün etiketi, ve (4) işaret etiketli kenarlarının listesinden gelen düğümü. A Graph, daha sonra, endüktif olarak ya Emptyda var Contextolanla birleşmiş (ile &) olarak düşünülebilir Graph.

Erwig belirttiği gibi, özgürce bir üretemez Graphile Emptyve &biz içeren bir liste oluşturabilir olarak, Consve Nilkurucular veya Treebirlikte Leafve Branch. Listelerden farklı olarak (diğerlerinin de bahsettiği gibi), bir kanonik temsili de olmayacak.Graph . Bunlar çok önemli farklılıklardır.

Bununla birlikte, bu gösterimi bu kadar güçlü ve listelerin ve ağaçların tipik Haskell temsillerine çok benzeyen şey, buradaki Graphveri türünün tümevarımsal olarak tanımlanmış olmasıdır . Bir listenin tümevarımsal olarak tanımlanmış olması gerçeği, üzerinde çok kısa ve öz bir şekilde kalıp eşlemesi yapmamıza, tek bir öğeyi işlememize ve listenin geri kalanını yinelemeli olarak işlememize izin veren şeydir; Aynı şekilde, Erwig'in tümevarımlı temsili, bir grafiği birer birer yinelemeli olarak işlememize izin verir Context. Bir grafiğin bu temsili, bir grafik ( gmap) üzerinde haritalama yönteminin basit bir tanımının yanı sıra grafikler üzerinde sırasız kıvrımlar gerçekleştirmenin bir yolunu (ufold ) .

Bu sayfadaki diğer yorumlar harika. Bununla birlikte, bu cevabı yazmamın ana sebebi, "grafikler cebirsel değildir" gibi ifadeleri okuduğumda, bazı okuyucuların kaçınılmaz olarak (hatalı) kimsenin grafikleri temsil etmenin güzel bir yolunu bulamadığı izlenimini edineceğinden korkuyorum. Haskell'de, üzerlerinde desen eşleştirmeye, onları haritalamaya, katlamaya veya genel olarak listeler ve ağaçlarla yapmaya alıştığımız türden havalı, işlevsel şeyler yapmaya izin verecek şekilde.


14

Martin Erwig'in burada okuyabileceğiniz "Endüktif Grafikler ve Fonksiyonel Grafik Algoritmaları" ndaki yaklaşımını her zaman sevmişimdir . FWIW, bir zamanlar bir Scala uygulaması da yazdım, bkz. Https://github.com/nicolast/scalagraphs .


3
Bu konuda genişletmek için çok kabaca, size soyut bir grafik türü üzerinde yapabilirsiniz desen maç verir. Bunun çalışması için gerekli uzlaşma, bir grafiğin tam olarak ayrıştırılma şeklinin benzersiz olmamasıdır, bu nedenle bir model eşleşmesinin sonucu uygulamaya özgü olabilir. Pratikte çok önemli değil. Bu konu hakkında daha fazla bilgi edinmek istiyorsanız, okumayı bozabilecek bir giriş blog yazısı yazdım .
Tikhon Jelvis

Özgürce davranacağım ve Tikhon'un bu begriffs.com/posts/2015-09-04-pure-functional-graphs.html hakkındaki güzel konuşmasını yayınlayacağım .
Martin Capodici

5

Haskell'de grafiklerin temsil edilmesiyle ilgili herhangi bir tartışma, Andy Gill'in data-reify kütüphanesinden bahsetmeye ihtiyaç duyar (işte makale ).

"Düğüm atma" tarzı gösterimi, çok zarif DSL'ler yapmak için kullanılabilir (aşağıdaki örneğe bakın). Bununla birlikte, veri yapısı sınırlı kullanıma sahiptir. Gill'in kütüphanesi size her iki dünyanın da en iyisini sunar. Bir "düğümü bağlayan" DSL kullanabilirsiniz, ancak daha sonra işaretçi tabanlı grafiği etiket tabanlı bir grafiğe dönüştürün, böylece üzerinde tercih ettiğiniz algoritmaları çalıştırabilirsiniz.

İşte basit bir örnek:

-- Graph we want to represent:
--    .----> a <----.
--   /               \
--  b <------------.  \
--   \              \ / 
--    `----> c ----> d

-- Code for the graph:
a = leaf
b = node2 a c
c = node1 d
d = node2 a b
-- Yes, it's that simple!



-- If you want to convert the graph to a Node-Label format:
main = do
    g <- reifyGraph b   --can't use 'a' because not all nodes are reachable
    print g

Yukarıdaki kodu çalıştırmak için aşağıdaki tanımlara ihtiyacınız olacak:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Reify
import Control.Applicative
import Data.Traversable

--Pointer-based graph representation
data PtrNode = PtrNode [PtrNode]

--Label-based graph representation
data LblNode lbl = LblNode [lbl] deriving Show

--Convenience functions for our DSL
leaf      = PtrNode []
node1 a   = PtrNode [a]
node2 a b = PtrNode [a, b]


-- This looks scary but we're just telling data-reify where the pointers are
-- in our graph representation so they can be turned to labels
instance MuRef PtrNode where
    type DeRef PtrNode = LblNode
    mapDeRef f (PtrNode as) = LblNode <$> (traverse f as)

Bunun basit bir DSL olduğunu vurgulamak istiyorum, ancak gökyüzü sınırdır! Bir düğümün bazı çocuklarına bir başlangıç ​​değeri yayınlaması için ağaç benzeri güzel bir sözdizimi ve belirli düğüm türlerini oluşturmak için birçok kolaylık işlevi içeren çok özellikli bir DSL tasarladım. Tabii ki, Node veri türü ve mapDeRef tanımları çok daha fazla ilgiliydi.


2

Buradan alınan bir grafiğin bu uygulamasını seviyorum

import Data.Maybe
import Data.Array

class Enum b => Graph a b | a -> b where
    vertices ::  a -> [b]
    edge :: a -> b -> b -> Maybe Double
    fromInt :: a -> Int -> b
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.