आप हास्केल में एक ग्राफ का प्रतिनिधित्व कैसे करते हैं?


125

बीजगणितीय डेटा प्रकारों का उपयोग करके हैस्केल में एक पेड़ या सूची का प्रतिनिधित्व करना काफी आसान है। लेकिन आप एक ग्राफ का प्रतिनिधित्व करते हुए टाइपोग्राफिक रूप से कैसे जाएंगे? ऐसा लगता है कि आपको संकेत करने की आवश्यकता है। मुझे लगता है कि तुम कुछ पसंद कर सकते हैं

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

और यह व्यावहारिक होगा। हालाँकि यह थोड़ा विघटित महसूस करता है; संरचना में अलग-अलग नोड्स के बीच के लिंक वास्तव में "महसूस" नहीं करते हैं क्योंकि सूची में वर्तमान पिछले और अगले तत्वों के बीच के लिंक, या एक पेड़ में एक नोड के माता-पिता और बच्चे। मेरे पास एक कूबड़ है जो ग्राफ पर बीजगणितीय जोड़तोड़ कर रहा है जैसा कि मैंने परिभाषित किया था कि यह टैग सिस्टम के माध्यम से शुरू किए गए अप्रत्यक्ष के स्तर से कुछ हद तक बाधित होगा।

यह मुख्य रूप से संदेह की भावना और असमानता की धारणा है जो मुझे यह सवाल पूछने का कारण बनता है। क्या हास्केल में रेखांकन को परिभाषित करने का एक बेहतर / अधिक गणितीय तरीका है? या क्या मैंने कुछ मुश्किल / मौलिक पर ठोकर खाई है? पुनरावर्ती डेटा संरचनाएं मीठी हैं, लेकिन यह कुछ और ही प्रतीत होता है। एक अलग संदर्भ में एक स्व-संदर्भात्मक डेटा संरचना कैसे पेड़ों और सूचियों को आत्म-संदर्भित किया जाता है। यह सूची की तरह है और पेड़ प्रकार के स्तर पर आत्म संदर्भित हैं, लेकिन मूल्य स्तर पर रेखांकन स्वयं संदर्भित हैं।

तो वास्तव में क्या हो रहा है?


12
आप कार्यात्मक ग्राफ़ एल्गोरिदम पर मार्टिन एर्विग के पेपर में रुचि रख सकते हैं: web.engr.oregonstate.edu/~erwig/papers/abstracts.html#JFP01 । इससे fglपैकेज विकसित हुआ।
जॉन एल

99 हास्केल समस्याओं पेज से पता चलता है एक समस्या को हल करने संदर्भ में प्रयोग किया रेखांकन के कुछ उदाहरण। विभिन्न अभ्यावेदन पर इसका संक्षिप्त परिचय भी है।
डोपामने

जवाबों:


47

मुझे यह भी अजीब लगता है कि एक शुद्ध भाषा में चक्रों के साथ डेटा संरचनाओं का प्रतिनिधित्व करने का प्रयास करें। यह चक्र है कि वास्तव में समस्या है; क्योंकि मूल्यों को किसी भी एडीटी से साझा किया जा सकता है जिसमें प्रकार का सदस्य शामिल हो सकता है (सूचियों और पेड़ों सहित) वास्तव में एक डीएजी (निर्देशित चक्रीय ग्राफ) है। मूल मुद्दा यह है कि यदि आपके पास ए और बी के मूल्य हैं, जिसमें ए और बी युक्त ए है, तो दूसरे के अस्तित्व में आने से पहले न तो बनाया जा सकता है। क्योंकि हास्केल आलसी है आप इस के आसपास पाने के लिए टिंट द नॉट के रूप में जानी जाने वाली ट्रिक का उपयोग कर सकते हैं , लेकिन इससे मेरे मस्तिष्क को चोट पहुंचती है (क्योंकि मैंने अभी तक ऐसा नहीं किया है)। मैंने अब तक हस्केल की तुलना में पारा में अपनी पर्याप्त प्रोग्रामिंग की है, और पारा सख्त है इसलिए गाँठ बांधना मदद नहीं करता है।

