| // run |
| |
| // Copyright 2021 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "errors" |
| "fmt" |
| ) |
| |
| // _Equal reports whether two slices are equal: the same length and all |
| // elements equal. All floating point NaNs are considered equal. |
| func _SliceEqual[Elem comparable](s1, s2 []Elem) bool { |
| if len(s1) != len(s2) { |
| return false |
| } |
| for i, v1 := range s1 { |
| v2 := s2[i] |
| if v1 != v2 { |
| isNaN := func(f Elem) bool { return f != f } |
| if !isNaN(v1) || !isNaN(v2) { |
| return false |
| } |
| } |
| } |
| return true |
| } |
| |
| // A Graph is a collection of nodes. A node may have an arbitrary number |
| // of edges. An edge connects two nodes. Both nodes and edges must be |
| // comparable. This is an undirected simple graph. |
| type _Graph[_Node _NodeC[_Edge], _Edge _EdgeC[_Node]] struct { |
| nodes []_Node |
| } |
| |
| // _NodeC is the constraints on a node in a graph, given the _Edge type. |
| type _NodeC[_Edge any] interface { |
| comparable |
| Edges() []_Edge |
| } |
| |
| // Edgec is the constraints on an edge in a graph, given the _Node type. |
| type _EdgeC[_Node any] interface { |
| comparable |
| Nodes() (a, b _Node) |
| } |
| |
| // _New creates a new _Graph from a collection of Nodes. |
| func _New[_Node _NodeC[_Edge], _Edge _EdgeC[_Node]](nodes []_Node) *_Graph[_Node, _Edge] { |
| return &_Graph[_Node, _Edge]{nodes: nodes} |
| } |
| |
| // nodePath holds the path to a node during ShortestPath. |
| // This should ideally be a type defined inside ShortestPath, |
| // but the translator tool doesn't support that. |
| type nodePath[_Node _NodeC[_Edge], _Edge _EdgeC[_Node]] struct { |
| node _Node |
| path []_Edge |
| } |
| |
| // ShortestPath returns the shortest path between two nodes, |
| // as an ordered list of edges. If there are multiple shortest paths, |
| // which one is returned is unpredictable. |
| func (g *_Graph[_Node, _Edge]) ShortestPath(from, to _Node) ([]_Edge, error) { |
| visited := make(map[_Node]bool) |
| visited[from] = true |
| workqueue := []nodePath[_Node, _Edge]{nodePath[_Node, _Edge]{from, nil}} |
| for len(workqueue) > 0 { |
| current := workqueue |
| workqueue = nil |
| for _, np := range current { |
| edges := np.node.Edges() |
| for _, edge := range edges { |
| a, b := edge.Nodes() |
| if a == np.node { |
| a = b |
| } |
| if !visited[a] { |
| ve := append([]_Edge(nil), np.path...) |
| ve = append(ve, edge) |
| if a == to { |
| return ve, nil |
| } |
| workqueue = append(workqueue, nodePath[_Node, _Edge]{a, ve}) |
| visited[a] = true |
| } |
| } |
| } |
| } |
| return nil, errors.New("no path") |
| } |
| |
| type direction int |
| |
| const ( |
| north direction = iota |
| ne |
| east |
| se |
| south |
| sw |
| west |
| nw |
| up |
| down |
| ) |
| |
| func (dir direction) String() string { |
| strs := map[direction]string{ |
| north: "north", |
| ne: "ne", |
| east: "east", |
| se: "se", |
| south: "south", |
| sw: "sw", |
| west: "west", |
| nw: "nw", |
| up: "up", |
| down: "down", |
| } |
| if str, ok := strs[dir]; ok { |
| return str |
| } |
| return fmt.Sprintf("direction %d", dir) |
| } |
| |
| type mazeRoom struct { |
| index int |
| exits [10]int |
| } |
| |
| type mazeEdge struct { |
| from, to int |
| dir direction |
| } |
| |
| // Edges returns the exits from the room. |
| func (m mazeRoom) Edges() []mazeEdge { |
| var r []mazeEdge |
| for i, exit := range m.exits { |
| if exit != 0 { |
| r = append(r, mazeEdge{ |
| from: m.index, |
| to: exit, |
| dir: direction(i), |
| }) |
| } |
| } |
| return r |
| } |
| |
| // Nodes returns the rooms connected by an edge. |
| //go:noinline |
| func (e mazeEdge) Nodes() (mazeRoom, mazeRoom) { |
| m1, ok := zork[e.from] |
| if !ok { |
| panic("bad edge") |
| } |
| m2, ok := zork[e.to] |
| if !ok { |
| panic("bad edge") |
| } |
| return m1, m2 |
| } |
| |
| // The first maze in Zork. Room indexes based on original Fortran data file. |
| // You are in a maze of twisty little passages, all alike. |
| var zork = map[int]mazeRoom{ |
| 11: {exits: [10]int{north: 11, south: 12, east: 14}}, // west to Troll Room |
| 12: {exits: [10]int{south: 11, north: 14, east: 13}}, |
| 13: {exits: [10]int{west: 12, north: 14, up: 16}}, |
| 14: {exits: [10]int{west: 13, north: 11, east: 15}}, |
| 15: {exits: [10]int{south: 14}}, // Dead End |
| 16: {exits: [10]int{east: 17, north: 13, sw: 18}}, // skeleton, etc. |
| 17: {exits: [10]int{west: 16}}, // Dead End |
| 18: {exits: [10]int{down: 16, east: 19, west: 18, up: 22}}, |
| 19: {exits: [10]int{up: 29, west: 18, ne: 15, east: 20, south: 30}}, |
| 20: {exits: [10]int{ne: 19, west: 20, se: 21}}, |
| 21: {exits: [10]int{north: 20}}, // Dead End |
| 22: {exits: [10]int{north: 18, east: 24, down: 23, south: 28, west: 26, nw: 22}}, |
| 23: {exits: [10]int{east: 22, west: 28, up: 24}}, |
| 24: {exits: [10]int{ne: 25, down: 23, nw: 28, sw: 26}}, |
| 25: {exits: [10]int{sw: 24}}, // Grating room (up to Clearing) |
| 26: {exits: [10]int{west: 16, sw: 24, east: 28, up: 22, north: 27}}, |
| 27: {exits: [10]int{south: 26}}, // Dead End |
| 28: {exits: [10]int{east: 22, down: 26, south: 23, west: 24}}, |
| 29: {exits: [10]int{west: 30, nw: 29, ne: 19, south: 19}}, |
| 30: {exits: [10]int{west: 29, south: 19}}, // ne to Cyclops Room |
| } |
| |
| func TestShortestPath() { |
| // The Zork maze is not a proper undirected simple graph, |
| // as there are some one way paths (e.g., 19 -> 15), |
| // but for this test that doesn't matter. |
| |
| // Set the index field in the map. Simpler than doing it in the |
| // composite literal. |
| for k := range zork { |
| r := zork[k] |
| r.index = k |
| zork[k] = r |
| } |
| |
| var nodes []mazeRoom |
| for idx, room := range zork { |
| mridx := room |
| mridx.index = idx |
| nodes = append(nodes, mridx) |
| } |
| g := _New[mazeRoom, mazeEdge](nodes) |
| path, err := g.ShortestPath(zork[11], zork[30]) |
| if err != nil { |
| panic(fmt.Sprintf("%v", err)) |
| } |
| var steps []direction |
| for _, edge := range path { |
| steps = append(steps, edge.dir) |
| } |
| want := []direction{east, west, up, sw, east, south} |
| if !_SliceEqual(steps, want) { |
| panic(fmt.Sprintf("ShortestPath returned %v, want %v", steps, want)) |
| } |
| } |
| |
| func main() { |
| TestShortestPath() |
| } |