Cukup mudah untuk merepresentasikan pohon atau daftar di haskell menggunakan tipe data aljabar. Tapi bagaimana Anda akan merepresentasikan grafik secara tipografis? Sepertinya Anda perlu memiliki petunjuk. Saya menduga Anda bisa mendapatkan sesuatu seperti
type Nodetag = String
type Neighbours = [Nodetag]
data Node a = Node a Nodetag Neighbours
Dan itu bisa diterapkan. Namun itu terasa agak terpisah; Hubungan antara node yang berbeda dalam struktur tidak benar-benar "terasa" sekokoh link antara elemen sebelumnya dan selanjutnya dalam daftar, atau orang tua dan turunan dari node dalam pohon. Saya memiliki firasat bahwa melakukan manipulasi aljabar pada grafik seperti yang saya definisikan akan terhalang oleh tingkat tipuan yang diperkenalkan melalui sistem tag.
Perasaan ragu-ragu dan persepsi tentang ketidaksengajaan inilah yang menyebabkan saya mengajukan pertanyaan ini. Apakah ada cara yang lebih baik / lebih elegan secara matematis untuk mendefinisikan grafik di Haskell? Atau apakah saya tersandung pada sesuatu yang secara inheren keras / mendasar? Struktur data rekursif memang bagus, tetapi tampaknya ini adalah sesuatu yang lain. Struktur data referensi mandiri dalam arti yang berbeda tentang bagaimana pohon dan daftar merujuk pada diri sendiri. Ini seperti daftar dan pohon yang merujuk pada diri sendiri pada tingkat jenis, tetapi grafik merujuk pada diri sendiri pada tingkat nilai.
Jadi apa yang sebenarnya terjadi?
sumber
fgl
paket yang dikembangkan dari ini.Jawaban:
Saya juga merasa canggung untuk mencoba merepresentasikan struktur data dengan siklus dalam bahasa murni. Siklus itulah yang menjadi masalah; karena nilai dapat dibagi ADT apa pun yang dapat berisi anggota jenis (termasuk daftar dan pohon) benar-benar DAG (Grafik Acyclic Terarah). Masalah mendasar adalah jika Anda memiliki nilai A dan B, dengan A berisi B dan B berisi A, maka tidak ada yang dapat dibuat sebelum yang lain ada. Karena Haskell malas, Anda bisa menggunakan trik yang dikenal sebagai Tying the Knot untuk menyiasatinya, tetapi itu membuat otak saya sakit (karena saya belum melakukan banyak hal). Sejauh ini, saya telah melakukan lebih banyak pemrograman substansial saya di Mercury daripada Haskell, dan Mercury sangat ketat sehingga mengikat simpul tidak membantu.
Biasanya ketika saya mengalami hal ini sebelumnya, saya baru saja menggunakan tipuan tambahan, seperti yang Anda sarankan; seringkali dengan menggunakan peta dari id ke elemen sebenarnya, dan memiliki elemen yang berisi referensi ke id, bukan ke elemen lain. Hal utama yang saya tidak suka melakukan itu (selain dari ketidakefisienan yang jelas) adalah rasanya lebih rapuh, memperkenalkan kemungkinan kesalahan mencari id yang tidak ada atau mencoba menetapkan id yang sama ke lebih dari satu elemen. Anda dapat menulis kode sehingga kesalahan ini tidak akan terjadi, tentu saja, dan bahkan menyembunyikannya di balik abstraksi sehingga satu-satunya tempat di mana kesalahan dapat terjadi dibatasi. Tapi masih ada satu hal lagi yang salah.
Namun, Google cepat untuk "grafik Haskell" membawa saya ke http://www.haskell.org/haskellwiki/The_Monad.Reader/Issue5/Practical_Graph_Handling , yang terlihat seperti bacaan yang bermanfaat.
sumber
Dalam jawaban shang Anda dapat melihat bagaimana merepresentasikan grafik menggunakan kemalasan. Masalah dengan representasi ini adalah bahwa mereka sangat sulit untuk diubah. Trik mengikat simpul hanya berguna jika Anda akan membuat grafik sekali, dan setelah itu tidak pernah berubah.
Dalam praktiknya, jika saya benar-benar ingin melakukan sesuatu dengan grafik saya, saya menggunakan representasi pejalan kaki yang lebih banyak:
Jika Anda akan sering mengubah atau mengedit grafik, saya sarankan menggunakan representasi berdasarkan ritsleting Huet. Ini adalah representasi yang digunakan secara internal di GHC untuk grafik aliran kontrol. Anda dapat membacanya di sini:
Grafik Aliran Kontrol Aplikatif berdasarkan Ritsleting Huet
Hoopl: Perpustakaan Modular, Dapat Digunakan Kembali untuk Analisis Aliran Data dan Transformasi
sumber
Seperti yang Ben sebutkan, data siklik di Haskell dibangun oleh mekanisme yang disebut "mengikat simpul". Dalam praktiknya, ini berarti bahwa kita menulis deklarasi yang saling rekursif menggunakan klausa
let
atauwhere
, yang berfungsi karena bagian yang saling rekursif dievaluasi secara malas.Berikut adalah contoh jenis grafik:
Seperti yang Anda lihat, kami menggunakan
Node
referensi aktual alih-alih tipuan. Berikut cara menerapkan fungsi yang menyusun grafik dari daftar asosiasi label.Kami mengambil daftar
(nodeLabel, [adjacentLabel])
pasangan dan membangun nilai aktualNode
melalui daftar pencarian menengah (yang melakukan ikatan simpul yang sebenarnya). Triknya adalah bahwanodeLookupList
(yang memiliki tipe[(a, Node a)]
) dibangun menggunakanmkNode
, yang pada gilirannya merujuk kembali kenodeLookupList
untuk menemukan node yang berdekatan.sumber
Memang benar, grafik bukanlah aljabar. Untuk mengatasi masalah ini, Anda memiliki beberapa opsi:
Int
s) dan merujuknya secara tidak langsung daripada secara aljabar. Ini bisa dibuat jauh lebih nyaman dengan membuat tipe abstrak dan menyediakan antarmuka yang menyulap tipuan untuk Anda. Ini adalah pendekatan yang diambil oleh, misalnya, fgl dan pustaka grafik praktis lainnya di Hackage.Jadi ada pro dan kontra untuk setiap pilihan di atas. Pilih salah satu yang menurut Anda terbaik.
sumber
Beberapa orang lain telah secara singkat menyebutkan
fgl
dan Martin Erwig's Inductive Graphs and Functional Graph Algorithms , tetapi mungkin ada baiknya menulis jawaban yang benar-benar memberikan gambaran tentang tipe data di balik pendekatan representasi induktif.Dalam makalahnya, Erwig memaparkan jenis-jenis berikut:
(Representasi dalam
fgl
sedikit berbeda, dan memanfaatkan kelas tipe dengan baik - tetapi idenya pada dasarnya sama.)Erwig mendeskripsikan multigraph di mana node dan edge memiliki label, dan di mana semua edge diarahkan. A
Node
memiliki label dari beberapa jenisa
; tepi memiliki label dari beberapa jenisb
. AContext
hanyalah (1) daftar tepi berlabel yang menunjuk ke simpul tertentu, (2) simpul yang dimaksud, (3) label simpul, dan (4) daftar tepi berlabel yang menunjuk dari simpul. AGraph
kemudian dapat dipahami secara induktif sebagai salah satuEmpty
, atau sebagaiContext
digabungkan (dengan&
) menjadi yang adaGraph
.Sebagai catatan Erwig, kita tidak dapat dengan bebas menghasilkan
Graph
denganEmpty
dan&
, karena kita mungkin menghasilkan daftar dengan konstruktorCons
danNil
, atauTree
denganLeaf
danBranch
. Juga, tidak seperti daftar (seperti yang telah disebutkan orang lain), tidak akan ada representasi kanonik dari aGraph
. Ini adalah perbedaan penting.Meskipun demikian, apa yang membuat representasi ini begitu kuat, dan sangat mirip dengan representasi daftar dan pohon Haskell yang khas, adalah bahwa
Graph
tipe data di sini didefinisikan secara induktif . Fakta bahwa daftar didefinisikan secara induktif adalah hal yang memungkinkan kita untuk mencocokkan pola dengan begitu ringkas, memproses satu elemen, dan secara rekursif memproses sisa daftar; sama, representasi induktif Erwig memungkinkan kita memproses grafik satuContext
per satu secara rekursif . Representasi grafik ini cocok untuk definisi sederhana tentang cara memetakan di atas grafik (gmap
), serta cara untuk melakukan lipatan tak berurutan di atas grafik (ufold
).Komentar lain di halaman ini bagus. Namun, alasan utama saya menulis jawaban ini adalah bahwa ketika saya membaca frasa seperti "grafik bukan aljabar", saya khawatir beberapa pembaca pasti akan mendapatkan kesan (keliru) bahwa tidak ada yang menemukan cara yang bagus untuk merepresentasikan grafik di Haskell dengan cara yang memungkinkan pencocokan pola pada mereka, memetakannya, melipatnya, atau secara umum melakukan hal-hal keren dan fungsional yang biasa kita lakukan dengan daftar dan pohon.
sumber
Saya selalu menyukai pendekatan Martin Erwig dalam "Grafik Induktif dan Algoritma Grafik Fungsional", yang dapat Anda baca di sini . FWIW, saya pernah menulis implementasi Scala juga, lihat https://github.com/nicolast/scalagraphs .
sumber
Setiap diskusi tentang merepresentasikan grafik di Haskell perlu menyebutkan pustaka data-reify Andy Gill (inilah makalahnya ).
Representasi gaya "mengikat-simpul" dapat digunakan untuk membuat DSL yang sangat elegan (lihat contoh di bawah). Namun, struktur datanya masih terbatas penggunaannya. Perpustakaan Gill memberi Anda yang terbaik dari kedua dunia. Anda dapat menggunakan DSL "mengikat simpul", tetapi kemudian mengubah grafik berbasis penunjuk menjadi grafik berbasis label sehingga Anda dapat menjalankan algoritme pilihan Anda di atasnya.
Berikut ini contoh sederhananya:
Untuk menjalankan kode di atas, Anda memerlukan definisi berikut:
Saya ingin menekankan bahwa ini adalah DSL sederhana, tetapi langit adalah batasnya! Saya merancang DSL yang sangat lengkap, termasuk sintaks mirip pohon yang bagus untuk membuat simpul menyiarkan nilai awal ke beberapa anaknya, dan banyak fungsi praktis untuk membangun tipe simpul tertentu. Tentu saja, tipe data Node dan definisi mapDeRef lebih banyak terlibat.
sumber
Saya suka implementasi grafik yang diambil dari sini
sumber