आमतौर पर जब मैंने इसमें भाग लिया है, इससे पहले कि मैं सिर्फ अतिरिक्त अप्रत्यक्ष का सहारा लेता हूं, जैसा कि आप सुझाव दे रहे हैं; आईडी से वास्तविक तत्वों में मानचित्र का उपयोग करके, और तत्वों में अन्य तत्वों के बजाय आईडी के संदर्भ होते हैं। मुख्य बात जो मुझे करना पसंद नहीं था (स्पष्ट अक्षमता से अलग) यह है कि यह अधिक नाजुक लगा, एक ऐसी आईडी को देखने की संभावित त्रुटियों का परिचय देना जो मौजूद नहीं है या एक ही आईडी को एक से अधिक में असाइन करने का प्रयास नहीं कर रहा है। तत्व। आप कोड लिख सकते हैं, ताकि ये त्रुटियां न हों, और यहां तक ​​कि इसे अमूर्तता के पीछे छिपा दें, ताकि ऐसी गलतियां हो सकती हैं। लेकिन यह अभी भी एक और बात गलत है।

हालाँकि, "हास्केल ग्राफ" के लिए एक त्वरित गूगल ने मुझे http://www.haskell.org/haskellwiki/The_Monad.Reader/Issue5/Practical_Graph_Handling पर ले गया , जो एक पढ़ने योग्य लगता है।


62

शंग के उत्तर में आप देख सकते हैं कि आलस्य का उपयोग करते हुए एक ग्राफ का प्रतिनिधित्व कैसे करें। इन अभ्यावेदन के साथ समस्या यह है कि उन्हें बदलना बहुत मुश्किल है। गाँठ बांधने की ट्रिक केवल तभी उपयोगी होती है जब आप एक बार एक ग्राफ बनाने जा रहे हों, और उसके बाद यह कभी नहीं बदलता है।

अभ्यास में, मैं वास्तव में करना चाहते हैं चाहिए करना मेरी ग्राफ के साथ कुछ, मैं और अधिक पैदल यात्री अभ्यावेदन का उपयोग करें:

  • एज लिस्ट
  • आसन्न सूची
  • प्रत्येक नोड को एक अनूठा लेबल दें, एक पॉइंटर के बजाय लेबल का उपयोग करें, और लेबल से नोड्स तक एक बारीक नक्शा रखें

यदि आप बार-बार ग्राफ़ बदलने या संपादित करने जा रहे हैं, तो मैं Huet के ज़िप के आधार पर एक प्रतिनिधित्व का उपयोग करने की सलाह देता हूं। यह नियंत्रण-प्रवाह ग्राफ़ के लिए GHC में आंतरिक रूप से उपयोग किया जाने वाला प्रतिनिधित्व है। आप इसके बारे में यहां पढ़ सकते हैं:


2
गाँठ बांधने के साथ एक और समस्या यह है कि गलती से इसे खोलना और बहुत सारे स्थान को बर्बाद करना बहुत आसान है।
ह्यूगॉम

ऐसा लगता है कि Tuft की वेबसाइट के साथ कुछ गलत है (कम से कम इस समय), और दोनों में से कोई भी लिंक वर्तमान में काम नहीं करता है। मैंने इनके लिए कुछ वैकल्पिक दर्पण खोजने में कामयाबी हासिल की है: Huet's Zipper , Hoopl: A Modular, Reusable Library for Dataflow एनालिसिस एंड ट्रांसफॉर्मेशन
gntskn

37

जैसा कि बेन ने उल्लेख किया है, हास्केल में चक्रीय डेटा "गाँठ बांधने" नामक एक तंत्र द्वारा निर्मित है। व्यवहार में, इसका मतलब है कि हम पारस्परिक रूप से पुनरावर्ती घोषणाओं का उपयोग करते हुए letया whereखंड लिखते हैं, जो काम करता है क्योंकि पारस्परिक रूप से पुनरावर्ती भागों का आलसी मूल्यांकन किया जाता है।

यहाँ एक उदाहरण ग्राफ प्रकार है:

import Data.Maybe (fromJust)

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

data Graph a = Graph [Node a]

