cmd/go2go: add pointer variant to graph package

Change-Id: Ib5cf28be42943ea816c64740f5698a5716d3dec1
This commit is contained in:
Ian Lance Taylor 2020-04-02 13:34:20 -07:00 committed by Robert Griesemer
parent 07382406ea
commit fad64b3083
2 changed files with 161 additions and 32 deletions

View File

@ -27,6 +27,9 @@ func New(type Node, Edge G)(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(type Node, Edge G) struct {
node Node
path []Edge
@ -63,3 +66,61 @@ func (g *Graph(Node, Edge)) ShortestPath(from, to Node) ([]Edge, error) {
}
return nil, errors.New("no path")
}
// GraphP is a version of Grgaph that uses pointers. This is for testing.
// I'm not sure which approach will be better in practice, or whether
// this indicates a problem with the draft design.
type GraphP(type Node, Edge GP) struct {
nodes []*Node
}
// GP is the contract that the GraphP Node and Edge types must implement.
contract GP(Node, Edge) {
Node Edges() []*Edge
Edge Nodes() (a, b *Node)
}
// NewP creates a new GraphP from a collection of Nodes.
func NewP(type Node, Edge GP)(nodes []*Node) *GraphP(Node, Edge) {
return &GraphP(Node, Edge){nodes: nodes}
}
// nodePathP 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 nodePathP(type Node, Edge GP) 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 *GraphP(Node, Edge)) ShortestPath(from, to *Node) ([]*Edge, error) {
visited := make(map[*Node]bool)
visited[from] = true
workqueue := [](nodePathP(Node, Edge)){nodePathP(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, nodePathP(Node, Edge){a, ve})
visited[a] = true
}
}
}
}
return nil, errors.New("no path")
}

View File

@ -27,17 +27,17 @@ const (
)
func (dir direction) String() string {
strs := map[direction]string {
strs := map[direction]string{
north: "north",
ne: "ne",
east: "east",
se: "se",
ne: "ne",
east: "east",
se: "se",
south: "south",
sw: "sw",
west: "west",
nw: "nw",
up: "up",
down: "down",
sw: "sw",
west: "west",
nw: "nw",
up: "up",
down: "down",
}
if str, ok := strs[dir]; ok {
return str
@ -52,7 +52,7 @@ type mazeRoom struct {
type mazeEdge struct {
from, to int
dir direction
dir direction
}
// Edges returns the exits from the room.
@ -62,8 +62,8 @@ func (m mazeRoom) Edges() []mazeEdge {
if exit != 0 {
r = append(r, mazeEdge{
from: m.index,
to: exit,
dir: direction(i),
to: exit,
dir: direction(i),
})
}
}
@ -86,26 +86,26 @@ func (e mazeEdge) Nodes() (mazeRoom, mazeRoom) {
// 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
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(t *testing.T) {
@ -141,3 +141,71 @@ func TestShortestPath(t *testing.T) {
t.Errorf("ShortestPath returned %v, want %v", steps, want)
}
}
// A version of the above using pointer types.
var zorkP = make(map[int]*mazeRoomP)
type mazeRoomP struct {
index int
exits [10]int
}
type mazeEdgeP struct {
from, to int
dir direction
}
func (m *mazeRoomP) Edges() []*mazeEdgeP {
var r []*mazeEdgeP
for i, exit := range m.exits {
if exit != 0 {
r = append(r, &mazeEdgeP{
from: m.index,
to: exit,
dir: direction(i),
})
}
}
return r
}
func (e *mazeEdgeP) Nodes() (*mazeRoomP, *mazeRoomP) {
m1, ok := zorkP[e.from]
if !ok {
panic("bad edge")
}
m2, ok := zorkP[e.to]
if !ok {
panic("bad edge")
}
return m1, m2
}
func TestShortestPathP(t *testing.T) {
var nodes []*mazeRoomP
var start, stop *mazeRoomP
for k, v := range zork {
mr := &mazeRoomP{index: k, exits: v.exits}
zorkP[k] = mr
nodes = append(nodes, mr)
if k == 11 {
start = mr
} else if k == 30 {
stop = mr
}
}
g := NewP(mazeRoomP, mazeEdgeP)(nodes)
path, err := g.ShortestPath(start, stop)
if err != nil {
t.Fatal(err)
}
var steps []direction
for _, edge := range path {
steps = append(steps, edge.dir)
}
want := []direction{east, west, up, sw, east, south}
if !slices.Equal(steps, want) {
t.Errorf("ShortestPath returned %v, want %v", steps, want)
}
}