├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── package.json ├── src └── Data │ └── Graph.purs └── test └── Main.purs /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Description of the change** 2 | 3 | Clearly and concisely describe the purpose of the pull request. If this PR relates to an existing issue or change proposal, please link to it. Include any other background context that would help reviewers understand the motivation for this PR. 4 | 5 | --- 6 | 7 | **Checklist:** 8 | 9 | - [ ] Added the change to the changelog's "Unreleased" section with a reference to this PR (e.g. "- Made a change (#0000)") 10 | - [ ] Linked any existing issues or proposals that this pull request should close 11 | - [ ] Updated or added relevant documentation 12 | - [ ] Added a test for the contribution (if applicable) 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - uses: purescript-contrib/setup-purescript@main 16 | with: 17 | purescript: "unstable" 18 | 19 | - uses: actions/setup-node@v2 20 | with: 21 | node-version: "14.x" 22 | 23 | - name: Install dependencies 24 | run: | 25 | npm install -g bower 26 | npm install 27 | bower install --production 28 | 29 | - name: Build source 30 | run: npm run-script build 31 | 32 | - name: Run tests 33 | run: | 34 | bower install 35 | npm run-script test --if-present 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.* 2 | !/.gitignore 3 | !/.github 4 | /bower_components/ 5 | /node_modules/ 6 | /output/ 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## [Unreleased] 6 | 7 | Breaking changes: 8 | 9 | New features: 10 | 11 | Bugfixes: 12 | 13 | Other improvements: 14 | 15 | ## [v8.1.0](https://github.com/purescript/purescript-graphs/releases/tag/v8.1.0) - 2022-07-30 16 | 17 | New features: 18 | - Added an `edges` function that returns a list of all edges in the graph (#17 by @MaybeJustJames) 19 | - Added `toMap` to unwrap `Graph` (#18 by @notquiteamonad) 20 | 21 | ## [v8.0.0](https://github.com/purescript/purescript-graphs/releases/tag/v8.0.0) - 2022-04-27 22 | 23 | Breaking changes: 24 | - Update project and deps to PureScript v0.15.0 (#19 by @JordanMartinez) 25 | 26 | New features: 27 | - Added `Foldable` and `Traversable` instances for `Graph` (#16 by @MaybeJustJames) 28 | 29 | Bugfixes: 30 | 31 | Other improvements: 32 | 33 | ## [v5.0.0](https://github.com/purescript/purescript-graphs/releases/tag/v5.0.0) - 2021-02-26 34 | 35 | Breaking changes: 36 | - Added support for PureScript 0.14 and dropped support for all previous versions (#11) 37 | 38 | New features: 39 | 40 | Bugfixes: 41 | 42 | Other improvements: 43 | - Migrated CI to GitHub Actions and updated installation instructions to use Spago 44 | - Added a CHANGELOG.md file and pull request template (#13, #14) 45 | 46 | ## [v4.0.0](https://github.com/purescript/purescript-graphs/releases/tag/v4.0.0) - 2018-06-06 47 | 48 | Updated for PureScript 0.12 49 | 50 | ## [v3.0.0](https://github.com/purescript/purescript-graphs/releases/tag/v3.0.0) - 2017-03-29 51 | 52 | - Updated for PureScript 0.11.1 53 | 54 | ## [v2.0.0](https://github.com/purescript/purescript-graphs/releases/tag/v2.0.0) - 2016-11-19 55 | 56 | 57 | 58 | ## [v1.0.0](https://github.com/purescript/purescript-graphs/releases/tag/v1.0.0) - 2016-06-01 59 | 60 | This release is intended for the PureScript 0.9.1 compiler and newer. 61 | 62 | **Note**: The v1.0.0 tag is not meant to indicate the library is “finished”, the core libraries are all being bumped to this for the 0.9 compiler release so as to use semver more correctly. 63 | 64 | ## [v1.0.0-rc.2](https://github.com/purescript/purescript-graphs/releases/tag/v1.0.0-rc.2) - 2016-05-21 65 | 66 | - Fixed shadowed variable warning 67 | 68 | ## [v1.0.0-rc.1](https://github.com/purescript/purescript-graphs/releases/tag/v1.0.0-rc.1) - 2016-03-28 69 | 70 | - Release candidate for the psc 0.8+ core libraries 71 | 72 | ## [v0.5.0](https://github.com/purescript/purescript-graphs/releases/tag/v0.5.0) - 2015-08-13 73 | 74 | - Updated dependencies 75 | - Removed unnecessary `Int` import 76 | 77 | ## [v0.4.0](https://github.com/purescript/purescript-graphs/releases/tag/v0.4.0) - 2015-06-30 78 | 79 | This release works with versions 0.7.\* of the PureScript compiler. It will not work with older versions. If you are using an older version, you should require an older, compatible version of this library. 80 | 81 | ## [v0.4.0-rc.1](https://github.com/purescript/purescript-graphs/releases/tag/v0.4.0-rc.1) - 2015-06-10 82 | 83 | Initial release candidate of the library intended for the 0.7 compiler. 84 | 85 | ## [v0.3.1](https://github.com/purescript/purescript-graphs/releases/tag/v0.3.1) - 2015-03-19 86 | 87 | Update docs 88 | 89 | ## [v0.3.0](https://github.com/purescript/purescript-graphs/releases/tag/v0.3.0) - 2015-02-21 90 | 91 | **This release requires PureScript v0.6.8 or later** 92 | - Updated dependencies 93 | 94 | ## [v0.2.0](https://github.com/purescript/purescript-graphs/releases/tag/v0.2.0) - 2015-01-10 95 | 96 | - Updated dependencies (@garyb) 97 | 98 | ## [v0.1.0](https://github.com/purescript/purescript-graphs/releases/tag/v0.1.0) - 2014-10-30 99 | 100 | Initial release after extracting from `purescript-maps` 101 | 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 PureScript 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-graphs 2 | 3 | [![Latest release](http://img.shields.io/github/release/purescript/purescript-graphs.svg)](https://github.com/purescript/purescript-graphs/releases) 4 | [![Build status](https://github.com/purescript/purescript-graphs/workflows/CI/badge.svg?branch=master)](https://github.com/purescript/purescript-graphs/actions?query=workflow%3ACI+branch%3Amaster) 5 | [![Pursuit](https://pursuit.purescript.org/packages/purescript-graphs/badge)](https://pursuit.purescript.org/packages/purescript-graphs) 6 | 7 | A data structure and functions for graphs. 8 | 9 | ## Installation 10 | 11 | ``` 12 | spago install graphs 13 | ``` 14 | 15 | ## Documentation 16 | 17 | Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-graphs). 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-graphs", 3 | "homepage": "https://github.com/purescript/purescript-graphs", 4 | "authors": [ 5 | "Phil Freeman " 6 | ], 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/purescript/purescript-graphs.git" 10 | }, 11 | "license": "MIT", 12 | "ignore": [ 13 | "**/.*", 14 | "bower_components", 15 | "node_modules", 16 | "output", 17 | "test", 18 | "bower.json", 19 | "package.json" 20 | ], 21 | "dependencies": { 22 | "purescript-catenable-lists": "^7.0.0", 23 | "purescript-ordered-collections": "^3.0.0" 24 | }, 25 | "devDependencies": { 26 | "purescript-console": "^6.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "clean": "rimraf output && rimraf .pulp-cache", 5 | "build": "pulp build -- --censor-lib --strict", 6 | "test": "pulp test" 7 | }, 8 | "devDependencies": { 9 | "pulp": "16.0.0-0", 10 | "purescript-psa": "^0.8.2", 11 | "rimraf": "^3.0.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Data/Graph.purs: -------------------------------------------------------------------------------- 1 | -- | A data structure and functions for graphs 2 | 3 | module Data.Graph 4 | ( Graph 5 | , Edge 6 | , unfoldGraph 7 | , fromMap 8 | , toMap 9 | , vertices 10 | , edges 11 | , lookup 12 | , outEdges 13 | , topologicalSort 14 | ) where 15 | 16 | import Prelude 17 | 18 | import Data.Bifunctor (lmap) 19 | import Data.CatList (CatList) 20 | import Data.CatList as CL 21 | import Data.Foldable (class Foldable, foldl, foldr, foldMap) 22 | import Data.FoldableWithIndex (foldlWithIndex) 23 | import Data.List (List(..), (:)) 24 | import Data.List as L 25 | import Data.Map (Map) 26 | import Data.Map as M 27 | import Data.Maybe (Maybe(..), maybe) 28 | import Data.Tuple (Tuple(..), fst, snd) 29 | import Data.Tuple.Nested ((/\)) 30 | import Data.Traversable (class Traversable, traverse) 31 | 32 | -- | A graph with vertices of type `v`. 33 | -- | 34 | -- | Edges refer to vertices using keys of type `k`. 35 | newtype Graph k v = Graph (Map k (Tuple v (List k))) 36 | 37 | instance functorGraph :: Functor (Graph k) where 38 | map f (Graph m) = Graph (map (lmap f) m) 39 | 40 | instance foldableGraph :: Foldable (Graph k) where 41 | foldl f z (Graph m) = foldl (\acc (Tuple k _) -> f acc k) z $ M.values m 42 | foldr f z (Graph m) = foldr (\(Tuple k _) acc -> f k acc) z $ M.values m 43 | foldMap f (Graph m) = foldMap (f <<< fst) $ M.values m 44 | 45 | instance traversableGraph :: Traversable (Graph k) where 46 | traverse f (Graph m) = Graph <$> (traverse (\(v /\ ks) -> (_ /\ ks) <$> (f v)) m) 47 | sequence = traverse identity 48 | 49 | -- | An Edge between 2 nodes in a Graph 50 | type Edge k = { start :: k, end :: k } 51 | 52 | -- | Unfold a `Graph` from a collection of keys and functions which label keys 53 | -- | and specify out-edges. 54 | unfoldGraph 55 | :: forall f k v out 56 | . Ord k 57 | => Functor f 58 | => Foldable f 59 | => Foldable out 60 | => f k 61 | -> (k -> v) 62 | -> (k -> out k) 63 | -> Graph k v 64 | unfoldGraph ks label theEdges = 65 | Graph (M.fromFoldable (map (\k -> 66 | Tuple k (Tuple (label k) (L.fromFoldable (theEdges k)))) ks)) 67 | 68 | -- | Create a `Graph` from a `Map` which maps vertices to their labels and 69 | -- | outgoing edges. 70 | fromMap :: forall k v. Map k (Tuple v (List k)) -> Graph k v 71 | fromMap = Graph 72 | 73 | -- | Create a `Map` which maps vertices to their labels and 74 | -- | outgoing edges from a `Graph`. 75 | toMap :: forall k v. Graph k v -> Map k (Tuple v (List k)) 76 | toMap (Graph g) = g 77 | 78 | -- | List all vertices in a graph. 79 | vertices :: forall k v. Graph k v -> List v 80 | vertices (Graph g) = map fst (M.values g) 81 | 82 | -- | List all edges in a graph 83 | edges :: forall k v. Graph k v -> List (Edge k) 84 | edges (Graph g) = foldlWithIndex edges' Nil g 85 | where 86 | edges' :: k -> List (Edge k) -> Tuple v (List k) -> List (Edge k) 87 | edges' src acc (_ /\ dests) = 88 | foldl (mkEdge src) acc dests 89 | 90 | mkEdge :: k -> List (Edge k) -> k -> List (Edge k) 91 | mkEdge src acc dest = { start: src, end: dest } : acc 92 | 93 | -- | Lookup a vertex by its key. 94 | lookup :: forall k v. Ord k => k -> Graph k v -> Maybe v 95 | lookup k (Graph g) = map fst (M.lookup k g) 96 | 97 | -- | Get the keys which are directly accessible from the given key. 98 | outEdges :: forall k v. Ord k => k -> Graph k v -> Maybe (List k) 99 | outEdges k (Graph g) = map snd (M.lookup k g) 100 | 101 | type SortState k v = 102 | { unvisited :: Map k (Tuple v (List k)) 103 | , result :: List k 104 | } 105 | 106 | -- To defunctionalize the `topologicalSort` function and make it tail-recursive, 107 | -- we introduce this data type which captures what we intend to do at each stage 108 | -- of the recursion. 109 | data SortStep a = Emit a | Visit a 110 | 111 | -- | Topologically sort the vertices of a graph. 112 | -- | 113 | -- | If the graph contains cycles, then the behavior is undefined. 114 | topologicalSort :: forall k v. Ord k => Graph k v -> List k 115 | topologicalSort (Graph g) = 116 | go initialState 117 | where 118 | go :: SortState k v -> List k 119 | go state@{ unvisited, result } = 120 | case M.findMin unvisited of 121 | Just { key } -> go (visit state (CL.fromFoldable [Visit key])) 122 | Nothing -> result 123 | 124 | visit :: SortState k v -> CatList (SortStep k) -> SortState k v 125 | visit state stack = 126 | case CL.uncons stack of 127 | Nothing -> state 128 | Just (Tuple (Emit k) ks) -> 129 | let state' = { result: Cons k state.result 130 | , unvisited: state.unvisited 131 | } 132 | in visit state' ks 133 | Just (Tuple (Visit k) ks) 134 | | k `M.member` state.unvisited -> 135 | let start :: SortState k v 136 | start = 137 | { result: state.result 138 | , unvisited: M.delete k state.unvisited 139 | } 140 | 141 | next :: List k 142 | next = maybe mempty snd (M.lookup k g) 143 | in visit start (CL.fromFoldable (map Visit next) <> CL.cons (Emit k) ks) 144 | | otherwise -> visit state ks 145 | 146 | initialState :: SortState k v 147 | initialState = { unvisited: g 148 | , result: Nil 149 | } 150 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Data.Foldable (foldr) 6 | import Data.Graph (Graph, Edge, edges, fromMap, unfoldGraph, topologicalSort) 7 | import Data.List (filter, length, toUnfoldable, range, (:), List(Nil)) 8 | import Data.Map (Map) 9 | import Data.Map as M 10 | import Data.Maybe (Maybe(Just)) 11 | import Data.Traversable (traverse) 12 | import Data.Tuple (Tuple) 13 | import Data.Tuple.Nested ((/\)) 14 | import Effect (Effect, foreachE) 15 | import Effect.Console (logShow) 16 | 17 | -- An example graph: 18 | -- 0 19 | -- / \ 20 | -- 1 2 21 | -- \/ 22 | -- 3 23 | 24 | example1 :: Graph Int Int 25 | example1 = 26 | fromMap example1' 27 | where 28 | example1' :: Map Int (Tuple Int (List Int)) 29 | example1' = 30 | M.fromFoldable 31 | [ (0 /\ (0 /\ (1 : 2 : Nil))) 32 | , (1 /\ (1 /\ (3 : Nil))) 33 | , (2 /\ (2 /\ (3 : Nil))) 34 | , (3 /\ (3 /\ Nil)) 35 | ] 36 | 37 | showEdge :: forall k. Show k => Edge k -> String 38 | showEdge { start, end } = 39 | "(" <> show start <> " --> " <> show end <> ")" 40 | 41 | main :: Effect Unit 42 | main = do 43 | let 44 | double x 45 | | x * 2 < 100000 = [ x * 2 ] 46 | | otherwise = [] 47 | 48 | graph :: Graph Int Int 49 | graph = unfoldGraph (range 1 100000) identity double 50 | foreachE (toUnfoldable (topologicalSort graph)) logShow 51 | logShow 52 | $ filter (_ /= 0) <<< foldr (:) Nil 53 | <$> traverse (\n -> if n `mod` 2 == 0 then Just 0 else Just n) graph 54 | logShow (length (edges graph)) 55 | logShow $ map showEdge $ edges example1 56 | --------------------------------------------------------------------------------