जैसा कि आप देख सकते हैं, हम Nodeअप्रत्यक्ष के बजाय वास्तविक संदर्भ का उपयोग करते हैं । यहां एक फ़ंक्शन को लागू करने का तरीका बताया गया है जो लेबल संघों की सूची से ग्राफ़ का निर्माण करता है।

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

हम (nodeLabel, [adjacentLabel])जोड़े की एक सूची में लेते हैं और Nodeएक मध्यवर्ती लुक-लिस्ट (जो वास्तविक गाँठ बांधते हैं) के माध्यम से वास्तविक मूल्यों का निर्माण करते हैं । चाल वह है nodeLookupList(जिसका प्रकार है [(a, Node a)]) का उपयोग करके बनाया गया है mkNode, जो बदले nodeLookupListमें आसन्न नोड्स को खोजने के लिए वापस संदर्भित करता है ।


20
आपको यह भी उल्लेख करना चाहिए कि यह डेटा संरचना रेखांकन का वर्णन करने में सक्षम नहीं है। यह केवल उनके खुलासे का वर्णन करता है। (परिमित अंतरिक्ष में अनंत खुलासा, लेकिन अभी भी ...)
रॉटसॉर

1
वाह। मेरे पास सभी उत्तरों की विस्तार से जांच करने का समय नहीं है, लेकिन मैं कहूंगा कि इस तरह से आलसी मूल्यांकन का दोहन करना चाहिए जैसे कि आप पतली बर्फ पर स्केटिंग करेंगे। अनंत पुनरावृत्ति में फिसलना कितना आसान होगा? अभी भी भयानक सामान, और सवाल में प्रस्तावित डेटाटाइप की तुलना में बहुत बेहतर लगता है।
theIronKnuckle

@ TheIronKnuckle अनंत सूचियों की तुलना में बहुत अधिक अंतर नहीं है जो हास्केलर हर समय उपयोग करते हैं :)
जस्टिन एल।

37

यह सच है, रेखांकन बीजगणितीय नहीं हैं। इस समस्या से निपटने के लिए, आपके पास कुछ विकल्प हैं:

  1. रेखांकन के बजाय, अनंत पेड़ों पर विचार करें। ग्राफ में चक्रों को उनके अनफॉलो अनफॉलो के रूप में देखें। कुछ मामलों में, आप "गाँठ बांधने" के रूप में ज्ञात चाल का उपयोग कर सकते हैं (यहां कुछ अन्य उत्तरों में अच्छी तरह से समझाया गया है) यहां तक ​​कि ढेर में एक चक्र बनाकर इन अनंत पेड़ों का परिमित स्थान में प्रतिनिधित्व करते हैं; हालांकि, आप हास्केल के भीतर से इन चक्रों का निरीक्षण या पता लगाने में सक्षम नहीं होंगे, जो विभिन्न प्रकार के ग्राफ़ संचालन को मुश्किल या असंभव बनाता है।
  2. साहित्य में विभिन्न प्रकार के ग्राफ अल्जेब्रा उपलब्ध हैं। जो सबसे पहले दिमाग में आता है वह है द्विदिशीकृत ग्राफ ट्रांसफॉर्मेशन के खंड दो में वर्णित ग्राफ निर्माणकर्ताओं का संग्रह । इन बीजगणितों द्वारा गारंटीकृत सामान्य संपत्ति यह है कि किसी भी ग्राफ को बीजगणितीय रूप से दर्शाया जा सकता है; हालाँकि, गंभीर रूप से, कई ग्राफ़ में एक विहित प्रतिनिधित्व नहीं होगा । इसलिए समानता की जांच करना संरचनात्मक रूप से पर्याप्त नहीं है; यह सही ढंग से ग्राफ isomorphism खोजने के लिए नीचे फोड़े - एक कठिन समस्या के कुछ होने के लिए जाना जाता है।
  3. बीजगणितीय डेटाेटिप्स पर छोड़ दें; स्पष्ट रूप से उन्हें प्रत्येक विशिष्ट मान (कहना, Int) देकर और बीजगणित के बजाय अप्रत्यक्ष रूप से संदर्भित करके नोड पहचान का प्रतिनिधित्व करते हैं । यह टाइप एब्सट्रैक्ट बनाकर और आपके लिए इनडायरेक्शन को आसान बनाने वाला इंटरफेस प्रदान करके काफी सुविधाजनक बनाया जा सकता है। यह दृष्टिकोण है, उदाहरण के लिए, Hackage पर fgl और अन्य व्यावहारिक ग्राफ पुस्तकालयों।
  4. बिल्कुल नए दृष्टिकोण के साथ आएं जो आपके उपयोग के मामले में बिल्कुल फिट बैठता है। यह बहुत मुश्किल काम है। =)

