├── .gitignore ├── .travis.yml ├── adj.go ├── adj_RO.go ├── adj_cg.go ├── adj_cg_test.go ├── adj_ro_test.go ├── adj_test.go ├── alt ├── adj.go ├── adj_test.go ├── bf2.go ├── bf2_test.go ├── dir.go ├── dir_test.go ├── doc.go ├── readme.adoc ├── traverse.go ├── traverse_test.go ├── undir.go └── undir_test.go ├── anecdote ├── anec.go ├── eulerian.go ├── prop.go ├── random.go ├── readme.adoc ├── short.go └── trav.go ├── bench ├── DijkstraAllPaths.adoc ├── img │ ├── DijkstraAllPaths.go │ ├── DijkstraAllPaths.svg │ ├── DijkstraAllPathsGeo.svg │ ├── Euclidean.go │ ├── Euclidean.svg │ ├── Geometric.go │ └── Geometric.svg └── readme.adoc ├── dir.go ├── dir_RO.go ├── dir_cg.go ├── dir_cg_test.go ├── dir_ro_test.go ├── dir_test.go ├── doc.go ├── dot ├── dot.go ├── dot_test.go ├── options.go └── options_test.go ├── fromlist.go ├── fromlist_test.go ├── go.mod ├── graph.go ├── graph_test.go ├── hacking.adoc ├── io ├── ex_test.go ├── int_test.go ├── readltext.go ├── readtext.go ├── text.go ├── text_test.go ├── writeltext.go └── writetext.go ├── mst.go ├── mst_test.go ├── random.go ├── random_test.go ├── readme.adoc ├── sssp.go ├── sssp_test.go ├── treevis ├── treevis.go └── treevis_test.go ├── tutorials ├── adjacencylist.adoc ├── dijkstra.adoc ├── img │ ├── al.go │ ├── al.svg │ ├── al0.svg │ ├── ald.svg │ ├── almem.dia │ ├── almem.svg │ ├── alpair.svg │ └── eulerian │ │ └── eu.go ├── missingmethods.adoc └── readme.adoc ├── undir.go ├── undir_RO.go ├── undir_cg.go ├── undir_cg_test.go ├── undir_ro_test.go └── undir_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.dot 2 | anecdote/anecdote 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - "1.10.x" 5 | - master 6 | before_script: 7 | - go tool vet -composites=false -printf=false -shift=false . 8 | - go get github.com/client9/misspell/cmd/misspell 9 | - go get github.com/soniakeys/vetc 10 | - misspell -error * */* */*/* 11 | - vetc 12 | -------------------------------------------------------------------------------- /alt/adj.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package alt 5 | 6 | import "github.com/soniakeys/graph" 7 | 8 | // AnyParallelMap identifies if a graph contains parallel arcs, multiple arcs 9 | // that lead from a node to the same node. 10 | // 11 | // If the graph has parallel arcs, the method returns true and 12 | // results fr and to represent an example where there are parallel arcs 13 | // from node `fr` to node `to`. 14 | // 15 | // If there are no parallel arcs, the method returns false, -1 -1. 16 | // 17 | // Multiple loops on a node count as parallel arcs. 18 | // 19 | // "Map" in the method name indicates that a Go map is used to detect parallel 20 | // arcs. Compared to method AnyParallelSort, this gives better asymptotic 21 | // performance for large dense graphs but may have increased overhead for 22 | // small or sparse graphs. 23 | func AnyParallelMap(g graph.AdjacencyList) (has bool, fr, to graph.NI) { 24 | for n, to := range g { 25 | if len(to) == 0 { 26 | continue 27 | } 28 | m := map[graph.NI]struct{}{} 29 | for _, to := range to { 30 | if _, ok := m[to]; ok { 31 | return true, graph.NI(n), to 32 | } 33 | m[to] = struct{}{} 34 | } 35 | } 36 | return false, -1, -1 37 | } 38 | -------------------------------------------------------------------------------- /alt/adj_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package alt_test 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/soniakeys/graph" 10 | "github.com/soniakeys/graph/alt" 11 | ) 12 | 13 | func ExampleAnyParallelMap_parallelArcs() { 14 | g := graph.AdjacencyList{ 15 | 1: {0, 0}, 16 | } 17 | // result true 1 0 means parallel arcs from node 1 to node 0 18 | fmt.Println(alt.AnyParallelMap(g)) 19 | // Output: 20 | // true 1 0 21 | } 22 | 23 | func ExampleAnyParallelMap_noParallelArcs() { 24 | g := graph.AdjacencyList{ 25 | 1: {0}, 26 | } 27 | fmt.Println(alt.AnyParallelMap(g)) // result false -1 -1 means no parallel arc 28 | // Output: 29 | // false -1 -1 30 | } 31 | -------------------------------------------------------------------------------- /alt/bf2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package alt 5 | 6 | import ( 7 | "github.com/soniakeys/bits" 8 | "github.com/soniakeys/graph" 9 | ) 10 | 11 | // BreadthFirst2 traverses a graph breadth first using a direction 12 | // optimizing algorithm pioneered by Scott Beamer. 13 | // 14 | // The algorithm is supposed to be faster than the conventional breadth first 15 | // algorithm but I haven't seen it yet. 16 | func BreadthFirst2(g, tr graph.AdjacencyList, ma int, start graph.NI, f *graph.FromList, v func(graph.NI) bool) int { 17 | if tr == nil { 18 | var d graph.Directed 19 | d, ma = graph.Directed{g}.Transpose() 20 | tr = d.AdjacencyList 21 | } 22 | switch { 23 | case f == nil: 24 | e := graph.NewFromList(len(g)) 25 | f = &e 26 | case f.Paths == nil: 27 | *f = graph.NewFromList(len(g)) 28 | } 29 | if ma <= 0 { 30 | ma = g.ArcSize() 31 | } 32 | rp := f.Paths 33 | level := 1 34 | rp[start] = graph.PathEnd{Len: level, From: -1} 35 | if !v(start) { 36 | f.MaxLen = level 37 | return -1 38 | } 39 | nReached := 1 // accumulated for a return value 40 | // the frontier consists of nodes all at the same level 41 | frontier := []graph.NI{start} 42 | mf := len(g[start]) // number of arcs leading out from frontier 43 | ctb := ma / 10 // threshold change from top-down to bottom-up 44 | k14 := 14 * ma / len(g) // 14 * mean degree 45 | cbt := len(g) / k14 // threshold change from bottom-up to top-down 46 | // well shoot. part of the speed problem is the bitmap implementation. 47 | // big.Ints are slow. this custom bits package is faster. faster still 48 | // is just a slice of bools. :( 49 | fBits := bits.New(len(g)) 50 | nextb := bits.New(len(g)) 51 | for { 52 | // top down step 53 | level++ 54 | var next []graph.NI 55 | for _, n := range frontier { 56 | for _, nb := range g[n] { 57 | if rp[nb].Len == 0 { 58 | rp[nb] = graph.PathEnd{From: n, Len: level} 59 | if !v(nb) { 60 | f.MaxLen = level 61 | return -1 62 | } 63 | next = append(next, nb) 64 | nReached++ 65 | } 66 | } 67 | } 68 | if len(next) == 0 { 69 | break 70 | } 71 | frontier = next 72 | if mf > ctb { 73 | // switch to bottom up! 74 | } else { 75 | // stick with top down 76 | continue 77 | } 78 | // convert frontier representation 79 | nf := 0 // number of vertices on the frontier 80 | for _, n := range frontier { 81 | fBits.SetBit(int(n), 1) 82 | nf++ 83 | } 84 | bottomUpLoop: 85 | level++ 86 | nNext := 0 87 | for n := range tr { 88 | if rp[n].Len == 0 { 89 | for _, nb := range tr[n] { 90 | if fBits.Bit(int(nb)) == 1 { 91 | rp[n] = graph.PathEnd{From: nb, Len: level} 92 | if !v(nb) { 93 | f.MaxLen = level 94 | return -1 95 | } 96 | nextb.SetBit(n, 1) 97 | nReached++ 98 | nNext++ 99 | break 100 | } 101 | } 102 | } 103 | } 104 | if nNext == 0 { 105 | break 106 | } 107 | fBits, nextb = nextb, fBits 108 | nextb.ClearAll() 109 | nf = nNext 110 | if nf < cbt { 111 | // switch back to top down! 112 | } else { 113 | // stick with bottom up 114 | goto bottomUpLoop 115 | } 116 | // convert frontier representation 117 | mf = 0 118 | frontier = frontier[:0] 119 | // TODO use exported bits representation here. quick search for a 120 | // non zero word, then convert all bits in the word, then clear the 121 | // word. 122 | for n := range g { 123 | if fBits.Bit(n) == 1 { 124 | frontier = append(frontier, graph.NI(n)) 125 | mf += len(g[n]) 126 | // fBits.SetBit(n, 0) // alternative to clear all. 127 | } 128 | } 129 | // or clear individual bits, but in bottom up step we expect a 130 | // relatively dense frontier, so ClearAll seems better. 131 | fBits.ClearAll() 132 | } 133 | f.MaxLen = level - 1 134 | return nReached 135 | } 136 | -------------------------------------------------------------------------------- /alt/bf2_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package alt_test 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/soniakeys/graph" 10 | "github.com/soniakeys/graph/alt" 11 | ) 12 | 13 | func ExampleBreadthFirst2_allPaths() { 14 | // arcs are directed right: 15 | // 1 3---5 16 | // / \ / / 17 | // 2 4---6--\ 18 | // \-/ 19 | g := graph.AdjacencyList{ 20 | 2: {1}, 21 | 1: {4}, 22 | 4: {3, 6}, 23 | 3: {5}, 24 | 6: {5, 6}, 25 | } 26 | var f graph.FromList 27 | alt.BreadthFirst2(g, nil, 0, 1, &f, func(n graph.NI) bool { 28 | return true 29 | }) 30 | fmt.Println("Max path length:", f.MaxLen) 31 | p := make([]graph.NI, f.MaxLen) 32 | for n := range g { 33 | fmt.Println(n, f.PathTo(graph.NI(n), p)) 34 | } 35 | // Output: 36 | // Max path length: 4 37 | // 0 [] 38 | // 1 [1] 39 | // 2 [] 40 | // 3 [1 4 3] 41 | // 4 [1 4] 42 | // 5 [1 4 3 5] 43 | // 6 [1 4 6] 44 | } 45 | -------------------------------------------------------------------------------- /alt/dir_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package alt_test 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/soniakeys/graph" 10 | "github.com/soniakeys/graph/alt" 11 | ) 12 | 13 | func ExampleTarjanCycles() { 14 | // 0-->1--->2-\ 15 | // ^ ^^\ ^| v 16 | // | / || / | 3 17 | // |/ \v/ v / 18 | // 4<---5<--6< 19 | g := graph.Directed{graph.AdjacencyList{ 20 | 0: {1}, 21 | 1: {2, 5}, 22 | 2: {3, 6}, 23 | 3: {6}, 24 | 4: {0, 1}, 25 | 5: {1, 2, 4}, 26 | 6: {5}, 27 | }} 28 | alt.TarjanCycles(g, func(c []graph.NI) bool { 29 | fmt.Println(c) 30 | return true 31 | }) 32 | // Output: 33 | // [0 1 2 3 6 5 4] 34 | // [0 1 2 6 5 4] 35 | // [0 1 5 4] 36 | // [1 2 3 6 5] 37 | // [1 2 3 6 5 4] 38 | // [1 2 6 5] 39 | // [1 2 6 5 4] 40 | // [1 5] 41 | // [1 5 4] 42 | // [2 3 6 5] 43 | // [2 6 5] 44 | } 45 | 46 | func ExampleTarjanCyclesLabeled() { 47 | // -1 2 48 | // 0----->1------>2--\ 49 | // ^ ^^\ ^| \2 50 | // 3| -4/ ||1 / |-1 v 51 | // | / || / | 3 52 | // | / || / | / 53 | // | / -2|| /-2 | /-1 54 | // |/ \v/ v / 55 | // 4<------5<-----6< 56 | // -1 1 57 | g := graph.LabeledDirected{graph.LabeledAdjacencyList{ 58 | 0: {{To: 1, Label: -1}}, 59 | 1: {{To: 2, Label: 2}, {To: 5, Label: 1}}, 60 | 2: {{To: 3, Label: 2}, {To: 6, Label: -1}}, 61 | 3: {{To: 6, Label: -1}}, 62 | 4: {{To: 0, Label: 3}, {To: 1, Label: -4}}, 63 | 5: {{To: 1, Label: -2}, {To: 2, Label: -2}, {To: 4, Label: -1}}, 64 | 6: {{To: 5, Label: 1}}, 65 | }} 66 | alt.TarjanCyclesLabeled(g, func(c []graph.Half) bool { 67 | fmt.Println(c) 68 | return true 69 | }) 70 | // Output: 71 | // [{1 -1} {2 2} {3 2} {6 -1} {5 1} {4 -1} {0 3}] 72 | // [{1 -1} {2 2} {6 -1} {5 1} {4 -1} {0 3}] 73 | // [{1 -1} {5 1} {4 -1} {0 3}] 74 | // [{2 2} {3 2} {6 -1} {5 1} {1 -2}] 75 | // [{2 2} {3 2} {6 -1} {5 1} {4 -1} {1 -4}] 76 | // [{2 2} {6 -1} {5 1} {1 -2}] 77 | // [{2 2} {6 -1} {5 1} {4 -1} {1 -4}] 78 | // [{5 1} {1 -2}] 79 | // [{5 1} {4 -1} {1 -4}] 80 | // [{3 2} {6 -1} {5 1} {2 -2}] 81 | // [{6 -1} {5 1} {2 -2}] 82 | } 83 | 84 | func ExampleSCCKosaraju() { 85 | // /---0---\ 86 | // | |\--/ 87 | // | v 88 | // | 5<=>4---\ 89 | // | | | | 90 | // v v | | 91 | // 7<=>6 | | 92 | // | v v 93 | // \-->3<--2 94 | // | ^ 95 | // | | 96 | // \-->1 97 | g := graph.Directed{graph.AdjacencyList{ 98 | 0: {0, 5, 7}, 99 | 5: {4, 6}, 100 | 4: {5, 2, 3}, 101 | 7: {6}, 102 | 6: {7, 3}, 103 | 3: {1}, 104 | 1: {2}, 105 | 2: {3}, 106 | }} 107 | alt.SCCKosaraju(g, func(c []graph.NI) bool { 108 | fmt.Println(c) 109 | return true 110 | }) 111 | // Output: 112 | // [0] 113 | // [5 4] 114 | // [6 7] 115 | // [2 1 3] 116 | } 117 | 118 | func ExampleSCCPathBased() { 119 | // /---0---\ 120 | // | |\--/ 121 | // | v 122 | // | 5<=>4---\ 123 | // | | | | 124 | // v v | | 125 | // 7<=>6 | | 126 | // | v v 127 | // \-->3<--2 128 | // | ^ 129 | // | | 130 | // \-->1 131 | g := graph.Directed{graph.AdjacencyList{ 132 | 0: {0, 5, 7}, 133 | 5: {4, 6}, 134 | 4: {5, 2, 3}, 135 | 7: {6}, 136 | 6: {7, 3}, 137 | 3: {1}, 138 | 1: {2}, 139 | 2: {3}, 140 | }} 141 | alt.SCCPathBased(g, func(c []graph.NI) bool { 142 | fmt.Println(c) 143 | return true 144 | }) 145 | // Output: 146 | // [1 3 2] 147 | // [7 6] 148 | // [4 5] 149 | // [0] 150 | } 151 | 152 | func ExampleSCCTarjan() { 153 | // /---0---\ 154 | // | |\--/ 155 | // | v 156 | // | 5<=>4---\ 157 | // | | | | 158 | // v v | | 159 | // 7<=>6 | | 160 | // | v v 161 | // \-->3<--2 162 | // | ^ 163 | // | | 164 | // \-->1 165 | g := graph.Directed{graph.AdjacencyList{ 166 | 0: {0, 5, 7}, 167 | 5: {4, 6}, 168 | 4: {5, 2, 3}, 169 | 7: {6}, 170 | 6: {7, 3}, 171 | 3: {1}, 172 | 1: {2}, 173 | 2: {3}, 174 | }} 175 | alt.SCCTarjan(g, func(c []graph.NI) bool { 176 | fmt.Println(c) 177 | return true 178 | }) 179 | // Output: 180 | // [1 3 2] 181 | // [7 6] 182 | // [4 5] 183 | // [0] 184 | } 185 | -------------------------------------------------------------------------------- /alt/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | // Alternative graph algorithms. 5 | // 6 | // Functions in this package implement alternatives to algorithms in the main 7 | // graph package. They produce equivalent results to their counterparts in the 8 | // main graph package but they are relegated here for inferior performance. 9 | package alt 10 | -------------------------------------------------------------------------------- /alt/readme.adoc: -------------------------------------------------------------------------------- 1 | = Alt 2 | 3 | Alternative graph algorithms 4 | 5 | Functions in this package implement alternatives to algorithms in the main 6 | graph package. They produce equivalent results to their counterparts in the 7 | main graph package but they are relegated here for inferior performance. 8 | -------------------------------------------------------------------------------- /alt/traverse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package alt 5 | 6 | import ( 7 | "math/rand" 8 | 9 | "github.com/soniakeys/bits" 10 | "github.com/soniakeys/graph" 11 | ) 12 | 13 | type config struct { 14 | // values initialized directly from parameters 15 | start graph.NI 16 | arcVisitor func(n graph.NI, x int) 17 | iterateFrom func(n graph.NI) 18 | levelVisitor func(l int, n []graph.NI) 19 | nodeVisitor func(n graph.NI) 20 | okArcVisitor func(n graph.NI, x int) bool 21 | okNodeVisitor func(n graph.NI) bool 22 | okLevelVisitor func(l int, n []graph.NI) bool 23 | rand *rand.Rand 24 | visBits *bits.Bits 25 | pathBits *bits.Bits 26 | fromList *graph.FromList 27 | 28 | // other stuff initialized in constructor 29 | rp []graph.PathEnd // fromList.Paths 30 | nvis func(graph.NI) bool // not-visited test 31 | } 32 | 33 | func newConfig(g graph.AdjacencyList, start graph.NI, opt []TraverseOption) *config { 34 | cf := &config{start: start} 35 | for _, o := range opt { 36 | o(cf) 37 | } 38 | // either visBits or fromList are suitable for recording visited nodes. 39 | // if neither is specified as an option, allocate bits. 40 | if cf.fromList == nil { 41 | if cf.visBits == nil { 42 | b := bits.New(len(g)) 43 | cf.visBits = &b 44 | } 45 | } else { 46 | if cf.fromList.Paths == nil { 47 | *cf.fromList = graph.NewFromList(len(g)) 48 | } 49 | cf.rp = cf.fromList.Paths 50 | } 51 | if cf.visBits != nil { 52 | if cf.visBits.Bit(int(cf.start)) == 1 { 53 | return nil 54 | } 55 | cf.visBits.SetBit(int(cf.start), 1) 56 | cf.nvis = func(n graph.NI) bool { return cf.visBits.Bit(int(n)) == 0 } 57 | } else { 58 | cf.nvis = func(n graph.NI) bool { return cf.rp[n].Len == 0 } 59 | } 60 | if cf.rp != nil { 61 | if cf.rp[cf.start].Len > 0 { 62 | return nil 63 | } 64 | cf.rp[cf.start] = graph.PathEnd{Len: 1, From: -1} 65 | } 66 | return cf 67 | } 68 | 69 | // A TraverseOption specifies an option for a breadth first or depth first 70 | // traversal. 71 | // 72 | // Values of this type are returned by various TraverseOption constructor 73 | // functions. These constructors take optional values to be used 74 | // in a traversal and wrap them in the TraverseOption type. This type 75 | // is actually a function. The BreadthFirst and DepthFirst traversal 76 | // methods call these functions in order, to initialize state that controls 77 | // the traversal. 78 | type TraverseOption func(*config) 79 | 80 | // ArcVisitor specifies a visitor function to call at each arc. 81 | // 82 | // See also OkArcVisitor. 83 | func ArcVisitor(v func(n graph.NI, x int)) TraverseOption { 84 | return func(c *config) { 85 | c.arcVisitor = v 86 | } 87 | } 88 | 89 | // From specifies a graph.FromList to populate. 90 | func From(f *graph.FromList) TraverseOption { 91 | return func(c *config) { 92 | c.fromList = f 93 | } 94 | } 95 | 96 | // LevelVisitor specifies a visitor function to call at each level or depth. 97 | // 98 | // The level visitor function is called before any node or arc visitor 99 | // functions. 100 | // 101 | // See also OkLevelVisitor. 102 | func LevelVisitor(v func(level int, nodes []graph.NI)) TraverseOption { 103 | return func(c *config) { 104 | c.levelVisitor = v 105 | } 106 | } 107 | 108 | // NodeVisitor specifies a visitor function to call at each node. 109 | // 110 | // The node visitor function is called before any arc visitor functions. 111 | // 112 | // See also OkNodeVisitor. 113 | func NodeVisitor(v func(graph.NI)) TraverseOption { 114 | return func(c *config) { 115 | c.nodeVisitor = v 116 | } 117 | } 118 | 119 | // OkArcVisitor specifies a visitor function to perform some test at each arc 120 | // and return a boolean result. 121 | // 122 | // As long as v return a result of true, the traverse progresses to traverse all 123 | // arcs. 124 | // 125 | // If v returns false, the traverse terminates immediately. 126 | // 127 | // See also ArcVisitor. 128 | func OkArcVisitor(v func(n graph.NI, x int) bool) TraverseOption { 129 | return func(c *config) { 130 | c.okArcVisitor = v 131 | } 132 | } 133 | 134 | // OKLevelVisitor specifies a visitor function to call at each level or depth, 135 | // returning a boolean result 136 | // 137 | // As long as v returns a result of true, the traverse progresses to traverse 138 | // all nodes. If v returns false, the traverse terminates immediately. 139 | // 140 | // The level visitor function is called before any node or arc visitor 141 | // functions. 142 | // 143 | // See also LevelVisitor. 144 | func OkLevelVisitor(v func(level int, nodes []graph.NI) bool) TraverseOption { 145 | return func(c *config) { 146 | c.okLevelVisitor = v 147 | } 148 | } 149 | 150 | // OkNodeVisitor specifies a visitor function to perform some test at each node 151 | // and return a boolean result. 152 | // 153 | // As long as v returns a result of true, the traverse progresses to traverse 154 | // all nodes. If v returns false, the traverse terminates immediately. 155 | // 156 | // The node visitor function is called before any arc visitor functions. 157 | // 158 | // See also NodeVisitor. 159 | func OkNodeVisitor(v func(graph.NI) bool) TraverseOption { 160 | return func(c *config) { 161 | c.okNodeVisitor = v 162 | } 163 | } 164 | 165 | // PathBits specifies a bits.Bits value for nodes of the path to the 166 | // currently visited node. 167 | // 168 | // A use for PathBits is identifying back arcs in a traverse. 169 | // 170 | // Unlike Visited, PathBits are zeroed at the start of a traverse. 171 | func PathBits(b *bits.Bits) TraverseOption { 172 | return func(c *config) { c.pathBits = b } 173 | } 174 | 175 | // Rand specifies to traverse edges from each visited node in random order. 176 | func Rand(r *rand.Rand) TraverseOption { 177 | return func(c *config) { c.rand = r } 178 | } 179 | 180 | // Visited specifies a bits.Bits value to record visited nodes. 181 | // 182 | // For each node visited, the corresponding bit is set to 1. Other bits 183 | // are not modified. 184 | // 185 | // The traverse algorithm controls the traverse using a bits.Bits. If this 186 | // function is used, argument b will be used as the controlling value. 187 | // 188 | // Bits are not zeroed at the start of a traverse, so the initial Bits value 189 | // passed in should generally be zero. Non-zero bits will limit the traverse. 190 | func Visited(b *bits.Bits) TraverseOption { 191 | return func(c *config) { c.visBits = b } 192 | } 193 | 194 | // BreadthFirst traverses a directed or undirected graph in breadth first order. 195 | // 196 | // Argument start is the start node for the traversal. Argument opt can be 197 | // any number of values returned by a supported TraverseOption function. 198 | // 199 | // Supported: 200 | // 201 | // From 202 | // ArcVisitor 203 | // LevelVisitor 204 | // NodeVisitor 205 | // OkArcVisitor 206 | // OkLevelVisitor 207 | // OkNodeVisitor 208 | // Rand 209 | // Visited 210 | // 211 | // Unsupported: 212 | // 213 | // PathBits 214 | // 215 | // See also alt.BreadthFirst2, a direction optimizing breadth first algorithm. 216 | func BreadthFirst(g graph.AdjacencyList, start graph.NI, options ...TraverseOption) { 217 | cf := newConfig(g, start, options) 218 | if cf == nil { 219 | return 220 | } 221 | // the frontier consists of nodes all at the same level 222 | frontier := []graph.NI{cf.start} 223 | level := 1 224 | var next []graph.NI 225 | visitNode := func(n graph.NI) bool { 226 | // visit nodes as they come off frontier 227 | if cf.nodeVisitor != nil { 228 | cf.nodeVisitor(n) 229 | } 230 | if cf.okNodeVisitor != nil { 231 | if !cf.okNodeVisitor(n) { 232 | return false 233 | } 234 | } 235 | for x, nb := range g[n] { 236 | if cf.arcVisitor != nil { 237 | cf.arcVisitor(n, x) 238 | } 239 | if cf.okArcVisitor != nil { 240 | if !cf.okArcVisitor(n, x) { 241 | return false 242 | } 243 | } 244 | if cf.nvis(nb) { 245 | next = append(next, nb) 246 | if cf.visBits != nil { 247 | cf.visBits.SetBit(int(nb), 1) 248 | } 249 | if cf.rp != nil { 250 | cf.rp[nb] = graph.PathEnd{From: n, Len: level} 251 | } 252 | } 253 | } 254 | return true 255 | } 256 | for { 257 | if cf.rand != nil { 258 | cf.rand.Shuffle(len(frontier), func(i, j int) { 259 | frontier[i], frontier[j] = frontier[j], frontier[i] 260 | }) 261 | } 262 | if cf.levelVisitor != nil { 263 | cf.levelVisitor(level, frontier) 264 | } 265 | if cf.okLevelVisitor != nil && !cf.okLevelVisitor(level, frontier) { 266 | return 267 | } 268 | if cf.fromList != nil { 269 | cf.fromList.MaxLen = level 270 | } 271 | level++ 272 | for _, n := range frontier { 273 | if !visitNode(n) { 274 | return 275 | } 276 | } 277 | if len(next) == 0 { 278 | break 279 | } 280 | frontier, next = next, nil 281 | } 282 | } 283 | 284 | // DepthFirst traverses a directed or undirected graph in depth first order. 285 | // 286 | // Argument start is the start node for the traversal. Argument opt can be 287 | // any number of values returned by a supported TraverseOption function. 288 | // 289 | // Supported: 290 | // 291 | // From 292 | // ArcVisitor 293 | // NodeVisitor 294 | // OkArcVisitor 295 | // OkNodeVisitor 296 | // PathBits 297 | // Rand 298 | // Visited 299 | // 300 | // Unsupported: 301 | // 302 | // LevelVisitor 303 | // OkLevelVisitor 304 | func DepthFirst(g graph.AdjacencyList, start graph.NI, options ...TraverseOption) { 305 | cf := newConfig(g, start, options) 306 | if cf == nil { 307 | return 308 | } 309 | if cf.pathBits != nil { 310 | cf.pathBits.ClearAll() 311 | } 312 | var dfArc func(graph.NI, graph.NI, int, int) bool 313 | dfNode := func(n graph.NI, level int) bool { 314 | if cf.visBits != nil { 315 | cf.visBits.SetBit(int(n), 1) 316 | } 317 | if cf.pathBits != nil { 318 | cf.pathBits.SetBit(int(n), 1) 319 | } 320 | if cf.nodeVisitor != nil { 321 | cf.nodeVisitor(n) 322 | } 323 | if cf.okNodeVisitor != nil { 324 | if !cf.okNodeVisitor(n) { 325 | return false 326 | } 327 | } 328 | if cf.rand == nil { 329 | for x, to := range g[n] { 330 | if !dfArc(n, to, x, level) { 331 | return false 332 | } 333 | } 334 | } else { 335 | to := g[n] 336 | for _, x := range cf.rand.Perm(len(to)) { 337 | if !dfArc(n, to[x], x, level) { 338 | return false 339 | } 340 | } 341 | } 342 | if cf.pathBits != nil { 343 | cf.pathBits.SetBit(int(n), 0) 344 | } 345 | return true 346 | } 347 | dfArc = func(fr, to graph.NI, x, level int) bool { 348 | if cf.arcVisitor != nil { 349 | cf.arcVisitor(fr, x) 350 | } 351 | if cf.okArcVisitor != nil { 352 | if !cf.okArcVisitor(fr, x) { 353 | return false 354 | } 355 | } 356 | if !cf.nvis(to) { 357 | return true 358 | } 359 | if cf.rp != nil { 360 | cf.rp[fr] = graph.PathEnd{From: fr, Len: level} 361 | } 362 | return dfNode(to, level+1) 363 | } 364 | dfNode(cf.start, 1) 365 | } 366 | -------------------------------------------------------------------------------- /alt/traverse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package alt_test 5 | 6 | import ( 7 | "fmt" 8 | "math/rand" 9 | 10 | "github.com/soniakeys/bits" 11 | "github.com/soniakeys/graph" 12 | "github.com/soniakeys/graph/alt" 13 | ) 14 | 15 | func ExampleArcVisitor() { 16 | // 0 17 | // / \ 18 | // 1-->2 19 | // ^ | 20 | // | v 21 | // \---3 22 | g := graph.AdjacencyList{ 23 | 0: {1, 2}, 24 | 1: {2}, 25 | 2: {3}, 26 | 3: {1}, 27 | } 28 | alt.DepthFirst(g, 0, alt.ArcVisitor(func(n graph.NI, x int) { 29 | fmt.Println(n, "->", g[n][x]) 30 | })) 31 | // Output: 32 | // 0 -> 1 33 | // 1 -> 2 34 | // 2 -> 3 35 | // 3 -> 1 36 | // 0 -> 2 37 | } 38 | 39 | func ExampleLevelVisitor() { 40 | // 0 41 | // / \ 42 | // 1-->2 43 | // ^ | 44 | // | v 45 | // \---3 46 | g := graph.AdjacencyList{ 47 | 0: {1, 2}, 48 | 1: {2}, 49 | 2: {3}, 50 | 3: {1}, 51 | } 52 | alt.BreadthFirst(g, 0, alt.LevelVisitor(func(l int, n []graph.NI) { 53 | fmt.Println(l, n) 54 | })) 55 | // Output: 56 | // 1 [0] 57 | // 2 [1 2] 58 | // 3 [3] 59 | } 60 | 61 | func ExampleNodeVisitor() { 62 | // 0 63 | // / \ 64 | // 1-->2 65 | // ^ | 66 | // | v 67 | // \---3 68 | g := graph.AdjacencyList{ 69 | 0: {1, 2}, 70 | 1: {2}, 71 | 2: {3}, 72 | 3: {1}, 73 | } 74 | alt.DepthFirst(g, 0, alt.NodeVisitor(func(n graph.NI) { 75 | fmt.Println(n) 76 | })) 77 | // Output: 78 | // 0 79 | // 1 80 | // 2 81 | // 3 82 | } 83 | 84 | func ExampleOkArcVisitor() { 85 | // 0 86 | // / \ 87 | // 1-->2 88 | // ^ | 89 | // | v 90 | // \---3 91 | g := graph.AdjacencyList{ 92 | 0: {1, 2}, 93 | 1: {2}, 94 | 2: {3}, 95 | 3: {1}, 96 | } 97 | alt.DepthFirst(g, 0, alt.OkArcVisitor(func(n graph.NI, x int) bool { 98 | fmt.Println(n, "->", g[n][x]) 99 | return n < g[n][x] 100 | })) 101 | // Output: 102 | // 0 -> 1 103 | // 1 -> 2 104 | // 2 -> 3 105 | // 3 -> 1 106 | } 107 | 108 | func ExampleOkLevelVisitor() { 109 | // 0 110 | // / \ 111 | // 1-->2 112 | // ^ | 113 | // | v 114 | // \---3 115 | g := graph.AdjacencyList{ 116 | 0: {1, 2}, 117 | 1: {2}, 118 | 2: {3}, 119 | 3: {1}, 120 | } 121 | alt.BreadthFirst(g, 0, alt.OkLevelVisitor(func(l int, n []graph.NI) bool { 122 | fmt.Println(l, n) 123 | return l < 2 124 | })) 125 | // Output: 126 | // 1 [0] 127 | // 2 [1 2] 128 | } 129 | 130 | func ExampleOkNodeVisitor() { 131 | // 0 132 | // / \ 133 | // 1-->2 134 | // ^ | 135 | // | v 136 | // \---3 137 | g := graph.AdjacencyList{ 138 | 0: {1, 2}, 139 | 1: {2}, 140 | 2: {3}, 141 | 3: {1}, 142 | } 143 | alt.DepthFirst(g, 0, alt.OkNodeVisitor(func(n graph.NI) bool { 144 | fmt.Println(n) 145 | return n != 2 146 | })) 147 | // Output: 148 | // 0 149 | // 1 150 | // 2 151 | } 152 | 153 | func ExamplePathBits() { 154 | // 0 155 | // / \ 156 | // 1 2 157 | // ^ | 158 | // | v 159 | // \---3 160 | g := graph.AdjacencyList{ 161 | 0: {1, 2}, 162 | 2: {3}, 163 | 3: {1}, 164 | } 165 | b := bits.New(len(g)) 166 | fmt.Println("node path bits") 167 | fmt.Println(" (3210)") 168 | fmt.Println("---- ----") 169 | alt.DepthFirst(g, 0, alt.PathBits(&b), 170 | alt.NodeVisitor(func(n graph.NI) { 171 | fmt.Printf("%4d %s\n", n, &b) 172 | })) 173 | // Output: 174 | // node path bits 175 | // (3210) 176 | // ---- ---- 177 | // 0 0001 178 | // 1 0011 179 | // 2 0101 180 | // 3 1101 181 | } 182 | 183 | func ExampleVisited() { 184 | // 0 185 | // / \ 186 | // 1-->2 187 | // ^ | 188 | // | v 189 | // \---3 190 | g := graph.AdjacencyList{ 191 | 0: {1, 2}, 192 | 1: {2}, 193 | 2: {3}, 194 | 3: {1}, 195 | } 196 | b := bits.New(len(g)) 197 | fmt.Println("3210") 198 | fmt.Println("----") 199 | alt.DepthFirst(g, 0, alt.Visited(&b), 200 | alt.NodeVisitor(func(graph.NI) { 201 | fmt.Println(b) 202 | })) 203 | // Output: 204 | // 3210 205 | // ---- 206 | // 0001 207 | // 0011 208 | // 0111 209 | // 1111 210 | } 211 | 212 | func ExampleRand() { 213 | // 0 214 | // | 215 | // ------------------- 216 | // | | | | | | | | | | 217 | // 1 2 3 4 5 6 7 8 9 10 218 | g := graph.AdjacencyList{ 219 | 0: {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 220 | 10: nil, 221 | } 222 | alt.DepthFirst(g, 0, alt.Rand(rand.New(rand.NewSource(7))), 223 | alt.NodeVisitor(func(n graph.NI) { 224 | fmt.Println(n) 225 | })) 226 | // Random output: 227 | // 0 228 | // 3 229 | // 1 230 | // 6 231 | // 4 232 | // 2 233 | // 7 234 | // 10 235 | // 9 236 | // 5 237 | // 8 238 | } 239 | 240 | func ExampleBreadthFirst_singlePath() { 241 | // arcs are directed right: 242 | // 1 3---5 243 | // / \ / / 244 | // 2 4---6--\ 245 | // \-/ 246 | g := graph.AdjacencyList{ 247 | 2: {1}, 248 | 1: {4}, 249 | 4: {3, 6}, 250 | 3: {5}, 251 | 6: {5, 6}, 252 | } 253 | var start, end graph.NI = 1, 6 254 | var f graph.FromList 255 | var visited int 256 | alt.BreadthFirst(g, start, alt.From(&f), 257 | alt.OkNodeVisitor(func(n graph.NI) bool { 258 | visited++ 259 | return n != end 260 | })) 261 | fmt.Println(visited, "nodes visited") 262 | fmt.Println("path:", f.PathTo(end, nil)) 263 | // Output: 264 | // 4 nodes visited 265 | // path: [1 4 6] 266 | } 267 | 268 | func ExampleBreadthFirst_allPaths() { 269 | // arcs are directed right: 270 | // 1 3---5 271 | // / \ / / 272 | // 2 4---6--\ 273 | // \-/ 274 | g := graph.AdjacencyList{ 275 | 2: {1}, 276 | 1: {4}, 277 | 4: {3, 6}, 278 | 3: {5}, 279 | 6: {5, 6}, 280 | } 281 | start := graph.NI(1) 282 | var f graph.FromList 283 | alt.BreadthFirst(g, start, alt.From(&f)) 284 | fmt.Println("Max path length:", f.MaxLen) 285 | p := make([]graph.NI, f.MaxLen) 286 | for n := range g { 287 | fmt.Println(n, f.PathTo(graph.NI(n), p)) 288 | } 289 | // Output: 290 | // Max path length: 4 291 | // 0 [] 292 | // 1 [1] 293 | // 2 [] 294 | // 3 [1 4 3] 295 | // 4 [1 4] 296 | // 5 [1 4 3 5] 297 | // 6 [1 4 6] 298 | } 299 | 300 | func ExampleBreadthFirst_traverse() { 301 | // arcs directed down 302 | // 0-- 303 | // /| \ 304 | // 1 2 3 305 | // /|\ |\ 306 | // 4 5 6 7 8 307 | g := graph.AdjacencyList{ 308 | 0: {1, 2, 3}, 309 | 2: {4, 5, 6}, 310 | 3: {7, 8}, 311 | 8: {}, 312 | } 313 | var f graph.FromList 314 | alt.BreadthFirst(g, 0, alt.From(&f), 315 | alt.NodeVisitor(func(n graph.NI) { 316 | fmt.Println("visit", n, "level", f.Paths[n].Len) 317 | })) 318 | // Output: 319 | // visit 0 level 1 320 | // visit 1 level 2 321 | // visit 2 level 2 322 | // visit 3 level 2 323 | // visit 4 level 3 324 | // visit 5 level 3 325 | // visit 6 level 3 326 | // visit 7 level 3 327 | // visit 8 level 3 328 | } 329 | 330 | func ExampleBreadthFirst_traverseRandom() { 331 | // arcs directed down 332 | // 0-- 333 | // /| \ 334 | // 1 2 3 335 | // /|\ |\ 336 | // 4 5 6 7 8 337 | g := graph.AdjacencyList{ 338 | 0: {1, 2, 3}, 339 | 2: {4, 5, 6}, 340 | 3: {7, 8}, 341 | 8: {}, 342 | } 343 | 344 | // only difference from non-random example 345 | r := rand.New(rand.NewSource(8)) 346 | 347 | var f graph.FromList 348 | alt.BreadthFirst(g, 0, alt.Rand(r), alt.From(&f), 349 | alt.NodeVisitor(func(n graph.NI) { 350 | fmt.Println("visit", n, "level", f.Paths[n].Len) 351 | })) 352 | // Random output: 353 | // visit 0 level 1 354 | // visit 1 level 2 355 | // visit 3 level 2 356 | // visit 2 level 2 357 | // visit 8 level 3 358 | // visit 5 level 3 359 | // visit 6 level 3 360 | // visit 4 level 3 361 | // visit 7 level 3 362 | } 363 | -------------------------------------------------------------------------------- /alt/undir.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package alt 5 | 6 | import ( 7 | "errors" 8 | 9 | "github.com/soniakeys/bits" 10 | "github.com/soniakeys/graph" 11 | ) 12 | 13 | // EulerianCycle finds an Eulerian cycle in a directed multigraph. 14 | // 15 | // * If g has no nodes, result is nil, nil. 16 | // 17 | // * If g is Eulerian, result is an Eulerian cycle with err = nil. 18 | // The cycle result is a list of nodes, where the first and last 19 | // nodes are the same. 20 | // 21 | // * Otherwise, result is nil, error 22 | // 23 | // The fast Eulerian cycle algorithm of the main graph library is destructive 24 | // on its input graph. A non-destructive variant simply makes a copy first. 25 | // This non-destructive variant for undirected graphs makes its copy not as 26 | // the usual slice-based adjacency list, but as a custom map-based one. 27 | // The idea was that the destructive removal of reciprocal edges requires 28 | // an additional scan of a slice-based neigbor list, potentially increasing 29 | // the asymptotic time complexity. A map-based adjacency list keeps node 30 | // retrieval constant time, preserving the time complexity of the original 31 | // algorithm. In practice, it doesn't seem worth the overhead of maps. 32 | // Anecdote shows it significantly slower. 33 | func EulerianCycle(g graph.Undirected) ([]graph.NI, error) { 34 | if g.Order() == 0 { 35 | return nil, nil 36 | } 37 | u := newUlerian(g) 38 | for u.s >= 0 { 39 | v := u.top() 40 | u.push() 41 | if u.top() != v { 42 | return nil, errors.New("not balanced") 43 | } 44 | u.keep() 45 | } 46 | if !u.uv.AllZeros() { 47 | return nil, errors.New("not strongly connected") 48 | } 49 | return u.p, nil 50 | } 51 | 52 | // undirected variant of similar class in dir.go 53 | type ulerian struct { 54 | g []map[graph.NI]int // map (multiset) based copy of graph 55 | m int // number of arcs in g, updated as g is consumed 56 | uv bits.Bits // unvisited 57 | // low end of p is stack of unfinished nodes 58 | // high end is finished path 59 | p []graph.NI // stack + path 60 | s int // stack pointer 61 | } 62 | 63 | func (u *ulerian) top() graph.NI { 64 | return u.p[u.s] 65 | } 66 | 67 | // starting with the node on top of the stack, move nodes with no arcs. 68 | func (u *ulerian) keep() { 69 | for u.s >= 0 { 70 | n := u.top() 71 | if len(u.g[n]) > 0 { 72 | break 73 | } 74 | u.p[u.m] = n 75 | u.s-- 76 | u.m-- 77 | } 78 | } 79 | 80 | func (e *ulerian) push() { 81 | for u := e.top(); ; { 82 | e.uv.SetBit(int(u), 0) 83 | arcs := e.g[u] 84 | if len(arcs) == 0 { 85 | return 86 | } 87 | // pick an arc 88 | var w graph.NI 89 | { 90 | var c int 91 | for w, c = range arcs { 92 | break 93 | } 94 | // consume arc 95 | if c > 1 { 96 | arcs[w]-- 97 | } else { 98 | delete(arcs, w) 99 | } 100 | // consume reciprocal arc as well 101 | r := e.g[w] 102 | if r[u] > 1 { 103 | r[u]-- 104 | } else { 105 | delete(r, u) 106 | } 107 | } 108 | e.s++ 109 | e.p[e.s] = w 110 | u = w 111 | } 112 | } 113 | 114 | func newUlerian(g graph.Undirected) *ulerian { 115 | a := g.AdjacencyList 116 | u := &ulerian{ 117 | g: make([]map[graph.NI]int, len(a)), 118 | uv: bits.New(len(a)), 119 | } 120 | // convert representation, this maintains time complexity for 121 | // the undirected case. 122 | m2 := 0 123 | for n, to := range a { 124 | m2 += len(to) 125 | s := map[graph.NI]int{} // a multiset for each node 126 | for _, to := range to { 127 | s[to]++ 128 | if to == graph.NI(n) { 129 | m2++ 130 | } 131 | } 132 | u.g[n] = s 133 | } 134 | u.m = m2 / 2 135 | u.p = make([]graph.NI, u.m+1) 136 | u.uv.SetAll() 137 | return u 138 | } 139 | -------------------------------------------------------------------------------- /alt/undir_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package alt_test 5 | 6 | import ( 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/soniakeys/graph" 11 | "github.com/soniakeys/graph/alt" 12 | ) 13 | 14 | func TestUndirectedEulerianCycle(t *testing.T) { 15 | var g graph.Undirected 16 | g.AddEdge(0, 1) 17 | g.AddEdge(0, 2) 18 | g.AddEdge(1, 2) 19 | g.AddEdge(1, 2) 20 | g.AddEdge(1, 2) 21 | g.AddEdge(2, 2) 22 | c, err := alt.EulerianCycle(g) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | // reconstruct from node list c 27 | var r graph.Undirected 28 | n1 := c[0] 29 | for _, n2 := range c[1:] { 30 | r.AddEdge(n1, n2) 31 | n1 = n2 32 | } 33 | // compare 34 | g.SortArcLists() 35 | r.SortArcLists() 36 | if !reflect.DeepEqual(r, g) { 37 | t.Fatal() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /anecdote/anec.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | // Anecdotal timings 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "math/rand" 11 | "runtime" 12 | "time" 13 | 14 | "github.com/soniakeys/graph" 15 | "github.com/soniakeys/graph/alt" 16 | ) 17 | 18 | func main() { 19 | fmt.Println("Anecdotal timings") 20 | fmt.Println(runtime.GOOS, runtime.GOARCH) 21 | random() 22 | prop() 23 | trav() 24 | allpairs() 25 | sssp() 26 | shortestone() 27 | eulerian() 28 | parallel() 29 | cycles() 30 | negcycles() 31 | } 32 | 33 | func h(n int) string { 34 | switch { 35 | case n < 1000: 36 | return fmt.Sprint(n) 37 | case n < 1e4: 38 | return fmt.Sprintf("%.1fK", float64(n)/1000) 39 | case n < 1e6: 40 | return fmt.Sprint(n/1000, "K") 41 | case n < 1e7: 42 | return fmt.Sprintf("%.1fM", float64(n)/1e6) 43 | default: 44 | return fmt.Sprint(n/1e6, "M") 45 | } 46 | } 47 | 48 | func random() { 49 | fmt.Println("\nRandom graph generation") 50 | fmt.Println("(arcs/edges lists arcs for directed graphs, edges for undirected)") 51 | fmt.Println("Graph type Nodes Arcs/edges Time") 52 | for _, tc := range []func() (string, int, int){ 53 | ChungLuSmall, ChungLuLarge, 54 | EucSmall, EucLarge, 55 | GeoSmall, GeoLarge, 56 | GnpDSmall, GnpDLarge, GnpUSmall, GnpULarge, 57 | GnmDSmall, GnmDLarge, GnmUSmall, GnmULarge, 58 | Gnm3USmall, Gnm3ULarge, 59 | KronDSmall, KronDLarge, KronUSmall, KronULarge, 60 | } { 61 | t := time.Now() 62 | g, n, a := tc() 63 | d := time.Now().Sub(t) 64 | fmt.Printf("%-25s %5s %5s %20s\n", g, h(n), h(a), d) 65 | } 66 | } 67 | 68 | func prop() { 69 | fmt.Println("\nProperties") 70 | fmt.Println("Method Graph Time") 71 | for _, tc := range []func() (string, string){ 72 | CCSmall, CCLarge, 73 | SCCKosarajuSmall, SCCKosarajuLarge, 74 | SCCTarjanSmall, SCCTarjanLarge, 75 | SCCPathSmall, SCCPathLarge, 76 | SCCPearceSmall, SCCPearceLarge, 77 | SCCEucSmall, SCCEucLarge, 78 | } { 79 | t := time.Now() 80 | m, g := tc() 81 | d := time.Now().Sub(t) 82 | fmt.Printf("%-22s %-22s %12s\n", m, g, d) 83 | } 84 | } 85 | 86 | func trav() { 87 | fmt.Println("\nTraversal") 88 | fmt.Println("Method Graph Time") 89 | for _, tc := range []func() (string, string){ 90 | DFSmall, DFLarge, BFSmall, BFLarge, 91 | AltBFSmall, AltBFLarge, 92 | } { 93 | t := time.Now() 94 | m, g := tc() 95 | d := time.Now().Sub(t) 96 | fmt.Printf("%-22s %-38s %12s\n", m, g, d) 97 | } 98 | 99 | } 100 | 101 | var geoSmallEnd graph.NI 102 | var geoLargeEnd graph.NI 103 | var geoSmallHeuristic func(graph.NI) float64 104 | var geoLargeHeuristic func(graph.NI) float64 105 | 106 | func allpairs() { 107 | fmt.Println("\nShortest path all pairs") 108 | fmt.Println("Method Graph Time") 109 | for _, tc := range []func() (string, string){ 110 | FloydEuc, FloydGeo, 111 | } { 112 | t := time.Now() 113 | m, g := tc() 114 | d := time.Now().Sub(t) 115 | fmt.Printf("%-22s %-38s %12s\n", m, g, d) 116 | } 117 | } 118 | 119 | func sssp() { 120 | fmt.Println("\nSingle source shortest path") 121 | fmt.Println("Method Graph Time") 122 | for _, tc := range []func() (string, string){ 123 | BellmanSmall, 124 | DijkstraAllSmall, DijkstraAllLarge, 125 | } { 126 | t := time.Now() 127 | m, g := tc() 128 | d := time.Now().Sub(t) 129 | fmt.Printf("%-22s %-38s %12s\n", m, g, d) 130 | } 131 | 132 | } 133 | 134 | func shortestone() { 135 | fmt.Println("\nSingle shortest path") 136 | fmt.Println("Method Graph Time") 137 | // pick end nodes about .7 distant from node 0 138 | p1 := geoSmallPos[0] 139 | nearestSmall, nearestLarge := 2., 2. 140 | var geoSmallEndPos struct{ X, Y float64 } 141 | var geoLargeEndPos struct{ X, Y float64 } 142 | for l := 1; l < len(geoSmallPos); l++ { 143 | p2 := geoSmallPos[1] 144 | d := math.Abs(.7 - math.Hypot(p2.X-p1.X, p2.Y-p1.Y)) 145 | if d < nearestSmall { 146 | nearestSmall = d 147 | geoSmallEnd = graph.NI(l) 148 | geoSmallEndPos = p2 149 | } 150 | p2 = geoLargePos[1] 151 | d = math.Abs(.7 - math.Hypot(p2.X-p1.X, p2.Y-p1.Y)) 152 | if d < nearestLarge { 153 | nearestLarge = d 154 | geoLargeEnd = graph.NI(l) 155 | geoLargeEndPos = p2 156 | } 157 | } 158 | // and define heuristics for AStar 159 | geoSmallHeuristic = func(from graph.NI) float64 { 160 | p := &geoSmallPos[from] 161 | return math.Hypot(geoSmallEndPos.X-p.X, geoSmallEndPos.Y-p.Y) 162 | } 163 | geoLargeHeuristic = func(from graph.NI) float64 { 164 | p := &geoLargePos[from] 165 | return math.Hypot(geoLargeEndPos.X-p.X, geoLargeEndPos.Y-p.Y) 166 | } 167 | for _, tc := range []func() (string, string){ 168 | Dijkstra1Small, Dijkstra1Large, 169 | AStarASmall, AStarALarge, AStarMSmall, AStarMLarge, 170 | } { 171 | t := time.Now() 172 | m, g := tc() 173 | d := time.Now().Sub(t) 174 | fmt.Printf("%-22s %-38s %12s\n", m, g, d) 175 | } 176 | } 177 | 178 | func eulerian() { 179 | fmt.Println("\nEulerian cycles") 180 | dirEuSmall := dirEu(1e3) 181 | dirEuLarge := dirEu(1e5) 182 | uEuSmall := uEu(1e3) 183 | uEuLarge := uEu(1e5) 184 | for _, r := range []euResult{ 185 | dirEuTest(dirEuSmall), // non-destructive first 186 | dirEuDTest(dirEuSmall), // this consumes dirEuSmall 187 | dirEuTest(dirEuLarge), 188 | dirEuDTest(dirEuLarge), 189 | uEuTest(uEuSmall), 190 | uEuDTest(uEuSmall), 191 | uEuTest(uEuLarge), 192 | uEuDTest(uEuLarge), 193 | } { 194 | fmt.Printf("%-22s %-38s %12s\n", r.method, r.tag, r.d) 195 | } 196 | } 197 | 198 | func parallel() { 199 | fmt.Println("\nParallel arc tests") 200 | fmt.Println("Graph Sort Map Best") 201 | for i, g := range []interface{}{ 202 | chungLuSmall, 203 | chungLuLarge, 204 | eucLarge, 205 | gnpDSmall, 206 | gnpDLarge, 207 | gnpUSmall, 208 | gnpULarge, 209 | gnmDSmall, 210 | gnmDLarge, 211 | gnmUSmall, 212 | gnmULarge, 213 | kronDSmall, 214 | kronDLarge, 215 | kronUSmall, 216 | kronULarge, 217 | } { 218 | var tm, ts time.Duration 219 | switch h := g.(type) { 220 | case graph.Directed: 221 | t := time.Now() 222 | alt.AnyParallelMap(h.AdjacencyList) 223 | tm = time.Now().Sub(t) 224 | t = time.Now() 225 | h.AnyParallel() 226 | ts = time.Now().Sub(t) 227 | case graph.Undirected: 228 | t := time.Now() 229 | alt.AnyParallelMap(h.AdjacencyList) 230 | tm = time.Now().Sub(t) 231 | t = time.Now() 232 | h.AnyParallel() 233 | ts = time.Now().Sub(t) 234 | } 235 | best := "Map" 236 | if ts < tm { 237 | best = "Sort" 238 | } 239 | fmt.Printf("%2d %12s %12s %s\n", i, ts, tm, best) 240 | } 241 | } 242 | 243 | func cycles() { 244 | fmt.Println("\nDirectedCycles") 245 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 246 | g := graph.GnmDirected(18, 90, r) 247 | g.ShuffleArcLists(r) 248 | fmt.Println("Method Graph Time") 249 | for _, tc := range []struct { 250 | f func(graph.Directed, func([]graph.NI) bool) 251 | s string 252 | }{ 253 | {graph.Directed.Cycles, "Johnson"}, 254 | {alt.TarjanCycles, "Tarjan"}, 255 | } { 256 | t := time.Now() 257 | tc.f(g, func(emit []graph.NI) bool { return true }) 258 | d := time.Now().Sub(t) 259 | fmt.Printf("%-22s %-38s %12s\n", tc.s, "Gnm 18 90", d) 260 | } 261 | } 262 | 263 | func negcycles() { 264 | fmt.Println("\nNegativeCycles") 265 | fmt.Println("Method Graph Time") 266 | k9_20 := complete(9, 20, 10) 267 | k11_20 := complete(11, 20, 10) 268 | k11_14 := complete(11, 14, 10) 269 | k13_14 := complete(13, 14, 10) 270 | w := func(l graph.LI) float64 { return float64(l) } 271 | filter := func(g graph.LabeledDirected, w graph.WeightFunc, emit func([]graph.Half) bool) { 272 | g.Cycles(func(cy []graph.Half) bool { 273 | d := 0. 274 | for _, h := range cy { 275 | d += w(h.Label) 276 | } 277 | if d < 0 { 278 | return emit(cy) 279 | } 280 | return true 281 | }) 282 | } 283 | for _, g := range []struct { 284 | g graph.LabeledDirected 285 | s string 286 | }{ 287 | {k9_20, "K9 2.0"}, 288 | {k11_20, "K11 2.0"}, 289 | {k11_14, "K11 1.4"}, 290 | {k13_14, "K13 1.4"}, 291 | } { 292 | for _, tc := range []struct { 293 | f func(graph.LabeledDirected, graph.WeightFunc, func([]graph.Half) bool) 294 | s string 295 | }{ 296 | {filter, "Filter Cycles"}, 297 | {alt.NegativeCycles, "Neg"}, 298 | {graph.LabeledDirected.NegativeCycles, "Neg'"}, 299 | } { 300 | if !(g.s == "K13 1.4" && tc.s == "Filter Cycles") { 301 | t := time.Now() 302 | tc.f(g.g, w, func([]graph.Half) bool { return true }) 303 | d := time.Now().Sub(t) 304 | fmt.Printf("%-22s %-38s %12s\n", tc.s, g.s, d) 305 | } 306 | } 307 | } 308 | } 309 | 310 | func complete(n, pNum, pDen int) graph.LabeledDirected { 311 | g := make(graph.LabeledAdjacencyList, n) 312 | c := (n*pDen + pNum - 1) / pNum 313 | for i := range g { 314 | to := make([]graph.Half, n-1) 315 | for k := range to { 316 | j := k 317 | if k >= i { 318 | j++ 319 | } 320 | d := i - j 321 | if d < 0 { 322 | d = -d 323 | } 324 | w := graph.LI(1) 325 | if d > c { 326 | w = -1 327 | } 328 | to[k] = graph.Half{graph.NI(j), w} 329 | } 330 | g[i] = to 331 | } 332 | return graph.LabeledDirected{g} 333 | } 334 | -------------------------------------------------------------------------------- /anecdote/eulerian.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | // Anecdotal timings 5 | package main 6 | 7 | import ( 8 | "log" 9 | "math/rand" 10 | "time" 11 | 12 | "github.com/soniakeys/graph" 13 | "github.com/soniakeys/graph/alt" 14 | ) 15 | 16 | type dirEuGraph struct { 17 | g graph.Directed 18 | ma int 19 | tag string 20 | } 21 | 22 | // generate directed graph with n nodes, 10*n arcs 23 | func dirEu(n int) dirEuGraph { 24 | unvis := make([]int, n-1) 25 | for i := range unvis { 26 | unvis[i] = i + 1 27 | } 28 | ma := n * 10 29 | a := make(graph.AdjacencyList, n) 30 | var fr, to int 31 | for i := 1; i < ma; i++ { 32 | // draw either from unvis or all nodes, p of drawing from unvis must 33 | // be 1 when (ma-i) == len(unvis). p of drawing from unvis is 0 at 34 | // first. 35 | if len(unvis) == 0 || i < rand.Intn(ma-len(unvis)) { 36 | to = rand.Intn(n) 37 | } else { 38 | x := rand.Intn(len(unvis)) 39 | to = unvis[x] 40 | last := len(unvis) - 1 41 | unvis[x] = unvis[last] 42 | unvis = unvis[:last] 43 | } 44 | a[fr] = append(a[fr], graph.NI(to)) 45 | fr = to 46 | } 47 | a[fr] = append(a[fr], 0) 48 | return dirEuGraph{ 49 | graph.Directed{a}, 50 | ma, 51 | "directed, " + h(ma) + " arcs", 52 | } 53 | } 54 | 55 | type uEuGraph struct { 56 | g graph.Undirected 57 | m int 58 | tag string 59 | } 60 | 61 | // generate undirected graph with n nodes, 10*n edges 62 | func uEu(n int) uEuGraph { 63 | unvis := make([]int, n-1) 64 | for i := range unvis { 65 | unvis[i] = i + 1 66 | } 67 | m := n * 10 68 | var u graph.Undirected 69 | var n1, n2 int 70 | for i := 1; i < m; i++ { 71 | // draw either from unvis or all nodes, p of drawing from unvis must 72 | // be 1 when (ma-i) == len(unvis). p of drawing from unvis is 0 at 73 | // first. 74 | if len(unvis) == 0 || i < rand.Intn(m-len(unvis)) { 75 | n2 = rand.Intn(n) 76 | } else { 77 | x := rand.Intn(len(unvis)) 78 | n2 = unvis[x] 79 | last := len(unvis) - 1 80 | unvis[x] = unvis[last] 81 | unvis = unvis[:last] 82 | } 83 | u.AddEdge(graph.NI(n1), graph.NI(n2)) 84 | n1 = n2 85 | } 86 | u.AddEdge(graph.NI(n1), 0) 87 | return uEuGraph{u, m, "undirected, " + h(m) + " edges"} 88 | } 89 | 90 | type euResult struct { 91 | method string 92 | tag string 93 | d time.Duration 94 | } 95 | 96 | func dirEuTest(g dirEuGraph) euResult { 97 | t := time.Now() 98 | _, err := g.g.EulerianCycle() 99 | d := time.Now().Sub(t) 100 | if err != nil { 101 | log.Fatal(err) 102 | } 103 | return euResult{"EulerianCycle", g.tag, d} 104 | } 105 | 106 | func dirEuDTest(g dirEuGraph) euResult { 107 | t := time.Now() 108 | _, err := g.g.EulerianCycleD(g.ma) 109 | d := time.Now().Sub(t) 110 | if err != nil { 111 | log.Fatal(err) 112 | } 113 | return euResult{"EulerianCycleD", g.tag, d} 114 | } 115 | 116 | func uEuTest(g uEuGraph) euResult { 117 | t := time.Now() 118 | _, err := alt.EulerianCycle(g.g) 119 | d := time.Now().Sub(t) 120 | if err != nil { 121 | log.Fatal(err) 122 | } 123 | return euResult{"Alt EulerianCycle", g.tag, d} 124 | } 125 | 126 | func uEuDTest(g uEuGraph) euResult { 127 | t := time.Now() 128 | _, err := g.g.EulerianCycleD(g.m) 129 | d := time.Now().Sub(t) 130 | if err != nil { 131 | log.Fatal(err) 132 | } 133 | return euResult{"EulerianCycleD", g.tag, d} 134 | } 135 | -------------------------------------------------------------------------------- /anecdote/prop.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/soniakeys/graph" 8 | "github.com/soniakeys/graph/alt" 9 | ) 10 | 11 | var chungLuSmallCCRep graph.NI 12 | var chungLuSmallCCma int 13 | var chungLuSmallCCTag string 14 | 15 | func CCSmall() (string, string) { 16 | reps, orders, ma := chungLuSmall.ConnectedComponentReps() 17 | max := 0 18 | for i, o := range orders { 19 | if o > max { 20 | max = o 21 | chungLuSmallCCRep = reps[i] 22 | chungLuSmallCCma = ma[i] 23 | } 24 | } 25 | chungLuSmallCCTag = "ChungLu giant component " + h(max) + " nds" 26 | return "Connected Components", chungLuSmallTag 27 | } 28 | 29 | var chungLuLargeCCRep graph.NI 30 | var chungLuLargeCCma int 31 | var chungLuLargeCCTag string 32 | 33 | func CCLarge() (string, string) { 34 | reps, orders, ma := chungLuLarge.ConnectedComponentReps() 35 | max := 0 36 | for i, o := range orders { 37 | if o > max { 38 | max = o 39 | chungLuLargeCCRep = reps[i] 40 | chungLuLargeCCma = ma[i] 41 | } 42 | } 43 | chungLuLargeCCTag = "ChungLu giant component " + h(max) + " nds" 44 | return "Connected Components", chungLuLargeTag 45 | } 46 | 47 | var eucSmallSCCRep graph.NI 48 | var eucSmallSCCTag string 49 | 50 | func SCCEucSmall() (string, string) { 51 | max := 0 52 | eucSmall.StronglyConnectedComponents(func(c []graph.NI) bool { 53 | if len(c) > max { 54 | max = len(c) 55 | eucSmallSCCRep = c[0] 56 | } 57 | return true 58 | }) 59 | eucSmallSCCTag = "Euclidean giant component " + h(max) + " nds" 60 | return "SCC (Pearce)", eucSmallTag 61 | } 62 | 63 | var eucLargeSCCRep graph.NI 64 | var eucLargeSCCTag string 65 | 66 | func SCCEucLarge() (string, string) { 67 | max := 0 68 | eucLarge.StronglyConnectedComponents(func(c []graph.NI) bool { 69 | if len(c) > max { 70 | max = len(c) 71 | eucLargeSCCRep = c[0] 72 | } 73 | return true 74 | }) 75 | eucLargeSCCTag = "Euclidean giant component " + h(max) + " nds" 76 | return "SCC (Pearce)", eucLargeTag 77 | } 78 | 79 | var kronDSmallSCCRep graph.NI 80 | var kronDSmallSCCTag string 81 | 82 | func SCCPearceSmall() (string, string) { 83 | max := 0 84 | kronDSmall.StronglyConnectedComponents(func(c []graph.NI) bool { 85 | if len(c) > max { 86 | max = len(c) 87 | kronDSmallSCCRep = c[0] 88 | } 89 | return true 90 | }) 91 | kronDSmallSCCTag = "Kronecker giant component " + h(max) + " nds" 92 | return "SCC (Pearce)", kronDSmallTag 93 | } 94 | 95 | func SCCPearceLarge() (string, string) { 96 | max := 0 97 | kronDLarge.StronglyConnectedComponents(func(c []graph.NI) bool { 98 | if len(c) > max { 99 | max = len(c) 100 | kronDLargeSCCRep = c[0] 101 | } 102 | return true 103 | }) 104 | kronDLargeSCCTag = "Kronecker giant component " + h(max) + " nds" 105 | return "SCC (Pearce)", kronDLargeTag 106 | } 107 | 108 | func SCCPathSmall() (string, string) { 109 | max := 0 110 | alt.SCCPathBased(kronDSmall, func(c []graph.NI) bool { 111 | if len(c) > max { 112 | max = len(c) 113 | kronDSmallSCCRep = c[0] 114 | } 115 | return true 116 | }) 117 | kronDSmallSCCTag = "Kronecker giant component " + h(max) + " nds" 118 | return "SCC path based", kronDSmallTag 119 | } 120 | 121 | var kronDLargeSCCRep graph.NI 122 | var kronDLargeSCCTag string 123 | 124 | func SCCPathLarge() (string, string) { 125 | max := 0 126 | alt.SCCPathBased(kronDLarge, func(c []graph.NI) bool { 127 | if len(c) > max { 128 | max = len(c) 129 | kronDLargeSCCRep = c[0] 130 | } 131 | return true 132 | }) 133 | kronDLargeSCCTag = "Kronecker giant component " + h(max) + " nds" 134 | return "SCC path based", kronDLargeTag 135 | } 136 | 137 | func SCCTarjanSmall() (string, string) { 138 | max := 0 139 | alt.SCCTarjan(kronDSmall, func(c []graph.NI) bool { 140 | if len(c) > max { 141 | max = len(c) 142 | kronDSmallSCCRep = c[0] 143 | } 144 | return true 145 | }) 146 | kronDSmallSCCTag = "Kronecker giant component " + h(max) + " nds" 147 | return "SCC Tarjan", kronDSmallTag 148 | } 149 | 150 | func SCCTarjanLarge() (string, string) { 151 | max := 0 152 | alt.SCCTarjan(kronDLarge, func(c []graph.NI) bool { 153 | if len(c) > max { 154 | max = len(c) 155 | kronDLargeSCCRep = c[0] 156 | } 157 | return true 158 | }) 159 | kronDLargeSCCTag = "Kronecker giant component " + h(max) + " nds" 160 | return "SCC Tarjan", kronDLargeTag 161 | } 162 | func SCCKosarajuSmall() (string, string) { 163 | max := 0 164 | alt.SCCKosaraju(kronDSmall, func(c []graph.NI) bool { 165 | if len(c) > max { 166 | max = len(c) 167 | kronDSmallSCCRep = c[0] 168 | } 169 | return true 170 | }) 171 | kronDSmallSCCTag = "Kronecker giant component " + h(max) + " nds" 172 | return "SCC Kosaraju", kronDSmallTag 173 | } 174 | 175 | func SCCKosarajuLarge() (string, string) { 176 | max := 0 177 | alt.SCCKosaraju(kronDLarge, func(c []graph.NI) bool { 178 | if len(c) > max { 179 | max = len(c) 180 | kronDLargeSCCRep = c[0] 181 | } 182 | return true 183 | }) 184 | kronDLargeSCCTag = "Kronecker giant component " + h(max) + " nds" 185 | return "SCC Kosaraju", kronDLargeTag 186 | } 187 | -------------------------------------------------------------------------------- /anecdote/random.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/soniakeys/graph" 12 | ) 13 | 14 | var r = rand.New(rand.NewSource(time.Now().UnixNano())) 15 | 16 | var chungLuSmall graph.Undirected 17 | var chungLuSmallTag string 18 | 19 | func ChungLuSmall() (string, int, int) { 20 | const n = 1e4 21 | w := make([]float64, n) 22 | for i := range w { 23 | w[i] = 5 + 10*float64(n-i)/float64(n) 24 | } 25 | chungLuSmall, m = graph.ChungLu(w, r) 26 | chungLuSmallTag = "ChungLu " + h(n) + " nds" 27 | return "Chung Lu (undirected)", n, m 28 | } 29 | 30 | var chungLuLarge graph.Undirected 31 | var chungLuLargeTag string 32 | 33 | func ChungLuLarge() (string, int, int) { 34 | const n = 2e5 35 | w := make([]float64, n) 36 | for i := range w { 37 | w[i] = 2 + 50*n/float64(i+1) 38 | } 39 | chungLuLarge, m = graph.ChungLu(w, r) 40 | chungLuLargeTag = "ChungLu " + h(n) + " nds" 41 | return "Chung Lu (undirected)", n, m 42 | } 43 | 44 | var eucSmall graph.LabeledDirected 45 | var eucSmallTag string 46 | var eucSmallWt []float64 47 | var eucSmallWtFunc = func(n graph.LI) float64 { return eucSmallWt[n] } 48 | 49 | func EucSmall() (string, int, int) { 50 | const n = 1024 51 | const ma = 5e3 52 | var err error 53 | eucSmall, _, eucSmallWt, err = graph.LabeledEuclidean(n, ma, 1, 1, r) 54 | if err != nil { 55 | return "nope", n, ma 56 | } 57 | eucSmallTag = "Euclidean " + h(n) + " nds" 58 | return "Euclidean (directed)", n, ma 59 | } 60 | 61 | var eucLarge graph.Directed 62 | var eucLargeTag string 63 | 64 | func EucLarge() (string, int, int) { 65 | const n = 1048576 66 | const ma = 5e6 67 | var err error 68 | eucLarge, _, err = graph.Euclidean(n, ma, 1, 1, r) 69 | if err != nil { 70 | return "nope", n, ma 71 | } 72 | eucLargeTag = "Euclidean " + h(n) + " nds" 73 | return "Euclidean (directed)", n, ma 74 | } 75 | 76 | var geoSmall graph.LabeledUndirected 77 | var geoSmallPos []struct{ X, Y float64 } 78 | var geoSmallWt []float64 79 | var geoSmallWtFunc = func(n graph.LI) float64 { return geoSmallWt[n] } 80 | var geoSmallTag string 81 | 82 | func GeoSmall() (string, int, int) { 83 | const n = 1000 84 | geoSmall, geoSmallPos, geoSmallWt = graph.LabeledGeometric(n, .1, r) 85 | geoSmallTag = "Geometric " + h(n) + " nds" 86 | return "Geometric (undirected)", n, len(geoSmallWt) 87 | } 88 | 89 | var geoLarge graph.LabeledUndirected 90 | var geoLargePos []struct{ X, Y float64 } 91 | var geoLargeWt []float64 92 | var geoLargeWtFunc = func(n graph.LI) float64 { return geoLargeWt[n] } 93 | var geoLargeTag string 94 | 95 | func GeoLarge() (string, int, int) { 96 | const n = 3e4 97 | geoLarge, geoLargePos, geoLargeWt = graph.LabeledGeometric(n, .01, r) 98 | geoLargeTag = "Geometric " + h(n) + " nds" 99 | return "Geometric (undirected)", n, len(geoLargeWt) 100 | } 101 | 102 | var gnpUSmall graph.Undirected 103 | var gnpUSmallTag string 104 | 105 | func GnpUSmall() (string, int, int) { 106 | const n = 1000 107 | gnpUSmallTag = fmt.Sprint("Gnp ", n, " nds") 108 | gnpUSmall, m = graph.GnpUndirected(n, .2, r) 109 | return "Gnp undirected", n, m 110 | } 111 | 112 | var gnpULarge graph.Undirected 113 | var gnpULargeTag string 114 | 115 | func GnpULarge() (string, int, int) { 116 | const n = 2e4 117 | gnpULargeTag = fmt.Sprint("Gnp ", n, " nds") 118 | gnpULarge, m = graph.GnpUndirected(n, .105, r) 119 | return "Gnp undirected", n, m 120 | } 121 | 122 | var gnmUSmall graph.Undirected 123 | var gnmUSmallTag string 124 | 125 | func GnmUSmall() (string, int, int) { 126 | const n = 1000 127 | const m = 100e3 128 | gnmUSmallTag = fmt.Sprint("Gnm ", n, " nds") 129 | gnmUSmall = graph.GnmUndirected(n, m, r) 130 | return "Gnm undirected", n, m 131 | } 132 | 133 | var gnmULarge graph.Undirected 134 | var gnmULargeTag string 135 | 136 | func GnmULarge() (string, int, int) { 137 | const n = 20e3 138 | const m = 20e6 139 | gnmULargeTag = fmt.Sprint("Gnm ", n, " nds") 140 | gnmULarge = graph.GnmUndirected(n, m, r) 141 | return "Gnm undirected", n, m 142 | } 143 | 144 | func Gnm3USmall() (string, int, int) { 145 | const n = 1000 146 | const m = 100e3 147 | graph.Gnm3Undirected(n, m, r) 148 | return "Gnm3 undirected", n, m 149 | } 150 | 151 | func Gnm3ULarge() (string, int, int) { 152 | const n = 20e3 153 | const m = 20e6 154 | graph.Gnm3Undirected(n, m, r) 155 | return "Gnm3 undirected", n, m 156 | } 157 | 158 | var gnpDSmall graph.Directed 159 | var gnpDSmallTag string 160 | 161 | func GnpDSmall() (string, int, int) { 162 | const n = 1000 163 | gnpDSmallTag = fmt.Sprint("Gnp ", n, " nds") 164 | gnpDSmall, ma = graph.GnpDirected(n, .101, r) 165 | return "Gnp directed", n, ma 166 | } 167 | 168 | var gnpDLarge graph.Directed 169 | var gnpDLargeTag string 170 | 171 | func GnpDLarge() (string, int, int) { 172 | const n = 2e4 173 | gnpDLargeTag = fmt.Sprint("Gnp ", n, " nds") 174 | gnpDLarge, ma = graph.GnpDirected(n, .05, r) 175 | return "Gnp directed", n, ma 176 | } 177 | 178 | var gnmDSmall graph.Directed 179 | var gnmDSmallTag string 180 | 181 | func GnmDSmall() (string, int, int) { 182 | const n = 1000 183 | const ma = 100e3 184 | gnmDSmallTag = fmt.Sprint("Gnm ", n, " nds") 185 | gnmDSmall = graph.GnmDirected(n, ma, r) 186 | return "Gnm directed", n, ma 187 | } 188 | 189 | var gnmDLarge graph.Directed 190 | var gnmDLargeTag string 191 | 192 | func GnmDLarge() (string, int, int) { 193 | const n = 20e3 194 | const ma = 20e6 195 | gnmDLargeTag = fmt.Sprint("Gnm ", n, " nds") 196 | gnmDLarge = graph.GnmDirected(n, ma, r) 197 | return "Gnm directed", n, ma 198 | } 199 | 200 | var kronDSmall graph.Directed 201 | var kronDSmallTag string 202 | var m, ma int 203 | 204 | func KronDSmall() (string, int, int) { 205 | kronDSmall, ma = graph.KroneckerDirected(11, 7, r) 206 | kronDSmallTag = "Kronecker " + h(kronDSmall.Order()) + "nds" 207 | return "Kronecker directed", kronDSmall.Order(), ma 208 | } 209 | 210 | var kronDLarge graph.Directed 211 | var kronDLargeTag string 212 | 213 | func KronDLarge() (string, int, int) { 214 | kronDLarge, ma = graph.KroneckerDirected(17, 21, r) 215 | kronDLargeTag = "Kronecker " + h(kronDLarge.Order()) + "nds" 216 | return "Kronecker directed", kronDLarge.Order(), ma 217 | } 218 | 219 | var kronUSmall graph.Undirected 220 | var kronUSmallTag string 221 | 222 | func KronUSmall() (string, int, int) { 223 | kronUSmall, m = graph.KroneckerUndirected(11, 7, r) 224 | kronUSmallTag = "Kronecker " + h(kronUSmall.Order()) + "nds" 225 | return "Kronecker undirected", kronUSmall.Order(), m 226 | } 227 | 228 | var kronULarge graph.Undirected 229 | var kronULargeTag string 230 | 231 | func KronULarge() (string, int, int) { 232 | kronULarge, m = graph.KroneckerUndirected(17, 21, r) 233 | kronULargeTag = "Kronecker " + h(kronULarge.Order()) + " nds" 234 | return "Kronecker undirected", kronULarge.Order(), m 235 | } 236 | -------------------------------------------------------------------------------- /anecdote/readme.adoc: -------------------------------------------------------------------------------- 1 | = Anecote 2 | 3 | Here are some timings, termed "anecdotal" because they are single runs of 4 | methods over single random data sets. More convincing would be to use the 5 | benchmark capabilities of the testing library and to test over a greater 6 | number of graphs. But that is tedious. This is quick and dirty. 7 | 8 | This is (yet another) work in progress. It covers only a few methods. 9 | It has already proven valuable though in showing some basic capacities 10 | (and also illuminating some problems!) 11 | 12 | Below is output from a sample run: 13 | 14 | .... 15 | Anecdotal timings 16 | linux amd64 17 | 18 | Random graph generation 19 | (arcs/edges lists arcs for directed graphs, edges for undirected) 20 | Graph type Nodes Arcs/edges Time 21 | Chung Lu (undirected) 10K 49K 22.295873ms 22 | Chung Lu (undirected) 200K 27M 3.306262789s 23 | Euclidean (directed) 1.0K 5.0K 880.61µs 24 | Euclidean (directed) 1.0M 5.0M 1.640114892s 25 | Geometric (undirected) 1.0K 14K 6.639848ms 26 | Geometric (undirected) 30K 140K 4.421220575s 27 | Gnp directed 1.0K 100K 4.393283ms 28 | Gnp directed 20K 19M 798.120249ms 29 | Gnp undirected 1.0K 99K 5.429077ms 30 | Gnp undirected 20K 20M 1.2374245s 31 | Gnm directed 1.0K 100K 35.710577ms 32 | Gnm directed 20K 20M 5.44915849s 33 | Gnm undirected 1.0K 100K 16.044223ms 34 | Gnm undirected 20K 20M 6.190043934s 35 | Gnm3 undirected 1.0K 100K 18.04302ms 36 | Gnm3 undirected 20K 20M 8.547999617s 37 | Kronecker directed 1.5K 12K 8.815785ms 38 | Kronecker directed 94K 2.5M 3.904553518s 39 | Kronecker undirected 1.5K 11K 7.640889ms 40 | Kronecker undirected 94K 2.4M 5.189660681s 41 | 42 | Properties 43 | Method Graph Time 44 | Connected Components ChungLu 10K nds 2.700921ms 45 | Connected Components ChungLu 200K nds 341.589606ms 46 | SCC path based Euclidean 1.0K nds 129.029µs 47 | SCC path based Euclidean 1.0M nds 518.286652ms 48 | SCC Pearce Euclidean 1.0K nds 169.551µs 49 | SCC Pearce Euclidean 1.0M nds 337.487082ms 50 | SCC Tarjan Euclidean 1.0K nds 222.04µs 51 | SCC Tarjan Euclidean 1.0M nds 449.002526ms 52 | 53 | Traversal 54 | Method Graph Time 55 | DepthFirst ChungLu giant component 10.0K nds 2.311187ms 56 | DepthFirst ChungLu giant component 200K nds 497.116607ms 57 | BreadthFirst ChungLu giant component 10.0K nds 900.545µs 58 | BreadthFirst ChungLu giant component 200K nds 119.814565ms 59 | BreadthFirst2 ChungLu giant component 10.0K nds 1.413648ms 60 | BreadthFirst2 ChungLu giant component 200K nds 154.937394ms 61 | 62 | Shortest path all pairs 63 | Method Graph Time 64 | Floyd-Warshall Euclidean 1.0K nds 1.429651474s 65 | Floyd-Warshall Geometric 1.0K nds 1.392797048s 66 | 67 | Single source shortest path 68 | Method Graph Time 69 | Bellman-Ford Euclidean giant component 1.0K nds 445.786µs 70 | Dijkstra all paths Geometric 1.0K nds 665.296µs 71 | Dijkstra all paths Geometric 30K nds 15.004497ms 72 | 73 | Single shortest path 74 | Method Graph Time 75 | Dijkstra single path Geometric 1.0K nds 109.686µs 76 | Dijkstra single path Geometric 30K nds 1.643191ms 77 | AStarA Geometric 1.0K nds 22.919µs 78 | AStarA Geometric 30K nds 307.947µs 79 | AStarM Geometric 1.0K nds 19.834µs 80 | AStarM Geometric 30K nds 309.677µs 81 | .... 82 | -------------------------------------------------------------------------------- /anecdote/short.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package main 5 | 6 | func FloydEuc() (string, string) { 7 | eucSmall.DistanceMatrix(eucSmallWtFunc).FloydWarshall() 8 | return "Floyd-Warshall", eucSmallTag 9 | } 10 | 11 | func FloydGeo() (string, string) { 12 | geoSmall.DistanceMatrix(geoSmallWtFunc).FloydWarshall() 13 | return "Floyd-Warshall", geoSmallTag 14 | } 15 | 16 | func BellmanSmall() (string, string) { 17 | eucSmall.BellmanFord(eucSmallWtFunc, eucSmallSCCRep) 18 | return "Bellman-Ford", eucSmallSCCTag 19 | } 20 | 21 | func DijkstraAllSmall() (string, string) { 22 | geoSmall.Dijkstra(0, -1, geoSmallWtFunc) 23 | return "Dijkstra all paths", geoSmallTag 24 | } 25 | 26 | func DijkstraAllLarge() (string, string) { 27 | geoLarge.Dijkstra(0, -1, geoLargeWtFunc) 28 | return "Dijkstra all paths", geoLargeTag 29 | } 30 | 31 | func Dijkstra1Small() (string, string) { 32 | geoSmall.Dijkstra(0, geoSmallEnd, geoSmallWtFunc) 33 | return "Dijkstra single path", geoSmallTag 34 | } 35 | 36 | func Dijkstra1Large() (string, string) { 37 | geoLarge.Dijkstra(0, geoLargeEnd, geoLargeWtFunc) 38 | return "Dijkstra single path", geoLargeTag 39 | } 40 | 41 | func AStarASmall() (string, string) { 42 | geoSmall.AStarA(geoSmallWtFunc, 0, geoSmallEnd, geoSmallHeuristic) 43 | return "AStarA", geoSmallTag 44 | } 45 | 46 | func AStarALarge() (string, string) { 47 | geoLarge.AStarA(geoLargeWtFunc, 0, geoLargeEnd, geoLargeHeuristic) 48 | return "AStarA", geoLargeTag 49 | } 50 | 51 | func AStarMSmall() (string, string) { 52 | geoSmall.AStarM(geoSmallWtFunc, 0, geoSmallEnd, geoSmallHeuristic) 53 | return "AStarM", geoSmallTag 54 | } 55 | 56 | func AStarMLarge() (string, string) { 57 | geoLarge.AStarM(geoLargeWtFunc, 0, geoLargeEnd, geoLargeHeuristic) 58 | return "AStarM", geoLargeTag 59 | } 60 | -------------------------------------------------------------------------------- /anecdote/trav.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/soniakeys/bits" 8 | "github.com/soniakeys/graph" 9 | "github.com/soniakeys/graph/alt" 10 | ) 11 | 12 | func DFSmall() (string, string) { 13 | b := bits.New(chungLuSmall.Order()) 14 | alt.DepthFirst(chungLuSmall.AdjacencyList, 15 | chungLuSmallCCRep, alt.Visited(&b)) 16 | return "DepthFirst", chungLuSmallCCTag 17 | } 18 | 19 | func DFLarge() (string, string) { 20 | b := bits.New(chungLuLarge.Order()) 21 | alt.DepthFirst(chungLuLarge.AdjacencyList, 22 | chungLuLargeCCRep, alt.Visited(&b)) 23 | return "DepthFirst", chungLuLargeCCTag 24 | } 25 | 26 | func BFSmall() (string, string) { 27 | chungLuSmall.AdjacencyList.BreadthFirst(chungLuSmallCCRep, 28 | func(graph.NI) {}) 29 | return "BreadthFirst", chungLuSmallCCTag 30 | } 31 | 32 | func BFLarge() (string, string) { 33 | chungLuLarge.AdjacencyList.BreadthFirst(chungLuLargeCCRep, 34 | func(graph.NI) {}) 35 | return "BreadthFirst", chungLuLargeCCTag 36 | } 37 | 38 | func AltBFSmall() (string, string) { 39 | g := chungLuSmall.AdjacencyList 40 | alt.BreadthFirst2(g, g, chungLuSmallCCma, chungLuSmallCCRep, nil, 41 | func(graph.NI) bool { return true }) 42 | return "DO BreadthFirst", chungLuSmallCCTag 43 | } 44 | 45 | func AltBFLarge() (string, string) { 46 | g := chungLuLarge.AdjacencyList 47 | alt.BreadthFirst2(g, g, chungLuLargeCCma, chungLuLargeCCRep, nil, 48 | func(graph.NI) bool { return true }) 49 | return "DO BreadthFirst", chungLuLargeCCTag 50 | } 51 | -------------------------------------------------------------------------------- /bench/DijkstraAllPaths.adoc: -------------------------------------------------------------------------------- 1 | # Dijkstra performance 2 | 3 | Dijkstra's algorithm is widely shown to have O(n log n) asymtotic performance. 4 | To test performance random graphs are constructed with graph.EuclideanLabeled 5 | and random start nodes are chosen. As shown in the example here, these graphs 6 | can have sinks and even isolated nodes. Start nodes are are chosen to not be 7 | sink nodes but are otherwise random. 8 | 9 | image::https://cdn.rawgit.com/soniakeys/graph/svg-v0/bench/img/Euclidean.svg[] 10 | 11 | The Benchmark function of the testing package is useful for accurate timings 12 | on a given graph from a given start node. Graphs of various sizes are 13 | constructed with order N a power of 2 ranging from 2^4 through 2^22. 14 | On the machine used for testing these graphs fit easily in RAM and don't take 15 | terribly long to test. Each graph is tested with five randomly chosen start 16 | nodes. The minimum and maximum of the five times for each graph are shown here 17 | with error bars. Also an n log n curve is fit and shown with the solid line 18 | 19 | image::https://cdn.rawgit.com/soniakeys/graph/svg-v0/bench/img/DijkstraAllPaths.svg[] 20 | 21 | The plot shows the implementation performing near the expected O(n log n). 22 | Inspection of the code shows that there is certainly an O(n) term but the plot 23 | indicates this term must have little significance. Also complications of 24 | memory management and memory architecture can be expected to add additional 25 | terms but these too appear to have little significance over the range tested. 26 | 27 | As an additional test, AllPaths is benchmarked on graph.Geometric graphs. 28 | These are undirected graphs with some different connectivity. 29 | 30 | image::https://cdn.rawgit.com/soniakeys/graph/svg-v0/bench/img/Geometric.svg[] 31 | 32 | A construction parameter for these graphs is not the number of edges but a 33 | radius from each node within which nodes are connected. The resulting number 34 | of edges can be controlled with the radius parameter but is somewhat imprecise. 35 | 36 | image::https://cdn.rawgit.com/soniakeys/graph/svg-v0/bench/img/DijkstraAllPathsGeo.svg[] 37 | 38 | The test here is over a shorter range of graph sizes because the code behind 39 | the current graph.Geometric is simple and O(n²) and not practical for larger 40 | graphs. The curve fit is not quite as good as with the Euclidean graphs but 41 | still fairly close to n log n over the range tested. 42 | 43 | -------------------------------------------------------------------------------- /bench/img/DijkstraAllPaths.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | // +build ignore 5 | 6 | //go:generate go run DijkstraAllPaths.go 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "image/color" 13 | "log" 14 | "math" 15 | "math/rand" 16 | "testing" 17 | "time" 18 | 19 | "github.com/gonum/plot" 20 | "github.com/gonum/plot/plotter" 21 | "github.com/gonum/plot/plotutil" 22 | "github.com/gonum/plot/vg" 23 | "github.com/soniakeys/graph" 24 | ) 25 | 26 | func main() { 27 | euc() 28 | geo() 29 | } 30 | 31 | func euc() { 32 | r := rand.New(rand.NewSource(time.Now().Unix())) 33 | ns := []int{16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 34 | 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304} 35 | rep := 5 36 | pts := make([]plotter.XYer, len(ns)) 37 | c := 0. 38 | top := len(ns) / 2 39 | bot := len(ns) - top 40 | fmt.Println("fit top", top) 41 | for i, n := range ns { 42 | g, _, wt, err := graph.LabeledEuclidean(n, n*10, 4, 100, r) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | w := func(l graph.LI) float64 { return wt[l] } 47 | xys := make(plotter.XYs, rep) 48 | pts[i] = xys 49 | for j := 0; j < rep; j++ { 50 | var start graph.NI 51 | for { 52 | start = graph.NI(r.Intn(n)) 53 | if len(g.LabeledAdjacencyList[start]) > 0 { 54 | break 55 | } 56 | } 57 | b := testing.Benchmark(func(b *testing.B) { 58 | for i := 0; i < b.N; i++ { 59 | g.Dijkstra(start, -1, w) 60 | } 61 | }) 62 | fmt.Printf("n=%4d, run %d: %v\n", n, j, b) 63 | x := float64(n) 64 | y := float64(b.NsPerOp()) * .001 65 | xys[j] = struct{ X, Y float64 }{x, y} 66 | if i >= bot { 67 | c += y / (x * math.Log(x)) 68 | } 69 | } 70 | } 71 | c /= float64(top * rep) 72 | p, err := plot.New() 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | p.Title.Text = "Dijkstra.AllPaths, Directed Graph\nArc Size MA = 10N" 77 | p.X.Label.Text = "Graph Order, N" 78 | p.Y.Label.Text = "µsec" 79 | p.X.Scale = plot.LogScale{} 80 | p.Y.Scale = plot.LogScale{} 81 | p.X.Tick.Marker = plot.LogTicks{} 82 | p.Y.Tick.Marker = plot.LogTicks{} 83 | 84 | nln := plotter.NewFunction(func(n float64) float64 { 85 | return c * n * math.Log(n) 86 | }) 87 | nln.Color = color.RGBA{B: 127, A: 255} 88 | p.Add(nln) 89 | p.Legend.Add(fmt.Sprintf("C(n log n), C = %.2f ", c), nln) 90 | 91 | mmm, err := plotutil.NewErrorPoints(meanMinMax, pts...) 92 | if err != nil { 93 | log.Fatal(err) 94 | } 95 | if err = plotutil.AddYErrorBars(p, mmm); err != nil { 96 | log.Fatal(err) 97 | } 98 | if err = p.Save(6*vg.Inch, 6*vg.Inch, "DijkstraAllPaths.svg"); err != nil { 99 | log.Fatal(err) 100 | } 101 | } 102 | 103 | func meanMinMax(vs []float64) (mean, lowerr, higherr float64) { 104 | low := math.Inf(1) 105 | high := math.Inf(-1) 106 | for _, v := range vs { 107 | mean += v 108 | low = math.Min(v, low) 109 | high = math.Max(v, high) 110 | } 111 | mean /= float64(len(vs)) 112 | return mean, mean - low, high - mean 113 | } 114 | 115 | func geo() { 116 | r := rand.New(rand.NewSource(time.Now().Unix())) 117 | // shorter list here because of Geometric O(n²) construction time 118 | ns := []int{16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 119 | 32768, 65536, 131072} 120 | const degree = 10 121 | const rep = 5 122 | pts := make([]plotter.XYer, len(ns)) 123 | c := 0. 124 | top := len(ns) / 2 125 | bot := len(ns) - top 126 | fmt.Println("fit top", top) 127 | for i, n := range ns { 128 | radius := math.Sqrt(degree * 2 / (float64(n) * math.Pi)) 129 | g, _, wt := graph.LabeledGeometric(n, radius, r) 130 | fmt.Println("n, m:", n, len(wt)) 131 | w := func(l graph.LI) float64 { return wt[l] } 132 | xys := make(plotter.XYs, rep) 133 | pts[i] = xys 134 | for j := 0; j < rep; j++ { 135 | start := graph.NI(r.Intn(n)) 136 | b := testing.Benchmark(func(b *testing.B) { 137 | for i := 0; i < b.N; i++ { 138 | g.Dijkstra(start, -1, w) 139 | } 140 | }) 141 | fmt.Printf("run %d: %v\n", j, b) 142 | x := float64(n) 143 | y := float64(b.NsPerOp()) * .001 144 | xys[j] = struct{ X, Y float64 }{x, y} 145 | if i >= bot { 146 | c += y / (x * math.Log(x)) // (x here is number of nodes) 147 | } 148 | } 149 | } 150 | c /= float64(top * rep) 151 | p, err := plot.New() 152 | if err != nil { 153 | log.Fatal(err) 154 | } 155 | p.Title.Text = "Dijkstra.AllPaths, Geometric Undirected Graph\nSize M ≈ 10N" 156 | p.X.Label.Text = "Graph Order, N" 157 | p.Y.Label.Text = "µsec" 158 | p.X.Scale = plot.LogScale{} 159 | p.Y.Scale = plot.LogScale{} 160 | p.X.Tick.Marker = plot.LogTicks{} 161 | p.Y.Tick.Marker = plot.LogTicks{} 162 | 163 | nln := plotter.NewFunction(func(n float64) float64 { 164 | return c * n * math.Log(n) 165 | }) 166 | nln.Color = color.RGBA{B: 127, A: 255} 167 | p.Add(nln) 168 | p.Legend.Add(fmt.Sprintf("C(n log n), C = %.2f ", c), nln) 169 | 170 | mmm, err := plotutil.NewErrorPoints(meanMinMax, pts...) 171 | if err != nil { 172 | log.Fatal(err) 173 | } 174 | if err = plotutil.AddYErrorBars(p, mmm); err != nil { 175 | log.Fatal(err) 176 | } 177 | err = p.Save(6*vg.Inch, 6*vg.Inch, "DijkstraAllPathsGeo.svg") 178 | if err != nil { 179 | log.Fatal(err) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /bench/img/Euclidean.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | // +build ignore 5 | 6 | //go:generate go run Euclidean.go 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "log" 13 | "math/rand" 14 | "os/exec" 15 | "time" 16 | 17 | "github.com/soniakeys/graph" 18 | "github.com/soniakeys/graph/dot" 19 | ) 20 | 21 | func main() { 22 | r := rand.New(rand.NewSource(time.Now().Unix())) 23 | n := 100 24 | g, pos, err := graph.Euclidean(n, n*3, 12, 100, r) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | c := exec.Command("neato", "-Tsvg", "-o", "Euclidean.svg") 29 | w, err := c.StdinPipe() 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | c.Start() 34 | dot.Write(g, w, dot.NodePos(func(n graph.NI) string { 35 | return fmt.Sprintf("%.3f,%.3f", 6*pos[n].X, 6*pos[n].Y) 36 | })) 37 | if err = w.Close(); err != nil { 38 | log.Fatal(err) 39 | } 40 | if err = c.Wait(); err != nil { 41 | log.Fatal(err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /bench/img/Geometric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | // +build ignore 5 | 6 | //go:generate go run Geometric.go 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "log" 13 | "math/rand" 14 | "os/exec" 15 | "time" 16 | 17 | "github.com/soniakeys/graph" 18 | "github.com/soniakeys/graph/dot" 19 | ) 20 | 21 | func main() { 22 | r := rand.New(rand.NewSource(time.Now().Unix())) 23 | n := 100 24 | g, pos, _ := graph.Geometric(n, .2, r) 25 | c := exec.Command("neato", "-Tsvg", "-o", "Geometric.svg") 26 | w, err := c.StdinPipe() 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | c.Start() 31 | dot.Write(g, w, dot.NodePos(func(n graph.NI) string { 32 | return fmt.Sprintf("%.3f,%.3f", 6*pos[n].X, 6*pos[n].Y) 33 | })) 34 | if err = w.Close(); err != nil { 35 | log.Fatal(err) 36 | } 37 | if err = c.Wait(); err != nil { 38 | log.Fatal(err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bench/readme.adoc: -------------------------------------------------------------------------------- 1 | = Benchmarks 2 | 3 | This directory holds benchmark results showing performance over a wide range 4 | of graph sizes, and comparing results to theoretical predictions. 5 | 6 | Compared to the performance measures of the anecdote program, the benchmarks 7 | here perform repeated tests to more reliably capture typical performance. 8 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | // Graph algorithms: Dijkstra, A*, Bellman Ford, Floyd Warshall; 5 | // Kruskal and Prim minimal spanning tree; topological sort and DAG longest 6 | // and shortest paths; Eulerian cycle and path; degeneracy and k-cores; 7 | // Bron Kerbosch clique finding; connected components; dominance; and others. 8 | // 9 | // This is a graph library of integer indexes. To use it with application 10 | // data, you associate data with integer indexes, perform searches or other 11 | // operations with the library, and then use the integer index results to refer 12 | // back to your application data. 13 | // 14 | // Thus it does not store application data, pointers to application data, 15 | // or require you to implement an interface on your application data. 16 | // The idea is to keep the library methods fast and lean. 17 | // 18 | // Representation overview 19 | // 20 | // The package defines a type for a node index (NI) which is just an integer 21 | // type. It defines types for a number of number graph representations using 22 | // NI. The fundamental graph type is AdjacencyList, which is the 23 | // common "list of lists" graph representation. It is a list as a slice 24 | // with one element for each node of the graph. Each element is a list 25 | // itself, a list of neighbor nodes, implemented as an NI slice. Methods 26 | // on an AdjacencyList generally work on any representable graph, including 27 | // directed or undirected graphs, simple graphs or multigraphs. 28 | // 29 | // The type Undirected embeds an AdjacencyList adding methods specific to 30 | // undirected graphs. Similarly the type Directed adds methods meaningful 31 | // for directed graphs. 32 | // 33 | // Similar to NI, the type LI is a "label index" which labels a 34 | // node-to-neighbor "arc" or edge. Just as an NI can index arbitrary node 35 | // data, an LI can index arbitrary arc or edge data. A number of algorithms 36 | // use a "weight" associated with an arc. This package does not represent 37 | // weighted arcs explicitly, but instead uses the LI as a more general 38 | // mechanism allowing not only weights but arbitrary data to be associated 39 | // with arcs. While AdjacencyList represents an arc with simply an NI, 40 | // the type LabeledAdjacencyList uses a type that pairs an NI with an LI. 41 | // This type is named Half, for half-arc. (A full arc would represent 42 | // both ends.) Types LabeledDirected and LabeledUndirected embed a 43 | // LabeledAdjacencyList. 44 | // 45 | // In contrast to Half, the type Edge represents both ends of an edge (but 46 | // no label.) The type LabeledEdge adds the label. The type WeightedEdgeList 47 | // bundles a list of LabeledEdges with a WeightFunc. (WeightedEdgeList has 48 | // few methods. It exists primarily to support the Kruskal algorithm.) 49 | // 50 | // FromList is a compact rooted tree (or forest) respresentation. Like 51 | // AdjacencyList and LabeledAdjacencyList, it is a list with one element for 52 | // each node of the graph. Each element contains only a single neighbor 53 | // however, its parent in the tree, the "from" node. 54 | // 55 | // Code generation 56 | // 57 | // A number of methods on AdjacencyList, Directed, and Undirected are 58 | // applicable to LabeledAdjacencyList, LabeledDirected, and LabeledUndirected 59 | // simply by ignoring the label. In these cases code generation provides 60 | // methods on both types from a single source implementation. These methods 61 | // are documented with the sentence "There are equivalent labeled and unlabeled 62 | // versions of this method." 63 | // 64 | // Terminology 65 | // 66 | // This package uses the term "node" rather than "vertex." It uses "arc" 67 | // to mean a directed edge, and uses "from" and "to" to refer to the ends 68 | // of an arc. It uses "start" and "end" to refer to endpoints of a search 69 | // or traversal. 70 | // 71 | // The usage of "to" and "from" is perhaps most strange. In common speech 72 | // they are prepositions, but throughout this package they are used as 73 | // adjectives, for example to refer to the "from node" of an arc or the 74 | // "to node". The type "FromList" is named to indicate it stores a list of 75 | // "from" values. 76 | // 77 | // A "half arc" refers to just one end of an arc, either the to or from end. 78 | // 79 | // Two arcs are "reciprocal" if they connect two distinct nodes n1 and n2, 80 | // one arc leading from n1 to n2 and the other arc leading from n2 to n1. 81 | // Undirected graphs are represented with reciprocal arcs. 82 | // 83 | // A node with an arc to itself represents a "loop." Duplicate arcs, where 84 | // a node has multiple arcs to another node, are termed "parallel arcs." 85 | // A graph with no loops or parallel arcs is "simple." A graph that allows 86 | // parallel arcs is a "multigraph" 87 | // 88 | // The "size" of a graph traditionally means the number of undirected edges. 89 | // This package uses "arc size" to mean the number of arcs in a graph. For an 90 | // undirected graph without loops, arc size is 2 * size. 91 | // 92 | // The "order" of a graph is the number of nodes. An "ordering" though means 93 | // an ordered list of nodes. 94 | // 95 | // A number of graph search algorithms use a concept of arc "weights." 96 | // The sum of arc weights along a path is a "distance." In contrast, the 97 | // number of nodes in a path, including start and end nodes, is the path's 98 | // "length." (Yes, mixing weights and lengths would be nonsense physically, 99 | // but the terms used here are just distinct terms for abstract values. 100 | // The actual meaning to an application is likely to be something else 101 | // entirely and is not relevant within this package.) 102 | // 103 | // Finally, this package documentation takes back the word "object" in some 104 | // places to refer to a Go value, especially a value of a type with methods. 105 | // 106 | // Shortest path searches 107 | // 108 | // This package implements a number of shortest path searches. Most work 109 | // with weighted graphs that are directed or undirected, and with graphs 110 | // that may have loops or parallel arcs. For weighted graphs, "shortest" 111 | // is defined as the path distance (sum of arc weights) with path length 112 | // (number of nodes) breaking ties. If multiple paths have the same minimum 113 | // distance with the same minimum length, search methods are free to return 114 | // any of them. 115 | // 116 | // Algorithm Description 117 | // Dijkstra Non-negative arc weights, single or all paths. 118 | // AStar Non-negative arc weights, heuristic guided, single path. 119 | // BellmanFord Negative arc weights allowed, no negative cycles, all paths. 120 | // DAGPath O(n) algorithm for DAGs, arc weights of any sign. 121 | // FloydWarshall all pairs distances, no negative cycles. 122 | package graph 123 | -------------------------------------------------------------------------------- /dot/dot_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | package dot_test 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/soniakeys/graph" 11 | "github.com/soniakeys/graph/dot" 12 | ) 13 | 14 | func ExampleString() { 15 | // arcs directed down: 16 | // 0 4 17 | // | /| 18 | // |/ | 19 | // 2 3 20 | g := graph.AdjacencyList{ 21 | 0: {2}, 22 | 4: {2, 3}, 23 | } 24 | s, _ := dot.String(g) 25 | fmt.Println(s) 26 | // Output: 27 | // digraph { 28 | // 0 -> 2 29 | // 4 -> {2 3} 30 | // } 31 | } 32 | 33 | func ExampleWrite_adjacencyList() { 34 | // arcs directed down: 35 | // 0 4 36 | // | /| 37 | // |/ | 38 | // 2 3 39 | g := graph.AdjacencyList{ 40 | 0: {2}, 41 | 4: {2, 3}, 42 | } 43 | dot.Write(g, os.Stdout) 44 | // Output: 45 | // digraph { 46 | // 0 -> 2 47 | // 4 -> {2 3} 48 | // } 49 | } 50 | 51 | func ExampleWrite_adjacencyListParallelArcs() { 52 | // arcs directed down: 53 | // 0 4 54 | // | /|\ 55 | // |/ \| 56 | // 2 3 57 | g := graph.AdjacencyList{ 58 | 0: {2}, 59 | 4: {2, 3, 3}, 60 | } 61 | dot.Write(g, os.Stdout) 62 | // Output: 63 | // digraph { 64 | // 0 -> 2 65 | // 4 -> {2 3} 66 | // 4 -> {3} 67 | // } 68 | } 69 | 70 | func ExampleWrite_directed() { 71 | // arcs directed down: 72 | // 0 4 73 | // | /| 74 | // |/ | 75 | // 2 3 76 | g := graph.Directed{graph.AdjacencyList{ 77 | 0: {2}, 78 | 4: {2, 3}, 79 | }} 80 | dot.Write(g, os.Stdout) 81 | // Output: 82 | // digraph { 83 | // 0 -> 2 84 | // 4 -> {2 3} 85 | // } 86 | } 87 | 88 | func ExampleWrite_directedLabeled() { 89 | // arcs directed down: 90 | // 0 4 91 | // (30)| /| 92 | // | (20) | 93 | // |/ |(10) 94 | // 2 3 95 | g := graph.LabeledDirected{graph.LabeledAdjacencyList{ 96 | 0: {{2, 30}}, 97 | 4: {{2, 20}, {3, 10}}, 98 | }} 99 | dot.Write(g, os.Stdout) 100 | // Output: 101 | // digraph { 102 | // 0 -> 2 [label = 30] 103 | // 4 -> 2 [label = 20] 104 | // 4 -> 3 [label = 10] 105 | // } 106 | } 107 | 108 | func ExampleWrite_fromList() { 109 | // 0 110 | // / \ 111 | // / 2 112 | // / \ 113 | // 1 3 114 | f := graph.FromList{Paths: []graph.PathEnd{ 115 | 0: {From: -1}, 116 | 1: {From: 0}, 117 | 2: {From: 0}, 118 | 3: {From: 2}, 119 | }} 120 | f.RecalcLeaves() 121 | dot.Write(f, os.Stdout) 122 | // Output: 123 | // digraph { 124 | // rankdir = BT 125 | // 1 -> 0 126 | // 2 -> 0 127 | // 3 -> 2 128 | // {rank = same 1 3} 129 | // } 130 | } 131 | 132 | func ExampleWrite_labeledAdjacencyList() { 133 | // arcs directed down: 134 | // 0 4 135 | // (30)| /| 136 | // | (20) | 137 | // |/ |(10) 138 | // 2 3 139 | g := graph.LabeledAdjacencyList{ 140 | 0: {{2, 30}}, 141 | 4: {{2, 20}, {3, 10}}, 142 | } 143 | dot.Write(g, os.Stdout) 144 | // Output: 145 | // digraph { 146 | // 0 -> 2 [label = 30] 147 | // 4 -> 2 [label = 20] 148 | // 4 -> 3 [label = 10] 149 | // } 150 | } 151 | 152 | func ExampleWrite_undirected() { 153 | // 0 154 | // / \ 155 | // 1---2 156 | var g graph.Undirected 157 | g.AddEdge(0, 1) 158 | g.AddEdge(0, 2) 159 | g.AddEdge(1, 2) 160 | dot.Write(g, os.Stdout) 161 | // Output: 162 | // graph { 163 | // 0 -- {1 2} 164 | // 1 -- 2 165 | // } 166 | } 167 | 168 | func ExampleWrite_undirectedLabeled() { 169 | // 0 170 | // (12) / \ (17) 171 | // 1---2 172 | // (64) 173 | var g graph.LabeledUndirected 174 | g.AddEdge(graph.Edge{0, 1}, 12) 175 | g.AddEdge(graph.Edge{0, 2}, 17) 176 | g.AddEdge(graph.Edge{1, 2}, 64) 177 | dot.Write(g, os.Stdout) 178 | // Output: 179 | // graph { 180 | // 0 -- 1 [label = 12] 181 | // 0 -- 2 [label = 17] 182 | // 1 -- 2 [label = 64] 183 | // } 184 | } 185 | 186 | func ExampleWrite_weightedEdgeList() { 187 | // (label 0, wt 1.6) 188 | // 0----------------------2 189 | // (label 1 | / 190 | // wt .33) | ------------------/ 191 | // | / (label 2, wt 1.7) 192 | // |/ 193 | // 1 194 | weights := []float64{ 195 | 0: 1.6, 196 | 1: .33, 197 | 2: 1.7, 198 | } 199 | g := graph.WeightedEdgeList{ 200 | WeightFunc: func(l graph.LI) float64 { return weights[int(l)] }, 201 | Order: 3, 202 | Edges: []graph.LabeledEdge{ 203 | {graph.Edge{0, 1}, 1}, 204 | {graph.Edge{1, 0}, 1}, 205 | 206 | {graph.Edge{0, 2}, 0}, 207 | {graph.Edge{2, 0}, 0}, 208 | 209 | {graph.Edge{1, 2}, 2}, 210 | {graph.Edge{2, 1}, 2}, 211 | }, 212 | } 213 | dot.Write(g, os.Stdout) 214 | // Output: 215 | // graph { 216 | // 0 -- 1 [label = "0.33"] 217 | // 0 -- 2 [label = "1.6"] 218 | // 1 -- 2 [label = "1.7"] 219 | // } 220 | } 221 | -------------------------------------------------------------------------------- /dot/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | package dot 5 | 6 | import ( 7 | "strconv" 8 | 9 | "github.com/soniakeys/graph" 10 | ) 11 | 12 | // AttrVal represents the dot format concept of an attribute-value pair. 13 | type AttrVal struct { 14 | Attr string 15 | Val string 16 | } 17 | 18 | // Config holds options that control the dot output. 19 | // 20 | // See Overview/Scheme for an overview of how this works. Generally you will 21 | // not set members of a Config struct directly. There is an option function 22 | // for each member. To set a member, pass the option function as an optional 23 | // argument to a Write or String function. 24 | type Config struct { 25 | Directed bool 26 | EdgeLabel func(graph.LI) string 27 | EdgeAttr func(graph.LI) []AttrVal 28 | GraphAttr []AttrVal 29 | Indent string 30 | Isolated bool 31 | NodeID func(graph.NI) string 32 | NodePos func(graph.NI) string 33 | UndirectArcs bool 34 | } 35 | 36 | // Defaults holds a package default Config struct. 37 | // 38 | // Defaults is copied as the first configuration step. See Overview/Scheme. 39 | var Defaults = Config{ 40 | Directed: true, 41 | EdgeLabel: func(l graph.LI) string { return strconv.Itoa(int(l)) }, 42 | Indent: " ", 43 | NodeID: func(n graph.NI) string { return strconv.Itoa(int(n)) }, 44 | } 45 | 46 | // Options are passed variadic arguments to a function like Write or String. 47 | type Option func(*Config) 48 | 49 | // Directed specifies whether to write a dot format directected or undirected 50 | // graph. 51 | // 52 | // The default, Directed(true), specifies a dot format directected graph, 53 | // or "digraph." In this case the Write or String function outputs each arc 54 | // of the graph as a dot format directed edge. 55 | // 56 | // Directed(false) specifies a dot format undirected graph or simply a "graph." 57 | // In this case the Write or String function requires that all arcs between 58 | // distinct nodes occur in reciprocal pairs. For each pair the function 59 | // outputs a single edge in dot format. 60 | func Directed(d bool) Option { 61 | return func(c *Config) { c.Directed = d } 62 | } 63 | 64 | // EdgeLabel specifies a function to generate edge label strings for the 65 | // dot format given the arc label integers of graph package. 66 | // 67 | // The default function is simply strconv.Itoa of the graph package arc label. 68 | func EdgeLabel(f func(graph.LI) string) Option { 69 | return func(c *Config) { c.EdgeLabel = f } 70 | } 71 | 72 | // EdgeAttr specifies a function to generate a list of edge attributes for 73 | // given the arc label integers of graph package. 74 | func EdgeAttr(f func(graph.LI) []AttrVal) Option { 75 | return func(c *Config) { c.EdgeAttr = f } 76 | } 77 | 78 | // GraphAttr adds a dot format graph attribute. 79 | // 80 | // Graph attributes are held in a slice, and so are ordered. This function 81 | // updates the value of the last matching attribute if it exists, or adds a 82 | // new attribute to the end of the list. 83 | func GraphAttr(attr, val string) Option { 84 | return func(c *Config) { 85 | for i := len(c.GraphAttr) - 1; i >= 0; i-- { 86 | if c.GraphAttr[i].Attr == attr { 87 | c.GraphAttr[i].Val = val 88 | return 89 | } 90 | } 91 | c.GraphAttr = append(c.GraphAttr, AttrVal{attr, val}) 92 | } 93 | } 94 | 95 | // Indent specifies an indent string for the body of the dot format. 96 | // 97 | // The default is two spaces. 98 | func Indent(i string) Option { 99 | return func(c *Config) { c.Indent = i } 100 | } 101 | 102 | // Isolated specifies whether to include isolated nodes. 103 | // 104 | // An isolated node has no arcs in or out. By default, isolated = false, 105 | // isolated nodes are not included in the dot output. 106 | // 107 | // Isolated(true) will include isolated nodes. 108 | func Isolated(i bool) Option { 109 | return func(c *Config) { c.Isolated = i } 110 | } 111 | 112 | // NodeID specifies a function to generate node ID strings for the 113 | // dot format given the node integers of graph package. 114 | // 115 | // The default function is simply strconv.Itoa of the graph package node 116 | // integer. 117 | func NodeID(f func(graph.NI) string) Option { 118 | return func(c *Config) { c.NodeID = f } 119 | } 120 | 121 | // NodePos specifies a function to format coordinate strings. 122 | // 123 | // The resulting dot file should be rendered with Graphviz programs 124 | // neato or fdp. 125 | func NodePos(f func(graph.NI) string) Option { 126 | return func(c *Config) { c.NodePos = f } 127 | } 128 | 129 | // UndirectArcs, for the WeightedEdgeList graph type, specifies to write 130 | // each element of the edge list as a dot file undirected edge. 131 | // 132 | // Note that while Directed(false) requires arcs to occur in reciprocal pairs, 133 | // UndirectArcs(true) does not, and does not collapse reciprocal arc pairs to 134 | // single dot format edges. 135 | // 136 | // See WriteWeightedEdgeList for more detail. 137 | func UndirectArcs(u bool) Option { 138 | return func(c *Config) { c.UndirectArcs = u } 139 | } 140 | -------------------------------------------------------------------------------- /dot/options_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | package dot_test 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/soniakeys/graph" 11 | "github.com/soniakeys/graph/dot" 12 | ) 13 | 14 | func ExampleDirected() { 15 | // arcs directed down: 16 | // 0 2 17 | // | /| 18 | // |/ | 19 | // 3 4 20 | g := graph.AdjacencyList{ 21 | 0: {3}, 22 | 2: {3, 4}, 23 | 4: {}, 24 | } 25 | // default for AdjacencyList is directed 26 | dot.Write(g, os.Stdout) 27 | fmt.Fprintln(os.Stdout) 28 | fmt.Fprintln(os.Stdout) 29 | 30 | // Directed(false) generates error witout reciprocal arcs 31 | err := dot.Write(g, os.Stdout, dot.Directed(false)) 32 | fmt.Fprintln(os.Stdout, "Error:", err) 33 | fmt.Fprintln(os.Stdout) 34 | 35 | // undirected 36 | u := graph.Directed{g}.Undirected() 37 | dot.Write(u.AdjacencyList, os.Stdout, dot.Directed(false)) 38 | 39 | // Output: 40 | // digraph { 41 | // 0 -> 3 42 | // 2 -> {3 4} 43 | // } 44 | // 45 | // Error: directed graph 46 | // 47 | // graph { 48 | // 0 -- 3 49 | // 2 -- {3 4} 50 | // } 51 | } 52 | 53 | func ExampleEdgeLabel() { 54 | // arcs directed down: 55 | // 0 4 56 | // (.33)| /| 57 | // | (1.7) | 58 | // |/ |(2e117) 59 | // 2 3 60 | weights := map[int]float64{ 61 | 30: .33, 62 | 20: 1.7, 63 | 10: 2e117, 64 | } 65 | lf := func(l graph.LI) string { 66 | return fmt.Sprintf(`"%g"`, weights[int(l)]) 67 | } 68 | g := graph.LabeledAdjacencyList{ 69 | 0: {{2, 30}}, 70 | 4: {{2, 20}, {3, 10}}, 71 | } 72 | dot.Write(g, os.Stdout, dot.EdgeLabel(lf)) 73 | // Output: 74 | // digraph { 75 | // 0 -> 2 [label = "0.33"] 76 | // 4 -> 2 [label = "1.7"] 77 | // 4 -> 3 [label = "2e+117"] 78 | // } 79 | } 80 | 81 | func ExampleGraphAttr() { 82 | // arcs directed right: 83 | // 0---2 84 | // \ / \ 85 | // 1---3 86 | g := graph.AdjacencyList{ 87 | 0: {1, 2}, 88 | 1: {2, 3}, 89 | 2: {3}, 90 | 3: {}, 91 | } 92 | dot.Write(g, os.Stdout, dot.GraphAttr("rankdir", "LR")) 93 | // Output: 94 | // digraph { 95 | // rankdir = LR 96 | // 0 -> {1 2} 97 | // 1 -> {2 3} 98 | // 2 -> 3 99 | // } 100 | } 101 | 102 | func ExampleIndent() { 103 | // arcs directed down: 104 | // 0 4 105 | // | /| 106 | // |/ | 107 | // 2 3 108 | g := graph.AdjacencyList{ 109 | 0: {2}, 110 | 4: {2, 3}, 111 | } 112 | dot.Write(g, os.Stdout, dot.Indent("")) // (default indent is 2 spaces) 113 | // Output: 114 | // digraph { 115 | // 0 -> 2 116 | // 4 -> {2 3} 117 | // } 118 | } 119 | 120 | func ExampleIsolated() { 121 | // 0 1-->2 122 | g := graph.AdjacencyList{ 123 | 1: {2}, 124 | 2: {}, 125 | } 126 | dot.Write(g, os.Stdout, dot.Isolated(true)) 127 | // Output: 128 | // digraph { 129 | // 0 130 | // 1 -> 2 131 | // } 132 | } 133 | 134 | func ExampleNodePos() { 135 | // 0--1 136 | // |\ 137 | // | \ 138 | // 2 3 139 | f := graph.AdjacencyList{ 140 | 0: {1, 2, 3}, 141 | 3: {}, 142 | } 143 | pos := []struct{ x, y float64 }{ 144 | {0, 0}, 145 | {0, 1}, 146 | {1, 0}, 147 | {1, 1}, 148 | } 149 | dot.Write(f, os.Stdout, dot.NodePos(func(n graph.NI) string { 150 | return fmt.Sprintf("%.0f,%.0f", 4*pos[n].x, 4*pos[n].y) 151 | })) 152 | // Output: 153 | // digraph { 154 | // node [shape=point] 155 | // 0 [pos="0,0!"] 156 | // 1 [pos="0,4!"] 157 | // 2 [pos="4,0!"] 158 | // 3 [pos="4,4!"] 159 | // 0 -> {1 2 3} 160 | // } 161 | } 162 | 163 | func ExampleNodeID() { 164 | // arcs directed down: 165 | // A D 166 | // | /| 167 | // |/ | 168 | // B C 169 | labels := []string{ 170 | 0: "A", 171 | 4: "D", 172 | 2: "B", 173 | 3: "C", 174 | } 175 | lf := func(n graph.NI) string { return labels[n] } 176 | g := graph.AdjacencyList{ 177 | 0: {2}, 178 | 4: {2, 3}, 179 | } 180 | dot.Write(g, os.Stdout, dot.NodeID(lf)) 181 | // Output: 182 | // digraph { 183 | // A -> B 184 | // D -> {B C} 185 | // } 186 | } 187 | 188 | func ExampleNodeID_construction() { 189 | // arcs directed down: 190 | // A D 191 | // | /| 192 | // |/ | 193 | // B C 194 | var g graph.AdjacencyList 195 | 196 | // example graph construction mechanism 197 | labels := []string{} 198 | nodes := map[string]graph.NI{} 199 | node := func(l string) graph.NI { 200 | if n, ok := nodes[l]; ok { 201 | return n 202 | } 203 | n := graph.NI(len(labels)) 204 | labels = append(labels, l) 205 | g = append(g, nil) 206 | nodes[l] = n 207 | return n 208 | } 209 | addArc := func(fr, to string) { 210 | f := node(fr) 211 | g[f] = append(g[f], node(to)) 212 | } 213 | 214 | // construct graph 215 | addArc("A", "B") 216 | addArc("D", "B") 217 | addArc("D", "C") 218 | 219 | // generate dot 220 | lf := func(n graph.NI) string { return labels[n] } 221 | dot.Write(g, os.Stdout, dot.NodeID(lf)) 222 | 223 | // Output: 224 | // digraph { 225 | // A -> B 226 | // D -> {B C} 227 | // } 228 | } 229 | 230 | func ExampleUndirectArcs() { 231 | // (label 0, wt 1.6) 232 | // 0----------------------2 233 | // (label 1 | / 234 | // wt .33) | ------------------/ 235 | // | / (label 2, wt 1.7) 236 | // |/ 237 | // 1 238 | weights := []float64{ 239 | 0: 1.6, 240 | 1: .33, 241 | 2: 1.7, 242 | } 243 | g := graph.WeightedEdgeList{ 244 | WeightFunc: func(l graph.LI) float64 { return weights[int(l)] }, 245 | Order: 3, 246 | Edges: []graph.LabeledEdge{ 247 | {graph.Edge{0, 1}, 1}, 248 | {graph.Edge{0, 2}, 0}, 249 | {graph.Edge{1, 2}, 2}, 250 | }, 251 | } 252 | dot.Write(g, os.Stdout, dot.UndirectArcs(true)) 253 | // Output: 254 | // graph { 255 | // 0 -- 1 [label = "0.33"] 256 | // 0 -- 2 [label = "1.6"] 257 | // 1 -- 2 [label = "1.7"] 258 | // } 259 | } 260 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module "github.com/soniakeys/graph" 2 | 3 | require "github.com/soniakeys/bits" v1.0.0 4 | -------------------------------------------------------------------------------- /graph_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | package graph_test 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/soniakeys/graph" 10 | ) 11 | 12 | // A directed graph with negative arc weights. 13 | // Arc weights are encoded simply as label numbers. 14 | func ExampleDistanceMatrix_FloydWarshall_labeledDirected() { 15 | // (1) (-1) (4) 16 | // 0---->1---->3---->2 17 | // ^ | | 18 | // |(2) |(3) |(-2) 19 | // | v | 20 | // ------4<----- 21 | g := graph.LabeledDirected{graph.LabeledAdjacencyList{ 22 | 0: {{To: 1, Label: 1}}, 23 | 1: {{To: 3, Label: -1}}, 24 | 2: {{To: 4, Label: -2}}, 25 | 3: {{To: 2, Label: 4}, {To: 4, Label: 3}}, 26 | 4: {{To: 1, Label: 2}}, 27 | }} 28 | d := g.DistanceMatrix(func(l graph.LI) float64 { return float64(l) }) 29 | d.FloydWarshall() 30 | for _, di := range d { 31 | fmt.Printf("%4.0f\n", di) 32 | } 33 | // Output: 34 | // [ 0 1 4 0 2] 35 | // [+Inf 0 3 -1 1] 36 | // [+Inf 0 0 -1 -2] 37 | // [+Inf 4 4 0 2] 38 | // [+Inf 2 5 1 0] 39 | } 40 | 41 | func ExampleDistanceMatrix_FloydWarshall_weightedEdgeList() { 42 | // (1) (4) 43 | // 0 1-----3-----2 44 | // | | | 45 | // |(2) |(3) |(2) 46 | // | | | 47 | // ------4------ 48 | l := graph.WeightedEdgeList{ 49 | Order: 5, 50 | WeightFunc: func(l graph.LI) float64 { return float64(l) }, 51 | Edges: []graph.LabeledEdge{ 52 | {graph.Edge{1, 3}, 1}, 53 | {graph.Edge{1, 4}, 2}, 54 | {graph.Edge{3, 2}, 4}, 55 | {graph.Edge{3, 4}, 3}, 56 | {graph.Edge{4, 2}, 2}, 57 | }, 58 | } 59 | d := l.DistanceMatrix() 60 | d.FloydWarshall() 61 | for _, di := range d { 62 | fmt.Printf("%4.0f\n", di) 63 | } 64 | // Output: 65 | // [ 0 +Inf +Inf +Inf +Inf] 66 | // [+Inf 0 4 1 2] 67 | // [+Inf 4 0 4 2] 68 | // [+Inf 1 4 0 3] 69 | // [+Inf 2 2 3 0] 70 | } 71 | 72 | func ExampleDistanceMatrix_FloydWarshallPaths() { 73 | // (1) (-1) (4) 74 | // 0---->1---->3---->2 75 | // ^ | | 76 | // |(2) |(3) |(-2) 77 | // | v | 78 | // ------4<----- 79 | g := graph.LabeledDirected{graph.LabeledAdjacencyList{ 80 | 0: {{To: 1, Label: 1}}, 81 | 1: {{To: 3, Label: -1}}, 82 | 2: {{To: 4, Label: -2}}, 83 | 3: {{To: 2, Label: 4}, {To: 4, Label: 3}}, 84 | 4: {{To: 1, Label: 2}}, 85 | }} 86 | d := g.DistanceMatrix(func(l graph.LI) float64 { return float64(l) }) 87 | p := d.FloydWarshallPaths() 88 | fmt.Println("Distances:") 89 | for _, di := range d { 90 | fmt.Printf("%4.0f\n", di) 91 | } 92 | fmt.Println("Paths:") 93 | b := make([]graph.NI, len(d)) 94 | for i := range p { 95 | for j := range p { 96 | fmt.Printf("%d->%d: %d\n", 97 | i, j, p.Path(graph.NI(i), graph.NI(j), b)) 98 | } 99 | } 100 | // Output: 101 | // Distances: 102 | // [ 0 1 4 0 2] 103 | // [+Inf 0 3 -1 1] 104 | // [+Inf 0 0 -1 -2] 105 | // [+Inf 4 4 0 2] 106 | // [+Inf 2 5 1 0] 107 | // Paths: 108 | // 0->0: [0] 109 | // 0->1: [0 1] 110 | // 0->2: [0 1 3 2] 111 | // 0->3: [0 1 3] 112 | // 0->4: [0 1 3 2 4] 113 | // 1->0: [] 114 | // 1->1: [1] 115 | // 1->2: [1 3 2] 116 | // 1->3: [1 3] 117 | // 1->4: [1 3 2 4] 118 | // 2->0: [] 119 | // 2->1: [2 4 1] 120 | // 2->2: [2] 121 | // 2->3: [2 4 1 3] 122 | // 2->4: [2 4] 123 | // 3->0: [] 124 | // 3->1: [3 2 4 1] 125 | // 3->2: [3 2] 126 | // 3->3: [3] 127 | // 3->4: [3 2 4] 128 | // 4->0: [] 129 | // 4->1: [4 1] 130 | // 4->2: [4 1 3 2] 131 | // 4->3: [4 1 3] 132 | // 4->4: [4] 133 | } 134 | 135 | func ExampleDistanceMatrix_FloydWarshallFromLists() { 136 | // (1) (-1) (4) 137 | // 0---->1---->3---->2 138 | // ^ | | 139 | // |(2) |(3) |(-2) 140 | // | v | 141 | // ------4<----- 142 | g := graph.LabeledDirected{graph.LabeledAdjacencyList{ 143 | 0: {{To: 1, Label: 1}}, 144 | 1: {{To: 3, Label: -1}}, 145 | 2: {{To: 4, Label: -2}}, 146 | 3: {{To: 2, Label: 4}, {To: 4, Label: 3}}, 147 | 4: {{To: 1, Label: 2}}, 148 | }} 149 | d := g.DistanceMatrix(func(l graph.LI) float64 { return float64(l) }) 150 | l := d.FloydWarshallFromLists() 151 | fmt.Println("Distances:") 152 | for _, di := range d { 153 | fmt.Printf("%4.0f\n", di) 154 | } 155 | fmt.Println("Paths:") 156 | s := make([]graph.NI, len(d)) 157 | for i, li := range l { 158 | for j := range l { 159 | p := li.PathTo(graph.NI(j), s) 160 | // Note: Test that returned path actually starts at i. 161 | // If not, there is no path. 162 | if p[0] != graph.NI(i) { 163 | p = nil 164 | } 165 | fmt.Printf("%d->%d: %d\n", i, j, p) 166 | } 167 | } 168 | // Output: 169 | // Distances: 170 | // [ 0 1 4 0 2] 171 | // [+Inf 0 3 -1 1] 172 | // [+Inf 0 0 -1 -2] 173 | // [+Inf 4 4 0 2] 174 | // [+Inf 2 5 1 0] 175 | // Paths: 176 | // 0->0: [0] 177 | // 0->1: [0 1] 178 | // 0->2: [0 1 3 2] 179 | // 0->3: [0 1 3] 180 | // 0->4: [0 1 3 2 4] 181 | // 1->0: [] 182 | // 1->1: [1] 183 | // 1->2: [1 3 2] 184 | // 1->3: [1 3] 185 | // 1->4: [1 3 2 4] 186 | // 2->0: [] 187 | // 2->1: [2 4 1] 188 | // 2->2: [2] 189 | // 2->3: [2 4 1 3] 190 | // 2->4: [2 4] 191 | // 3->0: [] 192 | // 3->1: [3 2 4 1] 193 | // 3->2: [3 2] 194 | // 3->3: [3] 195 | // 3->4: [3 2 4] 196 | // 4->0: [] 197 | // 4->1: [4 1] 198 | // 4->2: [4 1 3 2] 199 | // 4->3: [4 1 3] 200 | // 4->4: [4] 201 | } 202 | 203 | func ExampleOrderMap() { 204 | m := map[int]string{3: "three", 1: "one", 4: "four"} 205 | fmt.Println(graph.OrderMap(m)) 206 | // Output: 207 | // map[1:one 3:three 4:four] 208 | } 209 | 210 | func ExampleUndirectedSubgraph_AddEdge() { 211 | // supergraph: 212 | // 0 213 | // / \\ 214 | // 1 2 215 | var g graph.Undirected 216 | g.AddEdge(0, 1) 217 | g.AddEdge(0, 2) 218 | g.AddEdge(0, 2) 219 | s := g.InduceList(nil) // construct empty subgraph 220 | fmt.Println(s.AddEdge(0, 2)) // okay 221 | fmt.Println(s.AddEdge(0, 2)) // adding one parallel arc okay 222 | fmt.Println(s.AddEdge(0, 2)) // adding another not okay 223 | fmt.Println(s.AddEdge(1, 2)) // arc not in supergraph at all 224 | fmt.Println("Subgraph:") 225 | for fr, to := range s.Undirected.AdjacencyList { 226 | fmt.Printf("%d: %d\n", fr, to) 227 | } 228 | fmt.Println("Mappings:") 229 | // mapping from subgraph NIs to supergraph NIs 230 | fmt.Println(s.SuperNI) 231 | // mapping from supergraph NIs to subgraph NIs 232 | fmt.Println(graph.OrderMap(s.SubNI)) 233 | // Output: 234 | // 235 | // 236 | // edge not available in supergraph 237 | // edge not available in supergraph 238 | // Subgraph: 239 | // 0: [1 1] 240 | // 1: [0 0] 241 | // Mappings: 242 | // [0 2] 243 | // map[0:0 2:1] 244 | } 245 | 246 | func ExampleUndirectedSubgraph_AddEdge_panic() { 247 | // supergraph: 248 | // 0 249 | // / \\ 250 | // 1 2 251 | var g graph.Undirected 252 | g.AddEdge(0, 1) 253 | g.AddEdge(0, 2) 254 | g.AddEdge(0, 2) 255 | s := g.InduceList(nil) 256 | func() { 257 | defer func() { fmt.Println(recover()) }() 258 | s.AddEdge(0, -1) 259 | }() 260 | func() { 261 | defer func() { fmt.Println(recover()) }() 262 | s.AddEdge(3, 0) 263 | }() 264 | // Output: 265 | // AddEdge: NI -1 not in supergraph 266 | // AddEdge: NI 3 not in supergraph 267 | } 268 | 269 | func ExampleLabeledUndirectedSubgraph_AddEdge() { 270 | // supergraph: 271 | // 0 272 | // / \\ 273 | // x/ y\\z 274 | // 1 2 275 | var g graph.LabeledUndirected 276 | g.AddEdge(graph.Edge{0, 1}, 'x') 277 | g.AddEdge(graph.Edge{0, 2}, 'y') 278 | g.AddEdge(graph.Edge{0, 2}, 'z') 279 | s := g.InduceList(nil) // construct empty subgraph 280 | fmt.Println(s.AddEdge(graph.Edge{0, 1}, 'x')) // okay 281 | fmt.Println(s.AddEdge(graph.Edge{1, 2}, 'x')) // no edge 282 | fmt.Println(s.AddEdge(graph.Edge{0, 2}, 'w')) // no label match 283 | fmt.Println("Subgraph:") 284 | for fr, to := range s.LabeledUndirected.LabeledAdjacencyList { 285 | fmt.Print(fr, ":") 286 | for _, h := range to { 287 | fmt.Printf(" {%d, %c}", h.To, h.Label) 288 | } 289 | fmt.Println() 290 | } 291 | fmt.Println("Mappings:") 292 | // mapping from subgraph NIs to supergraph NIs 293 | fmt.Println(s.SuperNI) 294 | // mapping from supergraph NIs to subgraph NIs 295 | fmt.Println(graph.OrderMap(s.SubNI)) 296 | // Output: 297 | // 298 | // edge not available in supergraph 299 | // edge not available in supergraph 300 | // Subgraph: 301 | // 0: {1, x} 302 | // 1: {0, x} 303 | // Mappings: 304 | // [0 1] 305 | // map[0:0 1:1] 306 | } 307 | 308 | func ExampleLabeledUndirectedSubgraph_AddEdge_panic() { 309 | // supergraph: 310 | // 0 311 | // / \\ 312 | // x/ y\\z 313 | // 1 2 314 | var g graph.LabeledUndirected 315 | g.AddEdge(graph.Edge{0, 1}, 'x') 316 | g.AddEdge(graph.Edge{0, 2}, 'y') 317 | g.AddEdge(graph.Edge{0, 2}, 'z') 318 | s := g.InduceList(nil) 319 | func() { 320 | defer func() { fmt.Println(recover()) }() 321 | s.AddEdge(graph.Edge{0, -1}, -1) 322 | }() 323 | func() { 324 | defer func() { fmt.Println(recover()) }() 325 | s.AddEdge(graph.Edge{3, 0}, -1) 326 | }() 327 | // Output: 328 | // AddEdge: NI -1 not in supergraph 329 | // AddEdge: NI 3 not in supergraph 330 | } 331 | -------------------------------------------------------------------------------- /hacking.adoc: -------------------------------------------------------------------------------- 1 | = Hacking 2 | 3 | == Get, install 4 | Basic use of the package is just go get, or git clone; go install. There are 5 | no dependencies outside the standard library. 6 | 7 | == Build 8 | CI is currently on travis-ci.org. 9 | 10 | The build runs go vet with a few exceptions for things I'm not a big fan of. 11 | 12 | https://github.com/client9/misspell has been valuable. 13 | 14 | Also I wrote https://github.com/soniakeys/vetc to validate that each source 15 | file has copyright/license statement. 16 | 17 | Then, it’s not in the ci script, but I wrote https://github.com/soniakeys/rcv 18 | to put coverage stats in the readme. Maybe it could be commit hook or 19 | something but for now I’ll try just running it manually now and then. 20 | 21 | Go fmt is not in the ci script, but I have at least one editor set up to run 22 | it on save, so code should stay formatted pretty well. 23 | 24 | == Examples with random output 25 | The math/rand generators with constant seeds used to give consistent numbers 26 | across Go versions and so some examples relied on this. Sometime after Go 1.9 27 | though the numbers changed. The technique for now is to go ahead and write 28 | those examples, get them working, then change the `// Output:` line to 29 | `// Random output:`. This keeps them showing in go doc but keeps them from 30 | being run by go test. This works for now. It might be revisited at some 31 | point. 32 | 33 | == Plans 34 | The primary to-do list is the issue tracker on Github. 35 | 36 | == Direction, focus, features 37 | The project started with no real goal or purpose, just as a place for some code 38 | that might be useful. Here are some elements that characterize the direction. 39 | 40 | * The focus has been on algorithms on adjacency lists. That is, adjacency list 41 | is the fundamental representation for most implemented algorithms. There are 42 | many other interesting representations, many reasons to use them, but 43 | adjacency list is common in literature and practice. It has been useful to 44 | focus on this data representation, at first anyway. 45 | 46 | * The focus has been on single threaded algorithms. Again, there is much new 47 | and interesting work being done with concurrent, parallel, and distributed 48 | graph algorithms, and Go might be an excellent language to implement some of 49 | these algorithms. But as a preliminary step, more traditional 50 | single-threaded algorithms are implemented. 51 | 52 | * The focus has been on static finite graphs. Again there is much interesting 53 | work in online algorithms, dynamic graphs, and infinite graphs, but these 54 | are not generally considered here. 55 | 56 | * Algorithms selected for implementation are generally ones commonly appearing 57 | in beginning graph theory discussions and in general purpose graph libraries 58 | in other programming languages. With these as drivers, there's a big risk 59 | developing a library of curiosities and academic exercises rather than a 60 | library of practical utility. But well, it's a start. The hope is that 61 | there are some practical drivers behind graph theory and behind other graph 62 | libraries. 63 | 64 | * There is active current research going on in graph algorithm development. 65 | One goal for this library is to implement newer and faster algorithms. 66 | In some cases where it seems not too much work, older/classic/traditional 67 | algorithms may be implemented for comparison. These generally go in the 68 | alt subdirectory. 69 | 70 | == General principles 71 | * The API is rather low level. 72 | 73 | * Slices instead of maps. Maps are pretty efficient, and the property of 74 | unique keys can be useful, But slices are still faster and more efficient, 75 | and the unique key property is not always needed or wanted. The Adjacency 76 | list implementation of this library is all done in slices. Slices are used 77 | in algorithms where possible, in preference to maps. Maps are still used in 78 | some cases where uniqueness is needed. 79 | 80 | * Interfaces not generally used. Algorithms are implemented directly on 81 | concrete data types and not on interfaces describing the capabilities of 82 | the data types. The abstraction of interfaces is a nice match to graph 83 | theory and the convenience of running graph algorithms on any type that 84 | implements an interface is appealing, but the costs seem too high to me. 85 | Slices are rich with capababilites that get hidden behind interfaces and 86 | direct slice manipulation is always faster than going through interfaces. 87 | An impedance for programs using the library is that they will generally 88 | have to implement a mapping from slice indexes to their application data, 89 | often including for example, some other form of node ID. This seems fair 90 | to push this burden outside the graph library; the library cannot know 91 | the needs of this mapping. 92 | 93 | * Bitsets are widely used, particularly to store one bit of information per 94 | node of a graph. I used math/big at first but then moved to a dense bitset 95 | of my own. Yes, I considered other third-party bitsets but had my own 96 | feature set I wanted. A slice of bools is another alternative. Bools will 97 | be faster in almost all cases but the bitset will use less memory. I'm 98 | chosing size over speed for now. 99 | 100 | * Code generation is used to provide methods that work on both labeled and 101 | unlabeled graphs. Code is written to labeled types, then transformations 102 | generate the unlabled equivalents. 103 | 104 | * Methods are named for what they return rather than what they do, where 105 | reasonable anyway. 106 | 107 | * Consistency in method signature and behavior across corresponding methods, 108 | for example directed/undirected, labeled/unlabeled, again, as long as it's 109 | reasonable. 110 | 111 | * Sometimes in tension with the consistency principle, methods are lazy about 112 | datatypes of parameters and return values. Sometimes a vale might have 113 | different reasonable representations, a set might be a bitset, map, slice 114 | of bools, or slice of set members for example. Methods will take and return 115 | whatever is convenient for them and not convert the form just for consistency 116 | or to try to guess what a caller would prefer. 117 | 118 | * Methods return multiple results for whatever the algorithm produces that 119 | might be of interest. Sometimes an algorithm will have a primary result but 120 | then some secondary values that also might be of interest. If they are 121 | already computed as a byproduct of the algorithm, or can be computed at 122 | negligible cost, return them. 123 | 124 | * Sometimes in conflict with the multiple result principle, methods will not 125 | speculatively compute secondary results if there is any significant cost 126 | and if the secondary result can be just as easily computed later. 127 | 128 | == Code Maintenance 129 | There are tons of cut and paste variants. There's the basic AdjacencyList, 130 | then Directed and Undirected variants, then Labeled variants of each of those. 131 | Code gen helps avoid some cut and paste but there's a bunch that doesn't 132 | code gen very well and so is duplicated with cut and paste. In particular 133 | the testable examples in the _test files don't cg well and so are pretty much 134 | all duplicated by hand. If you change code, think about where there should 135 | be variants and go look to see if the variants need similar changes. 136 | -------------------------------------------------------------------------------- /io/ex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | package io_test 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "os" 10 | 11 | "github.com/soniakeys/graph" 12 | "github.com/soniakeys/graph/io" 13 | ) 14 | 15 | func ExampleArcDir() { 16 | // 0 17 | // / \\ 18 | // 1 2--\ 19 | // \-/ 20 | var g graph.Undirected 21 | g.AddEdge(0, 1) 22 | g.AddEdge(0, 2) 23 | g.AddEdge(0, 2) 24 | g.AddEdge(2, 2) 25 | 26 | fmt.Println("Default WriteArcs All: writes all arcs:") 27 | t := io.Text{} 28 | n, err := t.WriteAdjacencyList(g.AdjacencyList, os.Stdout) 29 | fmt.Printf("bytes: %d, err: %v\n\n", n, err) 30 | 31 | fmt.Println("Upper triangle:") 32 | t.WriteArcs = io.Upper 33 | n, err = t.WriteAdjacencyList(g.AdjacencyList, os.Stdout) 34 | fmt.Printf("bytes: %d, err: %v\n\n", n, err) 35 | 36 | fmt.Println("Lower triangle:") 37 | t.WriteArcs = io.Lower 38 | n, err = t.WriteAdjacencyList(g.AdjacencyList, os.Stdout) 39 | fmt.Printf("bytes: %d, err: %v\n", n, err) 40 | // Output: 41 | // Default WriteArcs All: writes all arcs: 42 | // 0: 1 2 2 43 | // 1: 0 44 | // 2: 0 0 2 45 | // bytes: 23, err: 46 | // 47 | // Upper triangle: 48 | // 0: 1 2 2 49 | // 2: 2 50 | //bytes: 14, err: 51 | // 52 | // Lower triangle: 53 | // 1: 0 54 | // 2: 0 0 2 55 | // bytes: 14, err: 56 | } 57 | 58 | func ExampleFormat() { 59 | // 0 60 | // / \\ 61 | // 2-->3 62 | g := graph.AdjacencyList{ 63 | 0: {2, 3, 3}, 64 | 2: {3}, 65 | 3: {}, 66 | } 67 | fmt.Println("Default Format Sparse:") 68 | var t io.Text 69 | n, err := t.WriteAdjacencyList(g, os.Stdout) 70 | fmt.Printf("bytes: %d, err: %v\n\n", n, err) 71 | 72 | fmt.Println("Format Dense:") 73 | t.Format = io.Dense 74 | n, err = t.WriteAdjacencyList(g, os.Stdout) 75 | fmt.Printf("bytes: %d, err: %v\n\n", n, err) 76 | 77 | fmt.Println("Format Arcs:") 78 | t.Format = io.Arcs 79 | n, err = t.WriteAdjacencyList(g, os.Stdout) 80 | fmt.Printf("bytes: %d, err: %v\n\n", n, err) 81 | // Output: 82 | // Default Format Sparse: 83 | // 0: 2 3 3 84 | // 2: 3 85 | // 3: 86 | // bytes: 17, err: 87 | // 88 | // Format Dense: 89 | // 2 3 3 90 | // 91 | // 3 92 | // 93 | // bytes: 10, err: 94 | // 95 | // Format Arcs: 96 | // 0 2 97 | // 0 3 98 | // 0 3 99 | // 2 3 100 | // bytes: 16, err: 101 | } 102 | 103 | func ExampleNewText() { 104 | r := bytes.NewBufferString(` 105 | 0: 1 // node 0 comment 106 | `) 107 | g, _, _, err := io.NewText().ReadAdjacencyList(r) 108 | for fr, to := range g { 109 | if len(to) > 0 { 110 | fmt.Println(fr, to) 111 | } 112 | } 113 | fmt.Println("err:", err) 114 | // Output: 115 | // 0 [1] 116 | // err: 117 | } 118 | 119 | // Example with zero value Text. For options see examples under Text. 120 | func ExampleText_ReadAdjacencyList() { 121 | // set r to output of example Text.WriteAdjacencyList: 122 | r := bytes.NewBufferString(` 123 | 0: 2 3 3 124 | 2: 3 125 | 3: 126 | `) 127 | g, _, _, err := io.Text{}.ReadAdjacencyList(r) 128 | // result g should be: 129 | // 0 130 | // / \\ 131 | // 2-->3 132 | for fr, to := range g { 133 | if len(to) > 0 { 134 | fmt.Println(fr, to) 135 | } 136 | } 137 | fmt.Println("err:", err) 138 | // Output: 139 | // 0 [2 3 3] 140 | // 2 [3] 141 | // err: 142 | } 143 | 144 | // Example with zero value Text. For options see examples under Text. 145 | func ExampleText_WriteAdjacencyList() { 146 | // 0 147 | // / \\ 148 | // 2-->3 149 | g := graph.AdjacencyList{ 150 | 0: {2, 3, 3}, 151 | 2: {3}, 152 | 3: {}, 153 | } 154 | n, err := io.Text{}.WriteAdjacencyList(g, os.Stdout) 155 | fmt.Printf("bytes: %d, err: %v\n\n", n, err) 156 | // Output: 157 | // 0: 2 3 3 158 | // 2: 3 159 | // 3: 160 | // bytes: 17, err: 161 | } 162 | 163 | func ExampleText_base() { 164 | const zz = 36*36 - 1 165 | g := graph.AdjacencyList{ 166 | zz: {0}, 167 | } 168 | n, err := io.Text{Base: 36}.WriteAdjacencyList(g, os.Stdout) 169 | fmt.Printf("bytes: %d, err: %v\n\n", n, err) 170 | // Output: 171 | // zz: 0 172 | // bytes: 6, err: 173 | } 174 | 175 | func ExampleText_delimiters() { 176 | // a d 177 | // / \ \ 178 | // b c e 179 | name := []string{"a", "b", "c", "d", "e"} 180 | g := graph.AdjacencyList{ 181 | 0: {1, 2}, 182 | 3: {4}, 183 | 4: {}, 184 | } 185 | t := io.Text{ 186 | NodeName: func(n graph.NI) string { return name[n] }, 187 | FrDelim: "->", 188 | ToDelim: ":", 189 | } 190 | n, err := t.WriteAdjacencyList(g, os.Stdout) 191 | fmt.Printf("bytes: %d, err: %v\n\n", n, err) 192 | // Output: 193 | // a->b:c 194 | // d->e 195 | // bytes: 12, err: 196 | } 197 | 198 | func ExampleText_mapNames() { 199 | // a d 200 | // / \ \ 201 | // b c e 202 | r := bytes.NewBufferString(` 203 | a b c # source target target 204 | d e 205 | `) 206 | // For reading, default blank delimiter fields enable 207 | // delimiting by whitespace. 208 | t := io.Text{MapNames: true, Comment: "#"} 209 | g, names, m, err := t.ReadAdjacencyList(r) 210 | 211 | fmt.Println("names:") 212 | for i, n := range names { 213 | fmt.Println(i, n) 214 | } 215 | fmt.Println("graph:") 216 | for n, to := range g { 217 | fmt.Println(n, to) 218 | } 219 | fmt.Println(graph.OrderMap(m)) 220 | fmt.Println("err:", err) 221 | // Output: 222 | // names: 223 | // 0 a 224 | // 1 b 225 | // 2 c 226 | // 3 d 227 | // 4 e 228 | // graph: 229 | // 0 [1 2] 230 | // 1 [] 231 | // 2 [] 232 | // 3 [4] 233 | // 4 [] 234 | // map[a:0 b:1 c:2 d:3 e:4] 235 | // err: 236 | } 237 | 238 | func ExampleText_nodeName() { 239 | // a d 240 | // / \ \ 241 | // b c e 242 | name := []string{"a", "b", "c", "d", "e"} 243 | g := graph.AdjacencyList{ 244 | 0: {1, 2}, 245 | 3: {4}, 246 | 4: {}, 247 | } 248 | t := io.Text{NodeName: func(n graph.NI) string { return name[n] }} 249 | n, err := t.WriteAdjacencyList(g, os.Stdout) 250 | fmt.Printf("bytes: %d, err: %v\n\n", n, err) 251 | // Output: 252 | // a: b c 253 | // d: e 254 | // bytes: 12, err: 255 | } 256 | 257 | /* 258 | func ExampleText_WriteLabeledAdjacencyList() { 259 | // 0 260 | // / \\ 261 | // a / b\\c 262 | // / \\ 263 | // 2------->3 264 | // d 265 | g := graph.LabeledAdjacencyList{ 266 | 0: {{2, 'a'}, {3, 'b'}, {3, 'c'}}, 267 | 2: {{3, 'd'}}, 268 | 3: {}, 269 | } 270 | n, err := io.Text{}.WriteLabeledAdjacencyList(g, os.Stdout) 271 | fmt.Printf("bytes: %d, err: %v\n\n", n, err) 272 | // Output: 273 | // 0: (2 96) (3 97) (3 98) 274 | // 2: (3 99) 275 | // 3: 276 | // bytes: 17, err: 277 | } 278 | */ 279 | -------------------------------------------------------------------------------- /io/int_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | package io 5 | 6 | import "testing" 7 | 8 | func TestParseNIs(t *testing.T) { 9 | defer func() { 10 | if x := recover(); x == nil { 11 | t.Fatal("expected panic") 12 | } 13 | }() 14 | parseNIs([]string{" 3"}, 10) 15 | } 16 | -------------------------------------------------------------------------------- /io/readltext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | // Package graph/io provides graph readers and writers. 5 | package io 6 | 7 | import ( 8 | "bufio" 9 | "fmt" 10 | "io" 11 | "regexp" 12 | "strconv" 13 | 14 | "github.com/soniakeys/graph" 15 | ) 16 | 17 | // ReadLabeledAdjacencyList reads text data and returns a LabeledAdjacencyList. 18 | // 19 | // Fields of the receiver Text define how the text data is interpreted. 20 | // See documentation of the Text struct. 21 | // 22 | // ReadLabeledAdjacencyList reads to EOF. 23 | // 24 | // On successful read, a valid AdjacencyList is returned with error = nil. 25 | // In addition, with Text.MapNames true, the method returns a list 26 | // of node names indexed by NI and the reverse mapping of NI by name. 27 | func (t Text) ReadLabeledAdjacencyList(r io.Reader) (g graph.LabeledAdjacencyList, err error) { 28 | sep, err := t.sep() 29 | if err != nil { 30 | return nil, err 31 | } 32 | for b := bufio.NewReader(r); ; { 33 | to, err := t.readHalf(b, sep) 34 | if err != nil { 35 | if err != io.EOF { 36 | return nil, err 37 | } 38 | return g, nil 39 | } 40 | g = append(g, to) 41 | } 42 | } 43 | 44 | // read and parse a single line of a to-list. if the last line is missing 45 | // the newline, it returns the data and err == nil. a subsequent call will 46 | // return io.EOF. 47 | func (t *Text) readHalf(r *bufio.Reader, sep *regexp.Regexp) (to []graph.Half, err error) { 48 | s, err := r.ReadString('\n') // but allow last line without \n 49 | if err != nil { 50 | if err != io.EOF || s == "" { 51 | return 52 | } 53 | } 54 | if s == "\n" { // fast path for blank line 55 | return 56 | } 57 | // s is non-empty at this point 58 | f := sep.Split(s, -1) 59 | // some minimal possibilities: 60 | // single sep: "\n" -> ["", ""], the empty strings before and after sep 61 | // single non-sep: "0" -> ["0"], a non-empty string 62 | if f[0] == "" { 63 | f = f[1:] // toss "" before leading any separator 64 | } 65 | last := len(f) - 1 66 | if f[last] == "" { 67 | f = f[:last] // toss "" after trailing separator, usually the \n 68 | } 69 | if len(f)%2 != 0 { 70 | return nil, fmt.Errorf("odd data") 71 | } 72 | to = make([]graph.Half, len(f)/2) 73 | y := 0 74 | for x := range to { 75 | ni, err := strconv.ParseInt(f[y], t.Base, graph.NIBits) 76 | if err != nil { 77 | return nil, err 78 | } 79 | y++ 80 | li, err := strconv.ParseInt(f[y], t.Base, graph.NIBits) 81 | if err != nil { 82 | return nil, err 83 | } 84 | y++ 85 | to[x] = graph.Half{graph.NI(ni), graph.LI(li)} 86 | } 87 | return to, nil 88 | } 89 | -------------------------------------------------------------------------------- /io/text.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | // Package graph/io provides graph readers and writers. 5 | package io 6 | 7 | import ( 8 | "github.com/soniakeys/graph" 9 | ) 10 | 11 | /* consider: 12 | should read with arcDir != All reconstruct undirected graphs? 13 | graph.Directed.Undirected will do it, but it would be more efficient to do it 14 | here. 15 | */ 16 | 17 | // ArcDir specifies whether to consider all arcs, or only arcs that would 18 | // be in the upper or lower triangle of an adjacency matrix representation. 19 | // 20 | // For the case of undirected graphs, the effect of Upper or Lower is to 21 | // specify that the text representation does not contain reciprocal arcs 22 | // but contains only a single arc for each undirected edge. 23 | type ArcDir int 24 | 25 | const ( 26 | All ArcDir = iota // all directed arcs 27 | Upper // only arcs where to >= from 28 | Lower // only arcs where to <= from 29 | ) 30 | 31 | // Format defines the fundamental format of text data. 32 | type Format int 33 | 34 | const ( 35 | // Sparse is the default. Each line has a from-node followed by a list 36 | // of to-nodes. A node with no to-nodes does not need to be listed unless 37 | // nodes are being stored as NIs and it is the maximum value NI. In this 38 | // case it goes on a line by itself to preserve graph order (number of 39 | // nodes.) If multiple lines have the same from-node, the to-nodes are 40 | // concatenated. 41 | Sparse Format = iota 42 | 43 | // Dense is only meaningful when nodes are being stored as NIs. The node 44 | // NI is implied by line number. There is a line for each node, to-nodes 45 | // or not. 46 | Dense 47 | 48 | // Arc format is not actually adjacency list, but arc list or edge list. 49 | // There are exactly two nodes per line, a from-node and a to-node. 50 | Arcs 51 | ) 52 | 53 | // Type Text defines options for reading and writing simple text formats. 54 | // 55 | // The zero value is valid and usable by all methods. Writing a graph with 56 | // a zero value Text writes a default format that is readable with a zero 57 | // value Text. 58 | // 59 | // Read methods delimit text based on a combination of Text fields: 60 | // 61 | // When MapNames is false, text data is parsed as numeric NIs and LIs in 62 | // the numeric base of field Base, although using 10 if Base is 0. IDs are 63 | // delimited in this case by any non-empty string of characters that cannot be 64 | // in an integer of the specified base. For example in the case of base 10 65 | // IDs, any string of characters not in "+-0123456789" will delimit IDs. 66 | // 67 | // When MapNames is true, delimiting depends on FrDelim and ToDelim. If 68 | // the whitespace-trimmed value of FrDelim or ToDelim is non-empty, text data 69 | // is split on the trimmed value. If the delimiter is non-empty but 70 | // all whitespace, text data is split on the untrimmed value. In either 71 | // of these cases, individual fields are then trimmed of leading and trailing 72 | // whitespace. If FrDelim or ToDelim are empty strings, input text data is 73 | // delimited by non-empty strings of whitespace. 74 | type Text struct { 75 | Format Format // Fundamental format of text representation 76 | Comment string // End of line comment delimiter 77 | 78 | // FrDelim is a delimiter following from-node, for Sparse and Arc formats. 79 | // Write methods write ": " if FrDelim is blank. For read behavior 80 | // see description at Type Text. 81 | FrDelim string 82 | 83 | // ToDelim separates nodes of to-list, for Sparse and Dense formats. 84 | // Write methods write " " if ToDelim is blank. For read behavior see 85 | // description at Type Text. 86 | ToDelim string 87 | 88 | // HalfDelim separates the NI and LI of a half arc in a labeled adjacency 89 | // list. WriteLabeledAdjacencyList writes " " if HalfDelim is blank. 90 | HalfDelim string 91 | 92 | // Open and Close surround the NI LI pair of a half arc in a labeled 93 | // adjacency list. WriteLabeledAdjacenyList writes "(" and ")" if both 94 | // are blank. 95 | Open, Close string 96 | 97 | // Base is the numeric base for NIs and LIs. Methods pass this to strconv 98 | // functions and so values should be in the range 2-36, although they will 99 | // pass 10 to strconv functions when Base is 0. 100 | Base int 101 | 102 | // MapNames true means to consider node text to be symbolic rather 103 | // than numeric NIs. Read methods assign numeric graph NIs as data is 104 | // read, and return the mapping between node names and NIs. 105 | MapNames bool 106 | 107 | // A non-nil NodeName is used by write methods to translate NIs to 108 | // strings and write the strings as symbolic node names rather than numeric 109 | // NIs. 110 | NodeName func(graph.NI) string 111 | 112 | // WriteArcs can specify to write only a single arc of an undirected 113 | // graph. See definition of ArcDir. 114 | WriteArcs ArcDir 115 | } 116 | 117 | // NewText is a small convenience constructor. 118 | // 119 | // It simply returns &Text{Comment: "//"}. 120 | func NewText() *Text { 121 | return &Text{Comment: "//"} 122 | } 123 | -------------------------------------------------------------------------------- /io/writeltext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | // Package graph/io provides graph readers and writers. 5 | package io 6 | 7 | import ( 8 | "bufio" 9 | "fmt" 10 | "io" 11 | "strconv" 12 | 13 | "github.com/soniakeys/graph" 14 | ) 15 | 16 | // WriteLabeledAdjacencyList writes a lableed adjacency list as text. 17 | // 18 | // Fields of the receiver Text define how the text data is formatted. 19 | // See documentation of the Text struct. 20 | // 21 | // Returned is number of bytes written and error. 22 | func (t Text) WriteLabeledAdjacencyList(g graph.LabeledAdjacencyList, 23 | w io.Writer) (n int, err error) { 24 | switch t.Format { 25 | case Sparse: 26 | return t.writeLALSparse(g, w) 27 | case Dense: 28 | return t.writeLALDense(g, w) 29 | case Arcs: 30 | return t.writeLALArcs(g, w) 31 | } 32 | return 0, fmt.Errorf("format %d invalid", t.Format) 33 | } 34 | 35 | func (t Text) writeLALSparse(g graph.LabeledAdjacencyList, 36 | w io.Writer) (n int, err error) { 37 | return 38 | } 39 | func (t Text) writeLALArcs(g graph.LabeledAdjacencyList, 40 | w io.Writer) (n int, err error) { 41 | return 42 | } 43 | func (t Text) writeLALDense(g graph.LabeledAdjacencyList, 44 | w io.Writer) (n int, err error) { 45 | t.fixBase() 46 | b := bufio.NewWriter(w) 47 | var c int 48 | for _, to := range g { 49 | if len(to) > 0 { 50 | c, err = b.WriteString(strconv.FormatInt(int64(to[0].To), t.Base)) 51 | n += c 52 | if err != nil { 53 | return 54 | } 55 | if err = b.WriteByte(' '); err != nil { 56 | return 57 | } 58 | c, err = b.WriteString(strconv.FormatInt(int64(to[0].Label), t.Base)) 59 | n += c + 1 60 | if err != nil { 61 | return 62 | } 63 | for _, to := range to[1:] { 64 | if err = b.WriteByte(' '); err != nil { 65 | return 66 | } 67 | c, err = b.WriteString(strconv.FormatInt(int64(to.To), t.Base)) 68 | n += c + 1 69 | if err != nil { 70 | return 71 | } 72 | if err = b.WriteByte(' '); err != nil { 73 | return 74 | } 75 | c, err = b.WriteString(strconv.FormatInt(int64(to.Label), t.Base)) 76 | n += c + 1 77 | if err != nil { 78 | return 79 | } 80 | } 81 | } 82 | if err = b.WriteByte('\n'); err != nil { 83 | return 84 | } 85 | n++ 86 | } 87 | b.Flush() 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /io/writetext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | // Package graph/io provides graph readers and writers. 5 | package io 6 | 7 | import ( 8 | "bufio" 9 | "fmt" 10 | "io" 11 | "strconv" 12 | "strings" 13 | "unicode" 14 | 15 | "github.com/soniakeys/graph" 16 | ) 17 | 18 | // WriteAdjacencyList writes an adjacency list as text. 19 | // 20 | // Fields of the receiver Text define how the text data is formatted. 21 | // See documentation of the Text struct. 22 | // 23 | // Returned is number of bytes written and error. 24 | func (t Text) WriteAdjacencyList(g graph.AdjacencyList, w io.Writer) ( 25 | int, error) { 26 | switch t.Format { 27 | case Sparse: 28 | return t.writeALSparse(g, w) 29 | case Dense: 30 | return t.writeALDense(g, w) 31 | case Arcs: 32 | return t.writeALArcs(g, w) 33 | } 34 | return 0, fmt.Errorf("format %d invalid", t.Format) 35 | } 36 | 37 | func (t Text) writeALDense(g graph.AdjacencyList, w io.Writer) ( 38 | n int, err error) { 39 | if t.WriteArcs != All { 40 | return t.writeALDenseTriangle(g, w) 41 | } 42 | t.fixBase() 43 | b := bufio.NewWriter(w) 44 | var c int 45 | for _, to := range g { 46 | if len(to) > 0 { 47 | c, err = b.WriteString(strconv.FormatInt(int64(to[0]), t.Base)) 48 | n += c 49 | if err != nil { 50 | return 51 | } 52 | for _, to := range to[1:] { 53 | if err = b.WriteByte(' '); err != nil { 54 | return 55 | } 56 | c, err = b.WriteString(strconv.FormatInt(int64(to), t.Base)) 57 | n += c + 1 58 | if err != nil { 59 | return 60 | } 61 | } 62 | } 63 | if err = b.WriteByte('\n'); err != nil { 64 | return 65 | } 66 | n++ 67 | } 68 | b.Flush() 69 | return 70 | } 71 | 72 | func (t Text) writeALSparse(g graph.AdjacencyList, w io.Writer) ( 73 | n int, err error) { 74 | if t.FrDelim == "" { 75 | t.FrDelim = ": " 76 | } 77 | if t.ToDelim == "" { 78 | t.ToDelim = " " 79 | } 80 | writeLast := t.NodeName == nil 81 | if writeLast { 82 | t.fixBase() 83 | t.NodeName = func(n graph.NI) string { 84 | return strconv.FormatInt(int64(n), t.Base) 85 | } 86 | } 87 | if t.WriteArcs != All { 88 | return t.writeALSparseTriangle(g, w, writeLast) 89 | } 90 | b := bufio.NewWriter(w) 91 | var c int 92 | last := len(g) - 1 93 | for i, to := range g { 94 | switch { 95 | case len(to) > 0: 96 | fr := graph.NI(i) 97 | c, err = b.WriteString(t.NodeName(fr)) 98 | n += c 99 | if err != nil { 100 | return 101 | } 102 | c, err = b.WriteString(t.FrDelim) 103 | n += c 104 | if err != nil { 105 | return 106 | } 107 | c, err = b.WriteString(t.NodeName(to[0])) 108 | n += c 109 | if err != nil { 110 | return 111 | } 112 | for _, to := range to[1:] { 113 | c, err = b.WriteString(t.ToDelim) 114 | n += c 115 | if err != nil { 116 | return 117 | } 118 | c, err = b.WriteString(t.NodeName(to)) 119 | n += c 120 | if err != nil { 121 | return 122 | } 123 | } 124 | case i == last && writeLast: 125 | fr := graph.NI(i) 126 | c, err = b.WriteString(t.NodeName(fr)) 127 | n += c 128 | if err != nil { 129 | return 130 | } 131 | c, err = b.WriteString( 132 | strings.TrimRightFunc(t.FrDelim, unicode.IsSpace)) 133 | n += c 134 | if err != nil { 135 | return 136 | } 137 | default: 138 | continue // without writing \n 139 | } 140 | if err = b.WriteByte('\n'); err != nil { 141 | return 142 | } 143 | n++ 144 | } 145 | b.Flush() 146 | return 147 | } 148 | 149 | func (t Text) writeALArcs(g graph.AdjacencyList, w io.Writer) ( 150 | n int, err error) { 151 | t.fixBase() 152 | if t.FrDelim == "" { 153 | t.FrDelim = " " 154 | } 155 | if t.NodeName == nil { 156 | t.NodeName = func(n graph.NI) string { 157 | return strconv.FormatInt(int64(n), t.Base) 158 | } 159 | } 160 | b := bufio.NewWriter(w) 161 | var c int 162 | for fr, to := range g { 163 | for _, to := range to { 164 | c, err = b.WriteString(t.NodeName(graph.NI(fr))) 165 | n += c 166 | if err != nil { 167 | return 168 | } 169 | c, err = b.WriteString(t.FrDelim) 170 | n += c 171 | if err != nil { 172 | return 173 | } 174 | c, err = b.WriteString(t.NodeName(to)) 175 | n += c 176 | if err != nil { 177 | return 178 | } 179 | if err = b.WriteByte('\n'); err != nil { 180 | return 181 | } 182 | n++ 183 | } 184 | } 185 | b.Flush() 186 | return 187 | } 188 | 189 | func (t Text) writeALDenseTriangle(g graph.AdjacencyList, w io.Writer) ( 190 | n int, err error) { 191 | t.fixBase() 192 | b := bufio.NewWriter(w) 193 | var c int 194 | p := func(fr, to graph.NI) bool { return to >= fr } 195 | if t.WriteArcs == Lower { 196 | p = func(fr, to graph.NI) bool { return to <= fr } 197 | } 198 | for i, to := range g { 199 | fr := graph.NI(i) 200 | one := false 201 | for _, to := range to { 202 | if p(fr, to) { 203 | if one { 204 | if err = b.WriteByte(' '); err != nil { 205 | return 206 | } 207 | n++ 208 | } else { 209 | one = true 210 | } 211 | c, err = b.WriteString(strconv.FormatInt(int64(to), t.Base)) 212 | n += c 213 | if err != nil { 214 | return 215 | } 216 | } 217 | } 218 | if err = b.WriteByte('\n'); err != nil { 219 | return 220 | } 221 | n++ 222 | } 223 | b.Flush() 224 | return 225 | } 226 | 227 | func (t Text) writeALSparseTriangle(g graph.AdjacencyList, w io.Writer, writeLast bool) ( 228 | n int, err error) { 229 | b := bufio.NewWriter(w) 230 | var c int 231 | p := func(fr, to graph.NI) bool { return to >= fr } 232 | if t.WriteArcs == Lower { 233 | p = func(fr, to graph.NI) bool { return to <= fr } 234 | } 235 | last := len(g) - 1 236 | for i, to := range g { 237 | fr := graph.NI(i) 238 | one := false 239 | for _, to := range to { 240 | if p(fr, to) { 241 | if !one { 242 | one = true 243 | c, err = b.WriteString(t.NodeName(fr)) 244 | n += c 245 | if err != nil { 246 | return 247 | } 248 | c, err = b.WriteString(t.FrDelim) 249 | n += c 250 | if err != nil { 251 | return 252 | } 253 | } else { 254 | c, err = b.WriteString(t.ToDelim) 255 | n += c 256 | if err != nil { 257 | return 258 | } 259 | } 260 | c, err = b.WriteString(t.NodeName(to)) 261 | n += c 262 | if err != nil { 263 | return 264 | } 265 | } 266 | } 267 | if writeLast && i == last && !one { 268 | one = true 269 | c, err = b.WriteString(t.NodeName(fr)) 270 | n += c 271 | if err != nil { 272 | return 273 | } 274 | c, err = b.WriteString( 275 | strings.TrimRightFunc(t.FrDelim, unicode.IsSpace)) 276 | n += c 277 | if err != nil { 278 | return 279 | } 280 | } 281 | if one { 282 | if err = b.WriteByte('\n'); err != nil { 283 | return 284 | } 285 | n++ 286 | } 287 | } 288 | b.Flush() 289 | return 290 | } 291 | -------------------------------------------------------------------------------- /mst.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package graph 5 | 6 | import ( 7 | "container/heap" 8 | "sort" 9 | 10 | "github.com/soniakeys/bits" 11 | ) 12 | 13 | type dsElement struct { 14 | from NI 15 | rank int 16 | } 17 | 18 | type disjointSet struct { 19 | set []dsElement 20 | } 21 | 22 | func newDisjointSet(n int) disjointSet { 23 | set := make([]dsElement, n) 24 | for i := range set { 25 | set[i].from = -1 26 | } 27 | return disjointSet{set} 28 | } 29 | 30 | // return true if disjoint trees were combined. 31 | // false if x and y were already in the same tree. 32 | func (ds disjointSet) union(x, y NI) bool { 33 | xr := ds.find(x) 34 | yr := ds.find(y) 35 | if xr == yr { 36 | return false 37 | } 38 | switch xe, ye := &ds.set[xr], &ds.set[yr]; { 39 | case xe.rank < ye.rank: 40 | xe.from = yr 41 | case xe.rank == ye.rank: 42 | xe.rank++ 43 | fallthrough 44 | default: 45 | ye.from = xr 46 | } 47 | return true 48 | } 49 | 50 | func (ds disjointSet) find(n NI) NI { 51 | // fast paths for n == root or from root. 52 | // no updates need in these cases. 53 | s := ds.set 54 | fr := s[n].from 55 | if fr < 0 { // n is root 56 | return n 57 | } 58 | n, fr = fr, s[fr].from 59 | if fr < 0 { // n is from root 60 | return n 61 | } 62 | // otherwise updates needed. 63 | // two iterative passes (rather than recursion or stack) 64 | // pass 1: find root 65 | r := fr 66 | for { 67 | f := s[r].from 68 | if f < 0 { 69 | break 70 | } 71 | r = f 72 | } 73 | // pass 2: update froms 74 | for { 75 | s[n].from = r 76 | if fr == r { 77 | return r 78 | } 79 | n = fr 80 | fr = s[n].from 81 | } 82 | } 83 | 84 | // Kruskal implements Kruskal's algorithm for constructing a minimum spanning 85 | // forest on an undirected graph. 86 | // 87 | // The forest is returned as an undirected graph. 88 | // 89 | // Also returned is a total distance for the returned forest. 90 | // 91 | // This method is a convenience wrapper for LabeledEdgeList.Kruskal. 92 | // If you have no need for the input graph as a LabeledUndirected, it may be 93 | // more efficient to construct a LabeledEdgeList directly. 94 | func (g LabeledUndirected) Kruskal(w WeightFunc) (spanningForest LabeledUndirected, dist float64) { 95 | return g.WeightedArcsAsEdges(w).Kruskal() 96 | } 97 | 98 | // Kruskal implements Kruskal's algorithm for constructing a minimum spanning 99 | // forest on an undirected graph. 100 | // 101 | // The algorithm allows parallel edges, thus it is acceptable to construct 102 | // the receiver with LabeledUndirected.WeightedArcsAsEdges. It may be more 103 | // efficient though, if you can construct the receiver WeightedEdgeList 104 | // directly without parallel edges. 105 | // 106 | // The forest is returned as an undirected graph. 107 | // 108 | // Also returned is a total distance for the returned forest. 109 | // 110 | // The edge list of the receiver is sorted in place as a side effect of this 111 | // method. See KruskalSorted for a version that relies on the edge list being 112 | // already sorted. This method is a wrapper for KruskalSorted. If you can 113 | // generate the input graph sorted as required for KruskalSorted, you can 114 | // call that method directly and avoid the overhead of the sort. 115 | func (l WeightedEdgeList) Kruskal() (g LabeledUndirected, dist float64) { 116 | e := l.Edges 117 | w := l.WeightFunc 118 | sort.Slice(e, func(i, j int) bool { return w(e[i].LI) < w(e[j].LI) }) 119 | return l.KruskalSorted() 120 | } 121 | 122 | // KruskalSorted implements Kruskal's algorithm for constructing a minimum 123 | // spanning tree on an undirected graph. 124 | // 125 | // When called, the edge list of the receiver must be already sorted by weight. 126 | // See the Kruskal method for a version that accepts an unsorted edge list. 127 | // As with Kruskal, parallel edges are allowed. 128 | // 129 | // The forest is returned as an undirected graph. 130 | // 131 | // Also returned is a total distance for the returned forest. 132 | func (l WeightedEdgeList) KruskalSorted() (g LabeledUndirected, dist float64) { 133 | ds := newDisjointSet(l.Order) 134 | g.LabeledAdjacencyList = make(LabeledAdjacencyList, l.Order) 135 | for _, e := range l.Edges { 136 | if ds.union(e.N1, e.N2) { 137 | g.AddEdge(Edge{e.N1, e.N2}, e.LI) 138 | dist += l.WeightFunc(e.LI) 139 | } 140 | } 141 | return 142 | } 143 | 144 | // Prim implements the Jarník-Prim-Dijkstra algorithm for constructing 145 | // a minimum spanning tree on an undirected graph. 146 | // 147 | // Prim computes a minimal spanning tree on the connected component containing 148 | // the given start node. The tree is returned in FromList f. Argument f 149 | // cannot be a nil pointer although it can point to a zero value FromList. 150 | // 151 | // If the passed FromList.Paths has the len of g though, it will be reused. 152 | // In the case of a graph with multiple connected components, this allows a 153 | // spanning forest to be accumulated by calling Prim successively on 154 | // representative nodes of the components. In this case if leaves for 155 | // individual trees are of interest, pass a non-nil zero-value for the argument 156 | // componentLeaves and it will be populated with leaves for the single tree 157 | // spanned by the call. 158 | // 159 | // If argument labels is non-nil, it must have the same length as g and will 160 | // be populated with labels corresponding to the edges of the tree. 161 | // 162 | // Returned are the number of nodes spanned for the single tree (which will be 163 | // the order of the connected component) and the total spanned distance for the 164 | // single tree. 165 | func (g LabeledUndirected) Prim(start NI, w WeightFunc, f *FromList, labels []LI, componentLeaves *bits.Bits) (numSpanned int, dist float64) { 166 | al := g.LabeledAdjacencyList 167 | if len(f.Paths) != len(al) { 168 | *f = NewFromList(len(al)) 169 | } 170 | if f.Leaves.Num != len(al) { 171 | f.Leaves = bits.New(len(al)) 172 | } 173 | b := make([]prNode, len(al)) // "best" 174 | for n := range b { 175 | b[n].nx = NI(n) 176 | b[n].fx = -1 177 | } 178 | rp := f.Paths 179 | var frontier prHeap 180 | rp[start] = PathEnd{From: -1, Len: 1} 181 | numSpanned = 1 182 | fLeaves := &f.Leaves 183 | fLeaves.SetBit(int(start), 1) 184 | if componentLeaves != nil { 185 | if componentLeaves.Num != len(al) { 186 | *componentLeaves = bits.New(len(al)) 187 | } 188 | componentLeaves.SetBit(int(start), 1) 189 | } 190 | for a := start; ; { 191 | for _, nb := range al[a] { 192 | if rp[nb.To].Len > 0 { 193 | continue // already in MST, no action 194 | } 195 | switch bp := &b[nb.To]; { 196 | case bp.fx == -1: // new node for frontier 197 | bp.from = fromHalf{From: a, Label: nb.Label} 198 | bp.wt = w(nb.Label) 199 | heap.Push(&frontier, bp) 200 | case w(nb.Label) < bp.wt: // better arc 201 | bp.from = fromHalf{From: a, Label: nb.Label} 202 | bp.wt = w(nb.Label) 203 | heap.Fix(&frontier, bp.fx) 204 | } 205 | } 206 | if len(frontier) == 0 { 207 | break // done 208 | } 209 | bp := heap.Pop(&frontier).(*prNode) 210 | a = bp.nx 211 | rp[a].Len = rp[bp.from.From].Len + 1 212 | rp[a].From = bp.from.From 213 | if len(labels) != 0 { 214 | labels[a] = bp.from.Label 215 | } 216 | dist += bp.wt 217 | fLeaves.SetBit(int(bp.from.From), 0) 218 | fLeaves.SetBit(int(a), 1) 219 | if componentLeaves != nil { 220 | componentLeaves.SetBit(int(bp.from.From), 0) 221 | componentLeaves.SetBit(int(a), 1) 222 | } 223 | numSpanned++ 224 | } 225 | return 226 | } 227 | 228 | type prNode struct { 229 | nx NI 230 | from fromHalf 231 | wt float64 // p.Weight(from.Label) 232 | fx int 233 | } 234 | 235 | type prHeap []*prNode 236 | 237 | func (h prHeap) Len() int { return len(h) } 238 | func (h prHeap) Less(i, j int) bool { return h[i].wt < h[j].wt } 239 | func (h prHeap) Swap(i, j int) { 240 | h[i], h[j] = h[j], h[i] 241 | h[i].fx = i 242 | h[j].fx = j 243 | } 244 | func (p *prHeap) Push(x interface{}) { 245 | nd := x.(*prNode) 246 | nd.fx = len(*p) 247 | *p = append(*p, nd) 248 | } 249 | func (p *prHeap) Pop() interface{} { 250 | r := *p 251 | last := len(r) - 1 252 | *p = r[:last] 253 | return r[last] 254 | } 255 | -------------------------------------------------------------------------------- /mst_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | package graph_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/soniakeys/bits" 11 | "github.com/soniakeys/graph" 12 | ) 13 | 14 | func ExampleLabeledUndirected_Kruskal() { 15 | // (10) 16 | // 0------4----\ 17 | // | /| \(70) 18 | // (30)| (40) |(60) \ 19 | // |/ | | 20 | // 1------2------3 21 | // (50) (20) 22 | w := func(l graph.LI) float64 { return float64(l) } 23 | // undirected graph 24 | var g graph.LabeledUndirected 25 | g.AddEdge(graph.Edge{0, 1}, 30) 26 | g.AddEdge(graph.Edge{0, 4}, 10) 27 | g.AddEdge(graph.Edge{1, 2}, 50) 28 | g.AddEdge(graph.Edge{1, 4}, 40) 29 | g.AddEdge(graph.Edge{2, 3}, 20) 30 | g.AddEdge(graph.Edge{2, 4}, 60) 31 | g.AddEdge(graph.Edge{3, 4}, 70) 32 | 33 | t, dist := g.Kruskal(w) 34 | 35 | fmt.Println("spanning tree as undirected graph:") 36 | for n, to := range t.LabeledAdjacencyList { 37 | fmt.Println(n, to) 38 | } 39 | fmt.Println("total distance: ", dist) 40 | // Output: 41 | // spanning tree as undirected graph: 42 | // 0 [{4 10} {1 30}] 43 | // 1 [{0 30} {2 50}] 44 | // 2 [{3 20} {1 50}] 45 | // 3 [{2 20}] 46 | // 4 [{0 10}] 47 | // total distance: 110 48 | } 49 | 50 | func ExampleWeightedEdgeList_Kruskal() { 51 | // (10) 52 | // 0------4----\ 53 | // | /| \(70) 54 | // (30)| (40) |(60) \ 55 | // |/ | | 56 | // 1------2------3 57 | // (50) (20) 58 | w := func(l graph.LI) float64 { return float64(l) } 59 | // construct WeightedEdgeList directly 60 | l := graph.WeightedEdgeList{5, w, []graph.LabeledEdge{ 61 | {graph.Edge{0, 1}, 30}, 62 | {graph.Edge{0, 4}, 10}, 63 | {graph.Edge{1, 2}, 50}, 64 | {graph.Edge{1, 4}, 40}, 65 | {graph.Edge{2, 3}, 20}, 66 | {graph.Edge{2, 4}, 60}, 67 | {graph.Edge{3, 4}, 70}, 68 | }} 69 | 70 | t, dist := l.Kruskal() 71 | 72 | fmt.Println("spanning tree as undirected graph:") 73 | for n, to := range t.LabeledAdjacencyList { 74 | fmt.Println(n, to) 75 | } 76 | fmt.Println("total distance: ", dist) 77 | // Output: 78 | // spanning tree as undirected graph: 79 | // 0 [{4 10} {1 30}] 80 | // 1 [{0 30} {2 50}] 81 | // 2 [{3 20} {1 50}] 82 | // 3 [{2 20}] 83 | // 4 [{0 10}] 84 | // total distance: 110 85 | } 86 | 87 | func ExampleWeightedEdgeList_Kruskal_fromUndirected() { 88 | // (10) 89 | // 0------4----\ 90 | // | /| \(70) 91 | // (30)| (40) |(60) \ 92 | // |/ | | 93 | // 1------2------3 94 | // (50) (20) 95 | w := func(l graph.LI) float64 { return float64(l) } 96 | // undirected graph 97 | var g graph.LabeledUndirected 98 | g.AddEdge(graph.Edge{0, 1}, 30) 99 | g.AddEdge(graph.Edge{0, 4}, 10) 100 | g.AddEdge(graph.Edge{1, 2}, 50) 101 | g.AddEdge(graph.Edge{1, 4}, 40) 102 | g.AddEdge(graph.Edge{2, 3}, 20) 103 | g.AddEdge(graph.Edge{2, 4}, 60) 104 | g.AddEdge(graph.Edge{3, 4}, 70) 105 | // convert to edge list for Kruskal. 106 | l := g.WeightedArcsAsEdges(w) 107 | 108 | t, dist := l.Kruskal() 109 | 110 | fmt.Println("spanning tree as undirected graph:") 111 | for n, to := range t.LabeledAdjacencyList { 112 | fmt.Println(n, to) 113 | } 114 | fmt.Println("total distance: ", dist) 115 | // Output: 116 | // spanning tree as undirected graph: 117 | // 0 [{4 10} {1 30}] 118 | // 1 [{0 30} {2 50}] 119 | // 2 [{3 20} {1 50}] 120 | // 3 [{2 20}] 121 | // 4 [{0 10}] 122 | // total distance: 110 123 | } 124 | 125 | func ExampleWeightedEdgeList_KruskalSorted() { 126 | // (10) 127 | // 0------4----\ 128 | // | /| \(70) 129 | // (30)| (40) |(60) \ 130 | // |/ | | 131 | // 1------2------3 132 | // (50) (20) 133 | w := func(l graph.LI) float64 { return float64(l) } 134 | // Bypass construction of an undirected graph if you can, by directly 135 | // constructing an edge list. No need for reciprocal arcs. Also if 136 | // you can, construct it already sorted by weight. 137 | l := graph.WeightedEdgeList{5, w, []graph.LabeledEdge{ 138 | {graph.Edge{0, 4}, 10}, 139 | {graph.Edge{2, 3}, 20}, 140 | {graph.Edge{0, 1}, 30}, 141 | {graph.Edge{1, 4}, 40}, 142 | {graph.Edge{1, 2}, 50}, 143 | {graph.Edge{2, 4}, 60}, 144 | {graph.Edge{3, 4}, 70}, 145 | }} 146 | 147 | t, dist := l.KruskalSorted() 148 | 149 | fmt.Println("spanning tree as undirected graph:") 150 | for n, to := range t.LabeledAdjacencyList { 151 | fmt.Println(n, to) 152 | } 153 | fmt.Println("total distance: ", dist) 154 | // Output: 155 | // spanning tree as undirected graph: 156 | // 0 [{4 10} {1 30}] 157 | // 1 [{0 30} {2 50}] 158 | // 2 [{3 20} {1 50}] 159 | // 3 [{2 20}] 160 | // 4 [{0 10}] 161 | // total distance: 110 162 | } 163 | 164 | func ExampleLabeledUndirected_Prim() { 165 | // graph: 166 | // 167 | // (2) (3) 168 | // |\ \ 169 | // | \ \ 2 170 | // | \ \ 171 | // 4 | \ 5 (4) 172 | // | \ 173 | // | \ 174 | // | \ 175 | // (1)-----(0) 176 | // 3 177 | var g graph.LabeledUndirected 178 | g.AddEdge(graph.Edge{0, 1}, 3) 179 | g.AddEdge(graph.Edge{1, 2}, 4) 180 | g.AddEdge(graph.Edge{2, 0}, 5) 181 | g.AddEdge(graph.Edge{3, 4}, 2) 182 | // weight function 183 | w := func(arcLabel graph.LI) float64 { return float64(arcLabel) } 184 | 185 | // get connected components 186 | reps, orders, _ := g.ConnectedComponentReps() 187 | fmt.Println(len(reps), "connected components:") 188 | fmt.Println("Representative node Order (number of nodes in component)") 189 | for i, r := range reps { 190 | fmt.Printf("%d %20d\n", r, orders[i]) 191 | } 192 | 193 | a := g.LabeledAdjacencyList 194 | f := graph.NewFromList(len(a)) 195 | labels := make([]graph.LI, len(a)) 196 | 197 | // construct spanning tree for each component 198 | fmt.Println("Span results:") 199 | fmt.Println("Root Nodes spanned Total tree distance Leaves") 200 | for _, r := range reps { 201 | var leaves bits.Bits 202 | ns, dist := g.Prim(r, w, &f, labels, &leaves) 203 | fmt.Printf("%d %17d %20.0f %d\n", r, ns, dist, leaves.Slice()) 204 | } 205 | 206 | // show final forest 207 | fmt.Println("Spanning forest:") 208 | fmt.Println("Node From Arc distance Path length Leaf") 209 | for n, pe := range f.Paths { 210 | fmt.Printf("%d %8d %13.0f %12d %5d\n", 211 | n, pe.From, w(labels[n]), pe.Len, f.Leaves.Bit(n)) 212 | } 213 | 214 | // optionally, convert to undirected graph 215 | d, _ := f.TransposeLabeled(labels, nil) 216 | u := d.Undirected() 217 | fmt.Println("Equivalent undirected graph:") 218 | for fr, to := range u.LabeledAdjacencyList { 219 | fmt.Printf("%d: %#v\n", fr, to) 220 | } 221 | 222 | // Output: 223 | // 2 connected components: 224 | // Representative node Order (number of nodes in component) 225 | // 0 3 226 | // 3 2 227 | // Span results: 228 | // Root Nodes spanned Total tree distance Leaves 229 | // 0 3 7 [2] 230 | // 3 2 2 [4] 231 | // Spanning forest: 232 | // Node From Arc distance Path length Leaf 233 | // 0 -1 0 1 0 234 | // 1 0 3 2 0 235 | // 2 1 4 3 1 236 | // 3 -1 0 1 0 237 | // 4 3 2 2 1 238 | // Equivalent undirected graph: 239 | // 0: []graph.Half{graph.Half{To:1, Label:3}} 240 | // 1: []graph.Half{graph.Half{To:2, Label:4}, graph.Half{To:0, Label:3}} 241 | // 2: []graph.Half{graph.Half{To:1, Label:4}} 242 | // 3: []graph.Half{graph.Half{To:4, Label:2}} 243 | // 4: []graph.Half{graph.Half{To:3, Label:2}} 244 | } 245 | 246 | func TestPrim100(t *testing.T) { 247 | r100 := r(100, 200, 62) 248 | u100 := r100.l.Undirected() 249 | reps, orders, _ := u100.ConnectedComponentReps() 250 | w := func(l graph.LI) float64 { return r100.w[l] } 251 | var f graph.FromList 252 | // construct spanning tree for each component 253 | for i, r := range reps { 254 | ns, _ := u100.Prim(r, w, &f, nil, nil) 255 | if ns != orders[i] { 256 | t.Fatal("Not all nodes spanned within a connected component.") 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /random_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | package graph_test 5 | 6 | import ( 7 | "fmt" 8 | "math/rand" 9 | "testing" 10 | "time" 11 | 12 | "github.com/soniakeys/graph" 13 | ) 14 | 15 | func ExampleEuclidean() { 16 | r := rand.New(rand.NewSource(7)) 17 | g, pos, err := graph.Euclidean(4, 6, 1, 1, r) 18 | if err != nil { 19 | fmt.Println(err) 20 | return 21 | } 22 | fmt.Println(g.Order(), "nodes") 23 | fmt.Println("n position") 24 | for n, p := range pos { 25 | fmt.Printf("%d (%.2f, %.2f)\n", n, p.X, p.Y) 26 | } 27 | fmt.Println(g.ArcSize(), "arcs:") 28 | for n, to := range g.AdjacencyList { 29 | fmt.Println(n, "->", to) 30 | } 31 | // Random output: 32 | // 4 nodes 33 | // n position 34 | // 0 (0.92, 0.23) 35 | // 1 (0.24, 0.91) 36 | // 2 (0.70, 0.15) 37 | // 3 (0.35, 0.34) 38 | // 6 arcs: 39 | // 0 -> [2 1] 40 | // 1 -> [0] 41 | // 2 -> [3 0] 42 | // 3 -> [1] 43 | } 44 | 45 | func ExampleGeometric() { 46 | r := rand.New(rand.NewSource(7)) 47 | g, pos, m := graph.Geometric(4, .6, r) 48 | fmt.Println(g.Order(), "nodes") 49 | fmt.Println("n position") 50 | for n, p := range pos { 51 | fmt.Printf("%d (%.2f, %.2f)\n", n, p.X, p.Y) 52 | } 53 | fmt.Println(m, "edges:") 54 | for n, to := range g.AdjacencyList { 55 | for _, to := range to { 56 | if graph.NI(n) < to { 57 | fmt.Println(n, "-", to) 58 | } 59 | } 60 | } 61 | // Random output: 62 | // 4 nodes 63 | // n position 64 | // 0 (0.92, 0.23) 65 | // 1 (0.24, 0.91) 66 | // 2 (0.70, 0.15) 67 | // 3 (0.35, 0.34) 68 | // 4 edges: 69 | // 0 - 2 70 | // 0 - 3 71 | // 1 - 3 72 | // 2 - 3 73 | } 74 | 75 | func ExampleKroneckerDirected() { 76 | r := rand.New(rand.NewSource(7)) 77 | g, ma := graph.KroneckerDirected(2, 2, r) 78 | a := g.AdjacencyList 79 | fmt.Println(len(a), "nodes") 80 | fmt.Println(ma, "arcs:") 81 | for fr, to := range a { 82 | fmt.Println(fr, "->", to) 83 | } 84 | // Random output: 85 | // 4 nodes 86 | // 5 arcs: 87 | // 0 -> [2] 88 | // 1 -> [2] 89 | // 2 -> [1] 90 | // 3 -> [2 1] 91 | } 92 | 93 | func ExampleKroneckerUndirected() { 94 | r := rand.New(rand.NewSource(7)) 95 | g, m := graph.KroneckerUndirected(2, 2, r) 96 | a := g.AdjacencyList 97 | fmt.Println(len(a), "nodes") 98 | fmt.Println(m, "edges:") 99 | for fr, to := range a { 100 | for _, to := range to { 101 | if graph.NI(fr) < to { 102 | fmt.Println(fr, "-", to) 103 | } 104 | } 105 | } 106 | // Random output: 107 | // 4 nodes 108 | // 4 edges: 109 | // 0 - 2 110 | // 1 - 2 111 | // 1 - 3 112 | // 2 - 3 113 | } 114 | 115 | func ExampleLabeledGeometric() { 116 | r := rand.New(rand.NewSource(7)) 117 | g, pos, wt := graph.LabeledGeometric(4, .6, r) 118 | fmt.Println(g.Order(), "nodes") 119 | fmt.Println("n position") 120 | for n, p := range pos { 121 | fmt.Printf("%d (%.2f, %.2f)\n", n, p.X, p.Y) 122 | } 123 | fmt.Println(len(wt), "edges:") 124 | for n, to := range g.LabeledAdjacencyList { 125 | for _, to := range to { 126 | if graph.NI(n) < to.To { 127 | fmt.Println(n, "-", to.To) 128 | } 129 | } 130 | } 131 | fmt.Println(g.ArcSize(), "arcs:") 132 | fmt.Println("arc label weight") 133 | for n, to := range g.LabeledAdjacencyList { 134 | for _, to := range to { 135 | fmt.Printf("%d->%d %d %.2f\n", 136 | n, to.To, to.Label, wt[to.Label]) 137 | } 138 | } 139 | // Random output: 140 | // 4 nodes 141 | // n position 142 | // 0 (0.92, 0.23) 143 | // 1 (0.24, 0.91) 144 | // 2 (0.70, 0.15) 145 | // 3 (0.35, 0.34) 146 | // 4 edges: 147 | // 0 - 2 148 | // 0 - 3 149 | // 1 - 3 150 | // 2 - 3 151 | // 8 arcs: 152 | // arc label weight 153 | // 0->2 0 0.24 154 | // 0->3 1 0.58 155 | // 1->3 2 0.58 156 | // 2->0 0 0.24 157 | // 2->3 3 0.40 158 | // 3->0 1 0.58 159 | // 3->1 2 0.58 160 | // 3->2 3 0.40 161 | } 162 | 163 | func ExampleLabeledEuclidean() { 164 | r := rand.New(rand.NewSource(7)) 165 | g, pos, wt, err := graph.LabeledEuclidean(4, 6, 1, 1, r) 166 | if err != nil { 167 | fmt.Println(err) 168 | return 169 | } 170 | fmt.Println(g.Order(), "nodes") 171 | fmt.Println("n position") 172 | for n, p := range pos { 173 | fmt.Printf("%d (%.2f, %.2f)\n", n, p.X, p.Y) 174 | } 175 | fmt.Println(g.ArcSize(), "arcs:") 176 | for n, to := range g.LabeledAdjacencyList { 177 | fmt.Println(n, "->", to) 178 | } 179 | fmt.Println("arc label weight") 180 | for n, to := range g.LabeledAdjacencyList { 181 | for _, to := range to { 182 | fmt.Printf("%d->%d %d %.2f\n", 183 | n, to.To, to.Label, wt[to.Label]) 184 | } 185 | } 186 | // Random output: 187 | // 4 nodes 188 | // n position 189 | // 0 (0.92, 0.23) 190 | // 1 (0.24, 0.91) 191 | // 2 (0.70, 0.15) 192 | // 3 (0.35, 0.34) 193 | // 6 arcs: 194 | // 0 -> [{2 1} {1 5}] 195 | // 1 -> [{0 4}] 196 | // 2 -> [{3 0} {0 2}] 197 | // 3 -> [{1 3}] 198 | // arc label weight 199 | // 0->2 1 0.24 200 | // 0->1 5 0.96 201 | // 1->0 4 0.96 202 | // 2->3 0 0.40 203 | // 2->0 2 0.24 204 | // 3->1 3 0.58 205 | } 206 | 207 | func TestEuclidean(t *testing.T) { 208 | var g graph.Directed 209 | var err error 210 | for { 211 | if g, _, err = graph.Euclidean(10, 30, 2, 10, nil); err == nil { 212 | break 213 | } 214 | } 215 | if s, n := g.IsSimple(); !s { 216 | t.Fatalf("Euclidean returned non-simple graph. Node %d to: %v", 217 | n, g.AdjacencyList[n]) 218 | } 219 | } 220 | 221 | func TestKroneckerDir(t *testing.T) { 222 | g, _ := graph.KroneckerDirected(10, 10, nil) 223 | if s, n := g.IsSimple(); !s { 224 | t.Fatalf("KroneckerDir returned non-simple graph. Node %d to: %v", 225 | n, g.AdjacencyList[n]) 226 | } 227 | } 228 | 229 | func TestKroneckerUndir(t *testing.T) { 230 | g, _ := graph.KroneckerUndirected(10, 10, nil) 231 | if s, n := g.IsSimple(); !s { 232 | t.Fatalf("KroneckerUndir returned non-simple graph. Node %d to: %v", 233 | n, g.AdjacencyList[n]) 234 | } 235 | if u, from, to := g.IsUndirected(); !u { 236 | t.Fatalf("KroneckerUndir returned directed graph. "+ 237 | "Arc %d->%d has no reciprocal.", from, to) 238 | } 239 | } 240 | 241 | func TestGnmUndirected(t *testing.T) { 242 | u := graph.GnmUndirected(15, 21, nil) 243 | if ok, _, _ := u.IsUndirected(); !ok { 244 | t.Fatal("GnmUndirected returned directed graph") 245 | } 246 | if ok, _ := u.IsSimple(); !ok { 247 | t.Fatal("GnmUndirected returned non-simple graph") 248 | } 249 | 250 | u = graph.GnmUndirected(15, 84, nil) 251 | /* 252 | rand.New(rand.NewSource(time.Now().UnixNano()))) 253 | for fr, to := range u.AdjacencyList { 254 | t.Log(fr, to) 255 | } 256 | t.Log("order, size: ", u.Order(), u.Size()) 257 | t.Log("density: ", u.Density()) 258 | */ 259 | if ok, _, _ := u.IsUndirected(); !ok { 260 | t.Fatal("GnmUndirected returned directed graph") 261 | } 262 | if ok, _ := u.IsSimple(); !ok { 263 | t.Fatal("GnmUndirected returned non-simple graph") 264 | } 265 | } 266 | 267 | func TestGnmDirected(t *testing.T) { 268 | d := graph.GnmDirected(15, 189, nil) 269 | if ok, _ := d.IsSimple(); !ok { 270 | t.Fatal("GnmDirected returned non-simple graph") 271 | } 272 | d = graph.GnmDirected(15, 21, nil) 273 | if ok, _ := d.IsSimple(); !ok { 274 | t.Fatal("GnmDirected returned non-simple graph") 275 | } 276 | } 277 | 278 | func TestGnm3Directed(t *testing.T) { 279 | d := graph.Gnm3Directed(15, 42, nil) 280 | if ok, _ := d.IsSimple(); !ok { 281 | t.Fatal("Gnm3Directed returned non-simple graph") 282 | } 283 | } 284 | 285 | func TestGnm3Undirected(t *testing.T) { 286 | u := graph.Gnm3Undirected(15, 21, nil) 287 | if ok, _, _ := u.IsUndirected(); !ok { 288 | t.Fatal("Gnm3Undirected returned directed graph") 289 | } 290 | if ok, _ := u.IsSimple(); !ok { 291 | t.Fatal("Gnm3Undirected returned non-simple graph") 292 | } 293 | } 294 | 295 | func TestGnpUndirected(t *testing.T) { 296 | u, _ := graph.GnpUndirected(15, .4, nil) 297 | if ok, _, _ := u.IsUndirected(); !ok { 298 | t.Fatal("GnpUndirected returned directed graph") 299 | } 300 | if ok, _ := u.IsSimple(); !ok { 301 | t.Fatal("GnpUndirected returned non-simple graph") 302 | } 303 | } 304 | 305 | func TestGnpDirected(t *testing.T) { 306 | u, _ := graph.GnpDirected(15, .4, nil) 307 | if ok, _ := u.IsSimple(); !ok { 308 | t.Fatal("GnpDirected returned non-simple graph") 309 | } 310 | } 311 | 312 | func TestChungLu(t *testing.T) { 313 | w := make([]float64, 15) 314 | for i := range w { 315 | w[i] = (15 - float64(i)) * .8 316 | } 317 | u, _ := graph.ChungLu(w, rand.New(rand.NewSource(time.Now().UnixNano()))) 318 | if ok, _ := u.IsSimple(); !ok { 319 | t.Fatal("ChungLu returned non-simple graph") 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /readme.adoc: -------------------------------------------------------------------------------- 1 | = Graph 2 | 3 | A graph library with goals of speed and simplicity, Graph implements 4 | graph algorithms on graphs of zero-based integer node IDs. 5 | 6 | image:https://godoc.org/github.com/soniakeys/graph?status.svg[link=https://godoc.org/github.com/soniakeys/graph] 7 | image:http://gowalker.org/api/v1/badge[link=https://gowalker.org/github.com/soniakeys/graph] 8 | image:http://go-search.org/badge?id=github.com%2Fsoniakeys%2Fgraph[link=http://go-search.org/view?id=github.com%2Fsoniakeys%2Fgraph] 9 | image:https://travis-ci.org/soniakeys/graph.svg?branch=master[link=https://travis-ci.org/soniakeys/graph] 10 | 11 | The library provides efficient graph representations and many methods on 12 | graph types. It can be imported and used directly in many applications that 13 | require or can benefit from graph algorithms. 14 | 15 | The library should also be considered as library of source code that can serve 16 | as starting material for coding variant or more complex algorithms. 17 | 18 | == Ancillary material of interest 19 | 20 | The directory link:tutorials[tutorials] is a work in progress - there are only 21 | a few tutorials there yet - but the concept is to provide some topical 22 | walk-throughs to supplement godoc. The source-based godoc documentation 23 | remains the primary documentation. 24 | 25 | The directory link:anecdote[anecdote] contains a stand-alone program that 26 | performs single runs of a number of methods, collecting one-off or "anecdotal" 27 | timings. It currently runs only a small fraction of the library methods but 28 | may still be of interest for giving a general idea of how fast some methods 29 | run. 30 | 31 | The directory link:bench[bench] is another work in progress. The concept is 32 | to present some plots showing benchmark performance approaching some 33 | theoretical asymptote. 34 | 35 | link:hacking.adoc[hacking.adoc] has some information about how the library is 36 | developed, built, and tested. It might be of interest if for example you 37 | plan to fork or contribute to the the repository. 38 | 39 | == Test coverage 40 | 1 Jul 2017 41 | .... 42 | graph 93.7% 43 | graph/alt 88.0% 44 | graph/dot 77.7% 45 | graph/treevis 79.4% 46 | .... 47 | 48 | == License 49 | All files in the repository are licensed with the MIT License, 50 | https://opensource.org/licenses/MIT. 51 | -------------------------------------------------------------------------------- /treevis/treevis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | // Treevis draws trees with text. Capabilities are currently quite limited. 5 | package treevis 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "math/big" 11 | "strconv" 12 | 13 | "github.com/soniakeys/graph" 14 | ) 15 | 16 | type G struct { 17 | Leaf string 18 | NonLeaf string 19 | Child string 20 | Vertical string 21 | LastChild string 22 | Indent string 23 | } 24 | 25 | type Config struct { 26 | NodeLabel func(graph.NI) string 27 | Glyphs G 28 | } 29 | 30 | var Defaults = Config{ 31 | NodeLabel: func(n graph.NI) string { return strconv.Itoa(int(n)) }, 32 | Glyphs: G{ 33 | Leaf: "╴", 34 | NonLeaf: "┐", 35 | Child: "├─", 36 | Vertical: "│ ", 37 | LastChild: "└─", 38 | Indent: " ", 39 | }, 40 | } 41 | 42 | type Option func(*Config) 43 | 44 | func NodeLabel(f func(graph.NI) string) Option { 45 | return func(c *Config) { c.NodeLabel = f } 46 | } 47 | 48 | func Glyphs(g G) Option { 49 | return func(c *Config) { c.Glyphs = g } 50 | } 51 | 52 | func Write(g graph.Directed, root graph.NI, w io.Writer, options ...Option) (err error) { 53 | cf := Defaults 54 | for _, o := range options { 55 | o(&cf) 56 | } 57 | var vis big.Int 58 | var f func(graph.NI, string) bool 59 | f = func(n graph.NI, pre string) bool { 60 | if vis.Bit(int(n)) != 0 { 61 | fmt.Fprintln(w, "%!(NONTREE)") 62 | err = fmt.Errorf("non-tree") 63 | return false 64 | } 65 | vis.SetBit(&vis, int(n), 1) 66 | to := g.AdjacencyList[n] 67 | if len(to) == 0 { 68 | _, err = fmt.Fprint(w, cf.Glyphs.Leaf, cf.NodeLabel(n), "\n") 69 | return err == nil 70 | } 71 | _, err = fmt.Fprint(w, cf.Glyphs.NonLeaf, cf.NodeLabel(n), "\n") 72 | if err != nil { 73 | return false 74 | } 75 | last := len(to) - 1 76 | for _, to := range to[:last] { 77 | if _, err = fmt.Fprint(w, pre, cf.Glyphs.Child); err != nil { 78 | return false 79 | } 80 | if !f(to, pre+cf.Glyphs.Vertical) { 81 | return false 82 | } 83 | } 84 | if _, err = fmt.Fprint(w, pre, cf.Glyphs.LastChild); err != nil { 85 | return false 86 | } 87 | return f(to[last], pre+cf.Glyphs.Indent) 88 | } 89 | f(root, "") 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /treevis/treevis_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: http://opensource.org/licenses/MIT 3 | 4 | package treevis_test 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/soniakeys/graph" 10 | "github.com/soniakeys/graph/treevis" 11 | ) 12 | 13 | func ExampleWrite() { 14 | g := graph.Directed{graph.AdjacencyList{ 15 | 0: {1, 2, 3}, 16 | 1: {4, 5}, 17 | 5: {6}, 18 | 6: {}, 19 | }} 20 | treevis.Write(g, 0, os.Stdout) 21 | // Output: 22 | // ┐0 23 | // ├─┐1 24 | // │ ├─╴4 25 | // │ └─┐5 26 | // │ └─╴6 27 | // ├─╴2 28 | // └─╴3 29 | } 30 | 31 | func ExampleGlyphs() { 32 | g := graph.Directed{graph.AdjacencyList{ 33 | 0: {1, 2, 3}, 34 | 1: {4, 5}, 35 | 5: {6}, 36 | 6: {}, 37 | }} 38 | treevis.Write(g, 0, os.Stdout, treevis.Glyphs(treevis.G{ 39 | Leaf: "-", 40 | NonLeaf: "-", 41 | Child: " |", 42 | Vertical: " |", 43 | LastChild: " `", 44 | Indent: " ", 45 | })) 46 | // Output: 47 | // -0 48 | // |-1 49 | // | |-4 50 | // | `-5 51 | // | `-6 52 | // |-2 53 | // `-3 54 | } 55 | 56 | func ExampleNodeLabel() { 57 | g := graph.Directed{graph.AdjacencyList{ 58 | 0: {1, 2, 3}, 59 | 1: {4, 5}, 60 | 5: {6}, 61 | 6: {}, 62 | }} 63 | labels := []string{ 64 | 0: "root", 65 | 1: "a", 66 | 2: "b", 67 | 3: "c", 68 | 4: "d", 69 | 5: "e", 70 | 6: "f", 71 | } 72 | treevis.Write(g, 0, os.Stdout, 73 | treevis.NodeLabel(func(n graph.NI) string { 74 | return " " + labels[n] 75 | })) 76 | // Output: 77 | // ┐ root 78 | // ├─┐ a 79 | // │ ├─╴ d 80 | // │ └─┐ e 81 | // │ └─╴ f 82 | // ├─╴ b 83 | // └─╴ c 84 | } 85 | -------------------------------------------------------------------------------- /tutorials/adjacencylist.adoc: -------------------------------------------------------------------------------- 1 | # AdjacencyList 2 | 3 | This tutorial introduces the AdjacencyList type and related types. It covers 4 | memory representation, how adjacency lists represent directed and undirected 5 | graphs, and how labeled graphs support weighted graph algorithms. 6 | 7 | ## Memory representation 8 | 9 | Here is the graph used in the godoc "single path" example for 10 | AdjacencyList.BreadthFirst. The example program contains comments with the 11 | graph crudely rendered with ASCII symbols but here is a more attractive 12 | version rendered with the http://www.graphviz.org/[Graphviz] dot program. 13 | 14 | image::https://cdn.rawgit.com/soniakeys/graph/svg-v0/tutorials/img/al.svg[] 15 | 16 | Relevant type definitions are 17 | 18 | [source,go] 19 | ---- 20 | type NI int32 21 | type AdjacencyList [][]NI 22 | ---- 23 | 24 | And the graph is defined with this literal, 25 | 26 | [source,go] 27 | ---- 28 | graph.AdjacencyList{ 29 | 2: {1}, 30 | 1: {4}, 31 | 4: {3, 6}, 32 | 3: {5}, 33 | 6: {5, 6}, 34 | } 35 | ---- 36 | 37 | As mentioned in the link:dijkstra.md[Dijkstra tutorial], you can think of NI 38 | standing for "node int" or "node index". The use of a named type helps with 39 | code readability. An AdjacencyList is just a slice of slices. A simplified 40 | memory digram is 41 | 42 | image::https://cdn.rawgit.com/soniakeys/graph/svg-v0/tutorials/img/almem.svg[] 43 | 44 | The top level slice has 7 elements, shown vertically here and numbered with 45 | their slice indexes 0-6. Elements 1-4 and 6 are non-empty slices themselves, shown 46 | horizontally. These slices contain node indexes, NIs. 47 | 48 | A slice interally contains a pointer to its "backing" array. 49 | Elements in the top level slice thus contain pointers *to* arrays of NIs. 50 | Each pointer thus represents a set of graph arcs from one node (implicit in 51 | the index) *to* others (with NIs stored explicitly). 52 | 53 | Because Go slices are zero based an AdjacencyList represents a graph of zero 54 | based node numbers. Actually there's a node 0 in this graph too. A more 55 | accurate diagram would be 56 | 57 | image:https://cdn.rawgit.com/soniakeys/graph/svg-v0/tutorials/img/al0.svg[] 58 | 59 | In some cases a 0 node or other nodes can be ignored. Some godoc examples do 60 | this, especially when 1 based example data is borrowed from some other source. 61 | 62 | == Undirected graphs 63 | 64 | Undirected graphs can be represented with adjacency lists with "reciprocal" 65 | arcs, paired arcs in opposite directions. As a diagram, 66 | 67 | image::https://cdn.rawgit.com/soniakeys/graph/svg-v0/tutorials/img/alpair.svg[] 68 | 69 | As a Go literal, 70 | 71 | [source,go] 72 | ---- 73 | graph.AdjacencyList{ 74 | 0: {1}, 75 | 1: {0}, 76 | } 77 | ---- 78 | 79 | A number of graph algorithms work specifically on undirected graphs. 80 | A distinct type for these is defined as 81 | 82 | [source,go] 83 | ---- 84 | type Undirected struct { 85 | AdjacencyList 86 | } 87 | ---- 88 | 89 | A distinct type provides a place for methods that are specific to undirected graphs. 90 | The technique of https://golang.org/ref/spec#Struct_types[embedding] 91 | allows the Directed type to also include all methods of AdjacencyList. 92 | 93 | An Undirected value can be constructed directly from an AdjacencyList, 94 | for example 95 | 96 | [source,go] 97 | ---- 98 | graph.Undirected{graph.AdjacencyList{ 99 | 0: {1}, 100 | 1: {0}, 101 | }} 102 | ---- 103 | 104 | But note that this does not automatically construct reciprocal arcs for 105 | a directed graph, nor does it validate that the underlying AdjacencyList 106 | contains reciprocal arcs. There are methods for performing these tasks 107 | as needed. 108 | 109 | == Directed graphs 110 | 111 | Directed graphs are defined similarly, 112 | 113 | [source,go] 114 | ---- 115 | type Directed struct { 116 | AdjacencyList 117 | } 118 | ---- 119 | 120 | Methods on the Directed type are generally algorithms that require directed 121 | graphs, specifically graphs where reciprocal arcs are not present. 122 | 123 | The types AdjacencyList, Directed, and Undirected can be easily transformed 124 | by construction or member selection in cases where the data is known to be 125 | compatible with the type. 126 | 127 | To convert a directed graph to an undirected graph, 128 | the method `Directed.Undirected` will create reciprocal arcs. 129 | 130 | == Labeled graphs 131 | 132 | The zero based integer values of node indexes are convenient for associating 133 | arbitrary information with nodes. Simply create any sort of zero based table 134 | of information, indexed as needed to recover node indexes for data values. 135 | This graph library does not compel you to use Go maps or any specific 136 | representation for this. 137 | 138 | To associate data with arcs or edges however, another mechanism is needed. 139 | Each arc in an AdjacencyList is represented by a slice element that contains 140 | an NI. To associate data with one of these NIs, the element type is expanded 141 | from just NI to `Half` with the type definitions, 142 | 143 | [source,go] 144 | ---- 145 | type LI int32 146 | type Half struct { 147 | To NI // node ID, usable as a slice index 148 | Label LI // half-arc ID for application data, often a weight 149 | } 150 | ---- 151 | 152 | It is called Half because it represents a "half arc", a full arc being 153 | something that would explicitly store both end points of the arc. 154 | 155 | LI stands for label integer and can be used for associating arbitrary 156 | information with an arc. Note that unlike an NI, an LI does not correspond 157 | to any index in the graph representation. It does not need to be zero based 158 | like an NI. LIs can be negative and they do not need to be contiguous. They 159 | also do not need to represent unique arc IDs. They can have arbitrary 160 | application dependent meaning. 161 | 162 | The type LabeledAdjacencyList is defined 163 | 164 | [source,go] 165 | ---- 166 | type LabeledAdjacencyList [][]Half 167 | ---- 168 | 169 | The data in the Dijkstra godoc example for example is 170 | 171 | [source,go] 172 | ---- 173 | graph.LabeledAdjacencyList{ 174 | 1: {{To: 2, Label: 7}, {To: 3, Label: 9}, {To: 6, Label: 11}}, 175 | 2: {{To: 3, Label: 10}, {To: 4, Label: 15}}, 176 | 3: {{To: 4, Label: 11}, {To: 6, Label: 2}}, 177 | 4: {{To: 5, Label: 7}}, 178 | 6: {{To: 5, Label: 9}}, 179 | } 180 | ---- 181 | 182 | Or, as a Graphviz formatted diagram, 183 | 184 | image::https://cdn.rawgit.com/soniakeys/graph/svg-v0/tutorials/img/ald.svg[] 185 | 186 | There is a separate type, `LabeledDirected`, for specifically directed 187 | labeled graphs, but the example here uses just a LabeledAdjacencyList. Dijkstra's algorithm 188 | works with adjacency lists representing either directed or undirected graphs, 189 | so methods simply take the LabeledAdjacencyList type. 190 | 191 | Also note that Dijkstra's algorithm requires arcs to be "weighted." The weight 192 | is application data that we must associate with arc labels. For this, Dijkstra 193 | methods take a weight function, defined 194 | 195 | [source,go] 196 | ---- 197 | type WeightFunc func(label LI) (weight float64) 198 | ---- 199 | 200 | to translate labels to application-meaningful weights. The Dijkstra example takes a 201 | short cut at this point by using integer weights that can be stored directly 202 | as label values. The weight function becomes 203 | 204 | [source,go] 205 | ---- 206 | func(label graph.LI) float64 { return float64(label) } 207 | ---- 208 | 209 | This direct encoding of application data is completely appropriate where 210 | application data consist of only a single integer, or where weights can be 211 | restricted to integers. 212 | -------------------------------------------------------------------------------- /tutorials/dijkstra.adoc: -------------------------------------------------------------------------------- 1 | = Dijkstra's Algorithm 2 | == Graph package concepts 3 | Dijkstra's is a favorite for introducing graphs. We'll introduce some concepts 4 | of this graph package by exploring details of the example program in the 5 | documentation. View the package documentation and find the example under 6 | `LabeledAdjacencyList.DijkstraPath` Here's the code reproduced: 7 | 8 | [source,go] 9 | ---- 10 | // arcs are directed right: 11 | // (wt: 11) 12 | // --------------6---- 13 | // / / \ 14 | // / /(2) \(9) 15 | // / (9) / \ 16 | // 1-------------3---- 5 17 | // \ / \ / 18 | // \ (10)/ (11)\ /(7) 19 | // (7)\ / \ / 20 | // ------2-----------4 21 | // (15) 22 | g := graph.LabeledAdjacencyList{ 23 | 1: {{To: 2, Label: 7}, {To: 3, Label: 9}, {To: 6, Label: 11}}, 24 | 2: {{To: 3, Label: 10}, {To: 4, Label: 15}}, 25 | 3: {{To: 4, Label: 11}, {To: 6, Label: 2}}, 26 | 4: {{To: 5, Label: 7}}, 27 | 6: {{To: 5, Label: 9}}, 28 | } 29 | w := func(label graph.LI) float64 { return float64(label) } 30 | p, d := g.DijkstraPath(1, 5, w) 31 | fmt.Println("Shortest path:", p) 32 | fmt.Println("Path distance:", d) 33 | ---- 34 | 35 | The example graph is taken from the Wikipedia page 36 | https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm[Dijkstra's algorithm] 37 | but with some weights changed. This tutorial will not cover the way the 38 | algorithm works. For that the Wikipedia page gives a good introduction. 39 | 40 | === The graph literal 41 | That's the construction `graph.LabeledAdjacencyList{`... To understand it, 42 | read the documentation on LabeledAdjacencyList and chase down the type 43 | definitions, 44 | 45 | [source,go] 46 | ---- 47 | type LabeledAdjacencyList [][]Half 48 | type Half struct { 49 | To NI // node ID, usable as a slice index 50 | Label LI // half-arc ID for application data, often a weight 51 | } 52 | type NI int32 53 | type LI int32 54 | ---- 55 | 56 | You can think of NI standing for "node int" or "node index", LI for 57 | "label int." Half is short for half-arc, half because the struct does not 58 | include the "from" node, but see that it's just a struct of two integers. 59 | LabeledAdjacencyList then is a slice of slices of these structs. That's it -- 60 | the graph representation is fundamentally just a bunch of integers. This 61 | implements an https://en.wikipedia.org/wiki/Adjacency_list[adjacency list] 62 | representation. 63 | 64 | Is the `:` syntax strange to you? Review the Go language spec on 65 | https://golang.org/ref/spec#Composite_literals[composite literals] 66 | and look for "KeyedElement." These are mostly used in map literals 67 | but can be very convenient for slice literals as well. Here the "KeyedElement" 68 | is our "from node." The line `3: {{To: 4, Label: 11}, {To: 6, Label: 2}},` 69 | represents two arcs in our graph, one going from node 3 to node 4 and another 70 | going from node 3 to node 6. 71 | 72 | === The weight function 73 | "Weighted graphs" are kind of a thing and many graph libraries have data 74 | structures that directly represent weights. This graph package though 75 | abstracts them a bit. None of type definitions shown above directly define a 76 | weight. Instead, `Half` defines a "label" that can be used to index or encode 77 | arbitrary information. Dijkstra's algorithm needs weights though, so the 78 | function signature for `DijkstraPath` has a `WeightFunc` argument. See these 79 | definitions in the doc, but Weight func is, 80 | 81 | [source,go] 82 | ---- 83 | type WeightFunc func(label LI) (weight float64) 84 | ---- 85 | 86 | It's just what we need to turn the label of the graph representation into the 87 | weight needed by Dijkstra's algorithm. You, as programmer, write the weight 88 | function according to however weights are stored. This could involve a table 89 | lookup of some sort but in the simplest cases you can just store the weight 90 | directly as the label. That's what we do here. All we need is a simple type 91 | conversion from the LI integer to float64: 92 | 93 | [source,go] 94 | ---- 95 | w := func(label graph.LI) float64 { return float64(label) } 96 | ---- 97 | 98 | 99 | === Method call 100 | With a graph and a weight function, we're ready to call `DijkstraPath`. 101 | We chose to find a shortest path starting at node 1 and ending at 5, and get 102 | back two interesting results, the path [1 6 5] and the distance 20. 103 | -------------------------------------------------------------------------------- /tutorials/img/al.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | // +build ignore 5 | 6 | // go generate in this directory generates images for the tutorials. 7 | 8 | //go:generate go run al.go 9 | //go:generate dia -t svg -e almem.svg almem.dia 10 | 11 | package main 12 | 13 | import ( 14 | "log" 15 | "os/exec" 16 | 17 | "github.com/soniakeys/graph" 18 | "github.com/soniakeys/graph/dot" 19 | ) 20 | 21 | func main() { 22 | for _, f := range []func() error{al, al0, alpair, ald} { 23 | if err := f(); err != nil { 24 | log.Fatal(err) 25 | } 26 | } 27 | } 28 | 29 | func al() error { 30 | g := graph.AdjacencyList{ 31 | 2: {1}, 32 | 1: {4}, 33 | 4: {3, 6}, 34 | 3: {5}, 35 | 6: {5, 6}, 36 | } 37 | c := exec.Command("dot", "-Tsvg", "-o", "al.svg") 38 | w, err := c.StdinPipe() 39 | if err != nil { 40 | return err 41 | } 42 | c.Start() 43 | dot.Write(g, w, dot.GraphAttr("rankdir", "LR")) 44 | w.Close() 45 | return c.Wait() 46 | } 47 | 48 | func al0() error { 49 | g := graph.AdjacencyList{ 50 | 2: {1}, 51 | 1: {4}, 52 | 4: {3, 6}, 53 | 3: {5}, 54 | 6: {5, 6}, 55 | } 56 | // al0 output file name 57 | c := exec.Command("dot", "-Tsvg", "-o", "al0.svg") 58 | w, err := c.StdinPipe() 59 | if err != nil { 60 | return err 61 | } 62 | c.Start() 63 | // otherwise, only difference from al() is dot.Isolated 64 | dot.Write(g, w, dot.GraphAttr("rankdir", "LR"), dot.Isolated(true)) 65 | w.Close() 66 | return c.Wait() 67 | } 68 | 69 | func alpair() error { 70 | g := graph.AdjacencyList{ 71 | 0: {1}, 72 | 1: {0}, 73 | } 74 | c := exec.Command("dot", "-Tsvg", "-o", "alpair.svg") 75 | w, err := c.StdinPipe() 76 | if err != nil { 77 | return err 78 | } 79 | c.Start() 80 | dot.Write(g, w, dot.GraphAttr("rankdir", "LR")) 81 | w.Close() 82 | return c.Wait() 83 | } 84 | 85 | func ald() error { 86 | g := graph.LabeledAdjacencyList{ 87 | 1: {{To: 2, Label: 7}, {To: 3, Label: 9}, {To: 6, Label: 11}}, 88 | 2: {{To: 3, Label: 10}, {To: 4, Label: 15}}, 89 | 3: {{To: 4, Label: 11}, {To: 6, Label: 2}}, 90 | 4: {{To: 5, Label: 7}}, 91 | 6: {{To: 5, Label: 9}}, 92 | } 93 | c := exec.Command("dot", "-Tsvg", "-o", "ald.svg") 94 | w, err := c.StdinPipe() 95 | if err != nil { 96 | return err 97 | } 98 | c.Start() 99 | dot.Write(g, w, dot.GraphAttr("rankdir", "LR")) 100 | w.Close() 101 | return c.Wait() 102 | } 103 | -------------------------------------------------------------------------------- /tutorials/img/al.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | %3 11 | 12 | 13 | 1 14 | 15 | 1 16 | 17 | 18 | 4 19 | 20 | 4 21 | 22 | 23 | 1->4 24 | 25 | 26 | 27 | 28 | 3 29 | 30 | 3 31 | 32 | 33 | 4->3 34 | 35 | 36 | 37 | 38 | 6 39 | 40 | 6 41 | 42 | 43 | 4->6 44 | 45 | 46 | 47 | 48 | 2 49 | 50 | 2 51 | 52 | 53 | 2->1 54 | 55 | 56 | 57 | 58 | 5 59 | 60 | 5 61 | 62 | 63 | 3->5 64 | 65 | 66 | 67 | 68 | 6->5 69 | 70 | 71 | 72 | 73 | 6->6 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /tutorials/img/al0.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | %3 11 | 12 | 13 | 0 14 | 15 | 0 16 | 17 | 18 | 1 19 | 20 | 1 21 | 22 | 23 | 4 24 | 25 | 4 26 | 27 | 28 | 1->4 29 | 30 | 31 | 32 | 33 | 3 34 | 35 | 3 36 | 37 | 38 | 4->3 39 | 40 | 41 | 42 | 43 | 6 44 | 45 | 6 46 | 47 | 48 | 4->6 49 | 50 | 51 | 52 | 53 | 2 54 | 55 | 2 56 | 57 | 58 | 2->1 59 | 60 | 61 | 62 | 63 | 5 64 | 65 | 5 66 | 67 | 68 | 3->5 69 | 70 | 71 | 72 | 73 | 6->5 74 | 75 | 76 | 77 | 78 | 6->6 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /tutorials/img/ald.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | %3 11 | 12 | 13 | 1 14 | 15 | 1 16 | 17 | 18 | 2 19 | 20 | 2 21 | 22 | 23 | 1->2 24 | 25 | 26 | 7 27 | 28 | 29 | 3 30 | 31 | 3 32 | 33 | 34 | 1->3 35 | 36 | 37 | 9 38 | 39 | 40 | 6 41 | 42 | 6 43 | 44 | 45 | 1->6 46 | 47 | 48 | 11 49 | 50 | 51 | 2->3 52 | 53 | 54 | 10 55 | 56 | 57 | 4 58 | 59 | 4 60 | 61 | 62 | 2->4 63 | 64 | 65 | 15 66 | 67 | 68 | 3->6 69 | 70 | 71 | 2 72 | 73 | 74 | 3->4 75 | 76 | 77 | 11 78 | 79 | 80 | 5 81 | 82 | 5 83 | 84 | 85 | 6->5 86 | 87 | 88 | 9 89 | 90 | 91 | 4->5 92 | 93 | 94 | 7 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /tutorials/img/almem.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soniakeys/graph/7b5d1f6e4fe06231cf5c9a55229321b73e167913/tutorials/img/almem.dia -------------------------------------------------------------------------------- /tutorials/img/almem.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 10 | 11 | 12 | 13 | 14 | 15 | 16 | 1 17 | 18 | 19 | 20 | 21 | 22 | 23 | 2 24 | 25 | 26 | 27 | 28 | 29 | 30 | 3 31 | 32 | 33 | 34 | 35 | 36 | 37 | 4 38 | 39 | 40 | 41 | 42 | 43 | 44 | 5 45 | 46 | 47 | 48 | 49 | 50 | 51 | 6 52 | 53 | 54 | nil 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 4 89 | 90 | 91 | 1 92 | 93 | 94 | 5 95 | 96 | 97 | 5 98 | 99 | 100 | 3 101 | 102 | 103 | 6 104 | 105 | 106 | 6 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | nil 135 | 136 | 137 | -------------------------------------------------------------------------------- /tutorials/img/alpair.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | %3 11 | 12 | 13 | 0 14 | 15 | 0 16 | 17 | 18 | 1 19 | 20 | 1 21 | 22 | 23 | 0->1 24 | 25 | 26 | 27 | 28 | 1->0 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tutorials/img/eulerian/eu.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Sonia Keys 2 | // License MIT: https://opensource.org/licenses/MIT 3 | 4 | // +build ignore 5 | 6 | // go generate in this directory generates images for the tutorials. 7 | 8 | //go:generate go run eu.go 9 | 10 | package main 11 | 12 | import ( 13 | "log" 14 | "os/exec" 15 | 16 | "github.com/soniakeys/graph" 17 | "github.com/soniakeys/graph/dot" 18 | ) 19 | 20 | func main() { 21 | for _, f := range []func() error{cycle, cycleOrder} { 22 | if err := f(); err != nil { 23 | log.Fatal(err) 24 | } 25 | } 26 | } 27 | 28 | func cycle() error { 29 | g := graph.LabeledDirected{graph.LabeledAdjacencyList{ 30 | 0: {{1, 'a'}}, 31 | 1: {{3, 'b'}, {2, 'e'}}, 32 | 2: {{1, 'd'}, {3, 'f'}}, 33 | 3: {{2, 'c'}, {0, 'g'}}, 34 | }} 35 | // g := graph.Directed{graph.AdjacencyList{ 36 | // 0: {1}, 37 | // 1: {3, 2}, 38 | // 2: {1, 3}, 39 | // 3: {2, 0}, 40 | // }} 41 | c := exec.Command("dot", "-Tsvg", "-o", "cycle.svg") 42 | w, err := c.StdinPipe() 43 | if err != nil { 44 | return err 45 | } 46 | c.Start() 47 | log.Print(dot.String(g, 48 | dot.GraphAttr("rankdir", "LR"), 49 | dot.EdgeLabel(func(l graph.LI) string { return "" }), 50 | dot.EdgeAttr(func(l graph.LI) []dot.AttrVal { 51 | return []dot.AttrVal{{"arrowhead", "none"}} 52 | }))) 53 | dot.Write(g, w, 54 | dot.GraphAttr("rankdir", "LR"), 55 | dot.EdgeLabel(func(l graph.LI) string { return "" }), 56 | dot.EdgeAttr(func(l graph.LI) []dot.AttrVal { 57 | return []dot.AttrVal{{"arrowhead", "none"}} 58 | })) 59 | w.Close() 60 | return c.Wait() 61 | } 62 | 63 | func cycleOrder() error { 64 | g := graph.LabeledDirected{graph.LabeledAdjacencyList{ 65 | 0: {{1, 'a'}}, 66 | 1: {{3, 'b'}, {2, 'e'}}, 67 | 2: {{1, 'd'}, {3, 'f'}}, 68 | 3: {{2, 'c'}, {0, 'g'}}, 69 | }} 70 | c := exec.Command("dot", "-Tsvg", "-o", "cycleOrder.svg") 71 | w, err := c.StdinPipe() 72 | if err != nil { 73 | return err 74 | } 75 | c.Start() 76 | dot.Write(g, w, 77 | dot.GraphAttr("rankdir", "LR"), 78 | dot.GraphAttr("start", "2"), 79 | dot.EdgeLabel(func(l graph.LI) string { return string(l) })) 80 | w.Close() 81 | return c.Wait() 82 | } 83 | -------------------------------------------------------------------------------- /tutorials/missingmethods.adoc: -------------------------------------------------------------------------------- 1 | = Missing Methods 2 | 3 | A number of methods you might expect are deliberately not implemented. 4 | In general, if something is directly available in the data representation 5 | or if the required code is trivial, you are expected to just write the code 6 | directly in you Go application. This may sound harsh and unfriendly but 7 | the expectation is that you have some minimal competence in Go. 8 | 9 | Most significantly you should understand Go slices. This library implements 10 | the basic graph types with slices and you are encouraged to operate directly 11 | on the underlying slices. 12 | 13 | The following lists functions and methods you might expect but are deliberately 14 | not implemented in the library. 15 | 16 | == Constructors, like NewAdjacencyList 17 | An AdjacencyList is just a slice. Use make. To make a graph with 10 nodes 18 | use `make(AdjacenclyList, 10)`. For a Directed, 19 | `graph.Directed{make(graph.AdjacenclyList, 10)}`. 20 | 21 | == HasNode 22 | The set of nodes in AdjacencyList g is simply the range of valid slice indexes 23 | for g. For a non-negative NI n, check if the graph already includes it with 24 | `int(n) < len(g)`. 25 | 26 | == ArcsFrom, successors 27 | For AdjacencyList g, the list of "successor" nodes, or nodes you can reach 28 | following arcs from node n is `g[n]`. It's a slice. g is a slice of slices. 29 | 30 | Note that if you want a *copy* of this list to modify without disturbing 31 | the graph, you copy it just like any other slice. If you like one-liners 32 | there's `append([]NI{}, g[n]...)`. Otherwise, 33 | 34 | [source,go] 35 | ---- 36 | c := make([]NI, len(g[n])) 37 | copy(c, g[n]) 38 | ---- 39 | 40 | For Undirected u, "neighbors" or nodes at edges from n are 41 | `u.AdjacencyList[n]`. 42 | 43 | == Unique neighbors 44 | You might consider "neighbors" to be _unique_ neighbor nodes _distinct from n_. 45 | In more complex cases where loops or parallel edges can be present, you'll 46 | have to handle these as approriate in your code. If you wanted to collect this 47 | set of unique neighbors for example you might use a map and write 48 | 49 | [source,go] 50 | ---- 51 | nb := map[NI]bool 52 | for _, to := range g[n] { 53 | if to != n { 54 | nb[to] = true 55 | } 56 | } 57 | ---- 58 | 59 | This is pretty basic logic for collecting a set and this library presumes you 60 | can write code like this as you need it. Before you cut and paste this code 61 | though, is it really best for you? Do you really have both loops and parallel 62 | edges to skip? Do you really need to skip them? Do you really need to collect 63 | them in a data structure in memory or do you simply need to iterate over them? 64 | Is a map even the best data structure for you? The library can't guess at 65 | these and provide a capability that handles all cases efficiently or even 66 | simply. It's simpler for you to write a few lines of code. 67 | 68 | == OutDegree 69 | For node n of Adjacencylist g, the out degree is `len(g[n])`. Thus for 70 | Directed d, it's `len(d.AdjacencyList[n])`. 71 | 72 | == AdjacencyList.InDegree 73 | `InDegree` is defined on Directed but not AdjacencyList. This is to keep it 74 | out of the method set for Undirected where the term "in-degree" is not 75 | meaningful. If you have only AdjacencyList g, compute in-degrees with 76 | `Directed{g}.InDegree()`. 77 | 78 | == AdjacencyList.IsDirected 79 | Many graph libraries have a common type for both directed and undirected 80 | and have some flag or property to tell which. This library has separate 81 | types, Directed and Undirected, to convey this information. The underlying 82 | AdjacencyList representation does not have this information. It fine to work 83 | with AdjacencyLists, but in this case, you should know if your data is 84 | directed graph or not. If you have an AdjacenclyList that just arrived from 85 | somewhere and you don't know how to treat it, you're doing something wrong. 86 | 87 | There is `AdjacencyList.IsUndirected`, but this is best seen as as a validation 88 | method to validate that data expected to represent an undirected graph is 89 | well formed. It is meaningful for example to have a directed graph that just 90 | happens to have reciprocals for all arcs. 91 | 92 | == AddNode 93 | To grow AdjacencyList g by one node, the next available node number will be 94 | `graph.NI(len(g))`. Add it with `g = append(g, nil)`. The implementation 95 | of append makes it efficient to do this repeatedly. 96 | 97 | == AddArc 98 | To add arc from `fr` to `to` in AdjacencyList g, 99 | 100 | 1. know that int(fr) < len(g). 101 | 2. `g[fr] = append(g[fr], to)` 102 | 103 | There's Undirected.AddEdge but no AdjacencyList.AddArc. AddEdge exists with 104 | reservation. The doc on it notes that it may have overhead of resizing and 105 | suggests preallocating. Still, it's useful and the code is complex enough to 106 | seem justified. A similar AddArc could be written but seems overkill. 107 | In the two steps above, you will often know the first without having to do 108 | any checks. Then it's just append. 109 | 110 | == Arcs, to get all arcs, like Undirected.Edges 111 | The Adjacency list is compact and easy to traverse. For example, 112 | 113 | [source,go] 114 | ---- 115 | for fr, toList := range g { 116 | for _, to := range toList { 117 | fmt.Println("arc:", fr, to) 118 | // or you know, collect the arcs in some other data structure 119 | } 120 | } 121 | ---- 122 | 123 | Undirected.Edges is not quite so trivial because reciprocal arcs must be 124 | matched up so this method is provided. 125 | 126 | == Undirected.ParallelEdges, like AdjacencyList.ParallelArcs 127 | ParallelArcs will find parallel edges, it just doesn't find the neighbor 128 | indexes of the reciprocals, which wouldn't be needed in many cases. 129 | 130 | == LabeledUndirected.SimpleEdges 131 | It's not clear what would be done with parallel edges with different labels. 132 | The action would be specific to the use-case. 133 | 134 | == Undirected.ArcsAsEdges 135 | The labeled version of this method is kind of specialized helper for Kruskal. 136 | A use case for an unlabeled version is hard to imagine. 137 | -------------------------------------------------------------------------------- /tutorials/readme.adoc: -------------------------------------------------------------------------------- 1 | = Tutorials 2 | 3 | Documents in this directory show usage patterns and explain concepts in more 4 | detail than the primary documentation. The primary documentation though 5 | remains the Go doc format documentation within the Go source files. 6 | 7 | They can be read in any order. A suggested order might be 8 | 9 | * link:dijkstra.adoc[Dijkstra's algorithm] (a quick start) 10 | * link:adjacencylist.adoc[AdjacencyList types] (more explanation 11 | about this library's most fundamental data type. 12 | * link:missingmethods.adoc[Missing methods] (looking for something?) 13 | --------------------------------------------------------------------------------