इसलिए उपरोक्त विकल्पों में से प्रत्येक के लिए पेशेवरों और विपक्ष हैं। वह चुनें जो आपके लिए सबसे अच्छा लगता है।


"आप हास्केल के भीतर से इन चक्रों का निरीक्षण या पता लगाने में सक्षम नहीं होंगे" यह बिल्कुल सच नहीं है - एक पुस्तकालय है जो आपको बस इतना ही देता है! मेरा जवाब देखिए।
आर्टेलियस

रेखांकन अब बीजगणितीय हैं! hackage.haskell.org/package/algebraic-graphs
जोश.फूल

16

कुछ अन्य लोगों ने संक्षेप में उल्लेख किया है fglऔर मार्टिन एर्विग के प्रेरक रेखांकन और कार्यात्मक ग्राफ एल्गोरिदम , लेकिन यह शायद एक जवाब लिखने के लायक है जो वास्तव में प्रेरक प्रतिनिधित्व दृष्टिकोण के पीछे डेटा प्रकारों की भावना देता है।

अपने कागज में, Erwig निम्नलिखित प्रकार प्रस्तुत करता है:

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

(इसमें प्रतिनिधित्व fglथोड़ा अलग है, और टाइपकास्ट का अच्छा उपयोग करता है - लेकिन विचार अनिवार्य रूप से समान है।)

एर्विग एक मल्टीग्राफ का वर्णन कर रहा है जिसमें नोड्स और किनारों पर लेबल हैं, और जिसमें सभी किनारों को निर्देशित किया गया है। ए Nodeमें कुछ प्रकार का एक लेबल है a; एक किनारे पर किसी प्रकार का एक लेबल होता है b। एक Contextबस (1) की ओर इशारा करते लेबल किनारों की एक सूची है करने के लिए एक विशेष नोड, (2) सवाल में नोड, (3) नोड के लेबल, और (4) की ओर इशारा करते लेबल किनारों की सूची से नोड। ए Graphको तब Emptyया तो विद्यमान रूप से या मौजूदा में Contextविलय (साथ &) के रूप में कल्पना की जा सकती है Graph

Erwig नोटों के रूप में, हम स्वतंत्र रूप से एक उत्पन्न नहीं कर सकते Graphके साथ Emptyऔर &, जैसा कि हम साथ एक सूची उत्पन्न हो सकता है Consऔर Nilकंस्ट्रक्टर, या एक Treeसाथ Leafऔर Branch। बहुत, सूचियों के विपरीत (जैसा कि अन्य लोगों ने उल्लेख किया है), वहाँ कोई भी कैनोनिकल प्रतिनिधित्व नहीं किया जा रहा है Graph। ये महत्वपूर्ण अंतर हैं।

फिर भी, जो इस प्रतिनिधित्व को इतना शक्तिशाली बनाता है, और इसलिए सूचियों और पेड़ों के विशिष्ट हास्केल अभ्यावेदन के समान है, यह है कि Graphयहाँ डेटाटाइप को परिभाषित किया गया है । तथ्य यह है कि एक सूची को आनुपातिक रूप से परिभाषित किया गया है, जो हमें उस पर इतने व्यापक रूप से मेल खाने की अनुमति देता है, एक ही तत्व की प्रक्रिया करता है, और बाकी की सूची की पुनरावृत्ति करता है; समान रूप से, एर्विग का प्रेरक प्रतिनिधित्व हमें Contextएक समय में एक ग्राफ को पुन: संसाधित करने की अनुमति देता है । एक ग्राफ का यह प्रतिनिधित्व ग्राफ ( gmap) पर मैप करने के तरीके की एक सरल परिभाषा के साथ-साथ ग्राफ़ पर अनियंत्रित सिलवटों को निष्पादित करने का एक तरीका है ufold

इस पृष्ठ पर अन्य टिप्पणियाँ बहुत अच्छी हैं। मुख्य कारण मैंने यह उत्तर लिखा है, हालांकि, जब मैंने वाक्यांशों को पढ़ा है जैसे कि "रेखांकन बीजगणितीय नहीं हैं," मुझे डर है कि कुछ पाठक अनिवार्य रूप से (गलत) धारणा के साथ आएंगे कि किसी को ग्राफ़ का प्रतिनिधित्व करने का एक अच्छा तरीका नहीं मिला है हास्केल एक तरह से है कि उन पर मेल खाते हुए पैटर्न की अनुमति देता है, उन पर मैपिंग करता है, उन्हें तह करता है, या आम तौर पर शांत, कार्यात्मक सामान की तरह करता है जिसका उपयोग हम सूचियों और पेड़ों के साथ करने के लिए करते हैं।


14

मुझे हमेशा "Inductive Graphs and Functional Graph Algorithms" में मार्टिन Erwig का दृष्टिकोण पसंद आया, जिसे आप यहाँ पढ़ सकते हैं । FWIW, मैंने एक बार एक स्काला कार्यान्वयन भी लिखा था, https://github.com/nicolast/scalagraphs देखें


3
बहुत मोटे तौर पर इस पर विस्तार करने के लिए , यह आपको एक सार ग्राफ प्रकार देता है, जिस पर आप मिलान कर सकते हैं। इस काम को करने के लिए आवश्यक समझौता यह है कि जिस तरह से एक ग्राफ विघटित किया जा सकता है वह अद्वितीय नहीं है, इसलिए एक पैटर्न मैच का परिणाम कार्यान्वयन-विशिष्ट हो सकता है। यह व्यवहार में कोई बड़ी बात नहीं है। यदि आप इसके बारे में अधिक जानने के लिए उत्सुक हैं, तो मैंने एक परिचयात्मक ब्लॉग पोस्ट लिखी है, जो पढ़ने में विकट हो सकती है।
तिखन जेल्विस

मैं एक आज़ादी लूँगा और टिखन की अच्छी बात को इस begriffs.com/posts/2015-09-04-pure-functional-graphs.html पर पोस्ट करूँगा
मार्टिन कैपोडिसी

5

हास्केल में रेखांकन का प्रतिनिधित्व करने की किसी भी चर्चा में एंडी गिल के डेटा-रिवाइज लाइब्रेरी (यहां कागज है ) का उल्लेख है ।

"टाईइंग-द-नॉट" स्टाइल प्रतिनिधित्व का उपयोग बहुत ही सुंदर डीएसएल बनाने के लिए किया जा सकता है (नीचे उदाहरण देखें)। हालाँकि, डेटा संरचना सीमित उपयोग की है। गिल की लाइब्रेरी आपको दोनों दुनिया की सबसे अच्छी अनुमति देती है। आप "गाँठ बांधने" डीएसएल का उपयोग कर सकते हैं, लेकिन फिर सूचक-आधारित ग्राफ़ को लेबल-आधारित ग्राफ़ में परिवर्तित कर सकते हैं ताकि आप उस पर अपनी पसंद के एल्गोरिदम चला सकें।

ये रहा एक सरल उदाहरण:

-- 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

उपरोक्त कोड चलाने के लिए आपको निम्नलिखित परिभाषाओं की आवश्यकता होगी:

{-# 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)

मैं तनाव देना चाहता हूं कि यह एक सरल डीएसएल है, लेकिन आकाश की सीमा है! मैंने एक बहुत ही शानदार डीएसएल डिजाइन किया है, जिसमें एक अच्छा पेड़ जैसा सिंटैक्स शामिल है, जिसमें एक नोड अपने कुछ बच्चों के लिए प्रारंभिक मूल्य प्रसारित करता है, और विशिष्ट नोड प्रकारों के निर्माण के लिए कई सुविधा कार्य करता है। बेशक, नोड डेटा प्रकार और mapDeRef परिभाषाएँ बहुत अधिक शामिल थीं।


2

मुझे यहाँ से लिया गया ग्राफ़ का यह कार्यान्वयन पसंद है

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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.