break comments at 100 columns

This commit is contained in:
Sonia Keys
2014-02-21 13:57:27 -05:00
parent 6700e55cec
commit d64152d9a4
10 changed files with 252 additions and 147 deletions

View File

@@ -5,17 +5,18 @@ import (
"math"
)
// A dense graph is a graph such that all IDs are in a contiguous block from 0 to TheNumberOfNodes-1
// it uses an adjacency matrix and should be relatively fast for both access and writing.
// A dense graph is a graph such that all IDs are in a contiguous block from 0 to
// TheNumberOfNodes-1. It uses an adjacency matrix and should be relatively fast for both access
// and writing.
//
// This graph implements the CrunchGraph, but since it's naturally dense this is superfluous
// This graph implements the CrunchGraph, but since it's naturally dense this is superfluous.
type DenseGraph struct {
adjacencyMatrix []float64
numNodes int
}
// Creates a dense graph with the proper number of nodes. If passable is true all nodes will have
// an edge with cost 1.0, otherwise every node will start unconnected (cost of +Inf)
// an edge with cost 1.0, otherwise every node will start unconnected (cost of +Inf.)
func NewDenseGraph(numNodes int, passable bool) *DenseGraph {
dg := &DenseGraph{adjacencyMatrix: make([]float64, numNodes*numNodes), numNodes: numNodes}
if passable {

View File

@@ -27,12 +27,17 @@ func (edge GonumEdge) Tail() gr.Node {
return edge.T
}
// A GonumGraph is a very generalized graph that can handle an arbitrary number of vertices and edges -- as well as act as either directed or undirected.
// A GonumGraph is a very generalized graph that can handle an arbitrary number of vertices and
// edges -- as well as act as either directed or undirected.
//
// Internally, it uses a map of successors AND predecessors, to speed up some operations (such as getting all successors/predecessors). It also speeds up thing like adding edges (assuming both edges exist).
// Internally, it uses a map of successors AND predecessors, to speed up some operations (such as
// getting all successors/predecessors). It also speeds up thing like adding edges (assuming both
// edges exist).
//
// However, its generality is also its weakness (and partially a flaw in needing to satisfy MutableGraph). For most purposes, creating your own graph is probably better. For instance, see discrete.TileGraph for an example
// of an immutable 2D grid of tiles that also implements the Graph interface, but would be more suitable if all you needed was a simple undirected 2D grid.
// However, its generality is also its weakness (and partially a flaw in needing to satisfy
// MutableGraph). For most purposes, creating your own graph is probably better. For instance,
// see TileGraph for an example of an immutable 2D grid of tiles that also implements the Graph
// interface, but would be more suitable if all you needed was a simple undirected 2D grid.
type GonumGraph struct {
successors map[int]map[int]float64
predecessors map[int]map[int]float64
@@ -141,7 +146,8 @@ func (graph *GonumGraph) AddEdge(e gr.Edge) {
func (graph *GonumGraph) SetEdgeCost(e gr.Edge, cost float64) {
id := e.Head().ID()
successor := e.Tail().ID()
// Normally I'd use graph.vertices.Contains(id) as above, but this is equivalent and a bit easier to read here
// Normally I'd use graph.vertices.Contains(id) as above, but this is equivalent and a bit
// easier to read here
if _, ok := graph.successors[id]; !ok {
return
} else if _, ok := graph.successors[id][successor]; !ok {
@@ -150,7 +156,8 @@ func (graph *GonumGraph) SetEdgeCost(e gr.Edge, cost float64) {
graph.successors[id][successor] = cost
graph.predecessors[successor][id] = cost
// By the spec, only the empty graph will be toggled between directed and undirected. Therefore we can be sure the reciprocal edge exists
// By the spec, only the empty graph will be toggled between directed and undirected.
// Therefore we can be sure the reciprocal edge exists
if !graph.directed {
graph.successors[successor][id] = cost
graph.predecessors[id][successor] = cost

34
doc.go
View File

@@ -1,22 +1,34 @@
/*
Package graph implements functions and interfaces to deal with formal discrete graphs. It aims to be first and foremost flexible, with speed as a strong second priority.
Package graph implements functions and interfaces to deal with formal discrete graphs. It aims to
be first and foremost flexible, with speed as a strong second priority.
In this package, graphs are taken to be directed, and undirected graphs are considered to be a special case of directed graphs that happen to have reciprocal edges. Graphs are, by default, unweighted,
but functions that require weighted edges have several methods of dealing with this. In order of precedence:
In this package, graphs are taken to be directed, and undirected graphs are considered to be a
special case of directed graphs that happen to have reciprocal edges. Graphs are, by default,
unweighted, but functions that require weighted edges have several methods of dealing with this.
In order of precedence:
1. These functions have an argument called Cost (and in some cases, HeuristicCost). If this is present, it will always be used to determine the cost between two nodes.
1. These functions have an argument called Cost (and in some cases, HeuristicCost). If this is
present, it will always be used to determine the cost between two nodes.
2. These functions will check if your graph implements the Coster (and/or HeuristicCoster) interface. If this is present, and the Cost (or HeuristicCost) argument is nil, these functions will be used
2. These functions will check if your graph implements the Coster (and/or HeuristicCoster)
interface. If this is present, and the Cost (or HeuristicCost) argument is nil, these functions
will be used.
3. Finally, if no user data is supplied, it will use the functions UniformCost (always returns 1) and/or NulLHeuristic (always returns 0).
3. Finally, if no user data is supplied, it will use the functions UniformCost (always returns 1)
and/or NulLHeuristic (always returns 0).
For information on the specification for Cost functions, please see the Coster interface.
Finally, although the functions take in a Graph -- they will always use the correct behavior. If your graph implements DirectedGraph, it will use Successors and Predecessors where applicable,
if undirected, it will use Neighbors instead. If it implements neither, it will scan the edge list for successors and predecessors where applicable. (This is slow, you should always implement either Directed or Undirected)
Finally, although the functions take in a Graph -- they will always use the correct behavior.
If your graph implements DirectedGraph, it will use Successors and Predecessors where applicable,
if undirected, it will use Neighbors instead. If it implements neither, it will scan the edge list
for successors and predecessors where applicable. (This is slow, you should always implement either
Directed or Undirected)
This package will never modify a graph that is not Mutable (and the interface does not allow it to do so). However, return values are free to be modified, so never pass a reference to your own edge list or node list.
It also guarantees that any nodes passed back to the user will be the same nodes returned to it -- that is, it will never take a Node's ID and then wrap the ID in a new struct and return that. You'll always get back your
original data.
This package will never modify a graph that is not Mutable (and the interface does not allow it to
do so). However, return values are free to be modified, so never pass a reference to your own edge
list or node list. It also guarantees that any nodes passed back to the user will be the same
nodes returned to it -- that is, it will never take a Node's ID and then wrap the ID in a new
struct and return that. You'll always get back your original data.
*/
package graph

View File

@@ -1,13 +1,15 @@
package graph
// All a node needs to do is identify itself. This allows the user to pass in nodes more interesting than an int,
// but also allow us to reap the benefits of having a map-storable, ==able type.
// All a node needs to do is identify itself. This allows the user to pass in nodes more
// interesting than an int, but also allow us to reap the benefits of having a map-storable,
// ==able type.
type Node interface {
ID() int
}
// Allows edges to do something more interesting that just be a group of nodes. While the methods are called Head and Tail,
// they are not considered directed unless the given interface specifies otherwise
// Allows edges to do something more interesting that just be a group of nodes. While the methods
// are called Head and Tail, they are not considered directed unless the given interface specifies
// otherwise.
type Edge interface {
Head() Node
Tail() Node
@@ -15,8 +17,10 @@ type Edge interface {
// A Graph ensures the behavior of an undirected graph, necessary to run certain algorithms on it.
//
// The Graph interface is directed. This means that EdgeList() should return an edge where Head always goes towards Tail. If your graph is undirected and you only maintain edges for one direction,
// simply return two edges for each one of your edges, with the Head and Tail swapped in each one.
// The Graph interface is directed. This means that EdgeList() should return an edge where Head
// always goes towards Tail. If your graph is undirected and you only maintain edges for one
// direction, simply return two edges for each one of your edges, with the Head and Tail swapped
// in each one.
type Graph interface {
// NodeExists returns true when node is currently in the graph.
NodeExists(node Node) bool
@@ -34,10 +38,13 @@ type Graph interface {
IsNeighbor(node, neighbor Node) bool
}
// Directed graphs are characterized by having seperable Heads and Tails in their edges. That is, if node1 goes to node2, that does not necessarily imply that node2 goes to node1.
// Directed graphs are characterized by having seperable Heads and Tails in their edges.
// That is, if node1 goes to node2, that does not necessarily imply that node2 goes to node1.
//
// While it's possible for a directed graph to have fully reciprocal edges (i.e. the graph is symmetric) -- it is not required to be. The graph is also required to implement UndirectedGraph
// because it can be useful to know all neighbors regardless of direction; not because this graph treats directed graphs as special cases of undirected ones (the truth is, in fact, the opposite)
// While it's possible for a directed graph to have fully reciprocal edges (i.e. the graph is
// symmetric) -- it is not required to be. The graph is also required to implement UndirectedGraph
// because it can be useful to know all neighbors regardless of direction; not because this graph
// treats directed graphs as special cases of undirected ones (the truth is, in fact, the opposite.)
type DirectedGraph interface {
Graph
// Successors gives the nodes connected by OUTBOUND edges.
@@ -74,46 +81,57 @@ type DirectedEdgeListGraph interface {
DirectedEdgeLister
}
// A crunch graph forces a sparse graph to become a dense graph. That is, if the node IDs are [1,4,9,7] it would "crunch" the ids into the contiguous block [0,1,2,3]
// A crunch graph forces a sparse graph to become a dense graph. That is, if the node IDs are
// [1,4,9,7] it would "crunch" the ids into the contiguous block [0,1,2,3].
//
// All dense graphs should have the first ID at 0
// All dense graphs should have the first ID at 0.
type CrunchGraph interface {
Graph
Crunch()
}
// A Graph that implements Coster has an actual cost between adjacent nodes, also known as a weighted graph. If a graph implements coster and a function needs to read cost (e.g. A*), this function will
// take precedence over the Uniform Cost function (all weights are 1) if "nil" is passed in for the function argument
// A Graph that implements Coster has an actual cost between adjacent nodes, also known as a
// weighted graph. If a graph implements coster and a function needs to read cost (e.g. A*),
// this function will take precedence over the Uniform Cost function (all weights are 1) if "nil"
// is passed in for the function argument.
//
// If no edge exists between node1 and node2, the cost should be taken to be +inf (can be gotten by math.Inf(1))
// If no edge exists between node1 and node2, the cost should be taken to be +inf (can be gotten
// by math.Inf(1).)
type Coster interface {
Cost(node1, node2 Node) float64
}
// Guarantees that something implementing Coster is also a Graph
// Guarantees that something implementing Coster is also a Graph.
type CostGraph interface {
Coster
Graph
}
// A graph that implements HeuristicCoster implements a heuristic between any two given nodes.
// Like Coster, if a graph implements this and a function needs a heuristic cost (e.g. A*), this function will
// take precedence over the Null Heuristic (always returns 0) if "nil" is passed in for the function argument.
// If HeuristicCost is not intended to be used, it can be implemented as the null heuristic (always returns 0.)
// Like Coster, if a graph implements this and a function needs a heuristic cost (e.g. A*), this
// function will take precedence over the Null Heuristic (always returns 0) if "nil" is passed in
// for the function argument. If HeuristicCost is not intended to be used, it can be implemented as
// the null heuristic (always returns 0.)
type HeuristicCoster interface {
// HeuristicCost returns a heuristic cost between any two nodes.
HeuristicCost(node1, node2 Node) float64
}
// A Mutable Graph is a graph that can be changed in an arbitrary way. It is useful for several algorithms; for instance, Johnson's Algorithm requires adding a temporary node and changing edge weights.
// Another case where this is used is computing minimum spanning trees. Since trees are graphs, a minimum spanning tree can be created using this interface.
// A Mutable Graph is a graph that can be changed in an arbitrary way. It is useful for several
// algorithms; for instance, Johnson's Algorithm requires adding a temporary node and changing
// edge weights. Another case where this is used is computing minimum spanning trees. Since trees
// are graphs, a minimum spanning tree can be created using this interface.
//
// Note that just because a graph does not implement MutableGraph does not mean that this package expects it to be invariant (though even a MutableGraph should be treated as invariant while an algorithm
// is operating on it), it simply means that without this interface this package can not properly handle the graph in order to, say, fill it with a minimum spanning tree.
// Note that just because a graph does not implement MutableGraph does not mean that this package
// expects it to be invariant (though even a MutableGraph should be treated as invariant while an
// algorithm is operating on it), it simply means that without this interface this package can not
// properly handle the graph in order to, say, fill it with a minimum spanning tree.
//
// In functions that take a MutableGraph as an argument, it should not be the same as the Graph argument as concurrent modification will likely cause problems in most cases.
// In functions that take a MutableGraph as an argument, it should not be the same as the Graph
// argument as concurrent modification will likely cause problems in most cases.
//
// Mutable graphs should always record the IDs as they are represented -- which means they are sparse by nature.
// Mutable graphs should always record the IDs as they are represented -- which means they are
// sparse by nature.
type MutableGraph interface {
CostGraph
// NewNode adds a node with an arbitrary ID and returns the new, unique ID
@@ -145,21 +163,27 @@ type MutableGraph interface {
// TODO AddNode, AddEdge, SetEdgeCost, RemoveNode, RemoveEdge, SetDirected need to say what they do.
// A DStarGraph is a special interface that allows the DStarLite function to be used on a graph
// A DStarGraph is a special interface that allows the DStarLite function to be used on a graph.
//
// D*-lite is an algorithm that allows for the graph representation to change when actions are taken, whether this be from actions taken by the agent or simply new information gathered.
// As such, there's a Move function, that allows the graph to take into account an agent moving to the next node. This is always followed by a call to ChangedEdges.
// D*-lite is an algorithm that allows for the graph representation to change when actions are
// taken, whether this be from actions taken by the agent or simply new information gathered.
// As such, there's a Move function, that allows the graph to take into account an agent moving
// to the next node. This is always followed by a call to ChangedEdges.
//
// Traditionally in D*-lite, the algorithm would scan every edge to see if the cost changed, and then update its information if it detected any changes. This slightly remixed step
// allows the graph to provide notification of any changes, and even provide an alternate cost function if it needs to. This can be used to speed up the algorithm significantly
// since the graph no longer has to scan for changes, and only updates when told to. If changedEdges is nil or of len 0, no updates will be performed. If changedEdges is not nil, it
// will update the internal representation. If newCostFunc is non-nil it will be swapped with dStar's current cost function if and only if changedEdges is non-nil/len>0, however,
// newCostFunc is not required to be non-nil if updates are present. DStar will continue using the current cost function if that is the case.
// Traditionally in D*-lite, the algorithm would scan every edge to see if the cost changed, and
// then update its information if it detected any changes. This slightly remixed step allows the
// graph to provide notification of any changes, and even provide an alternate cost function if it
// needs to. This can be used to speed up the algorithm significantly since the graph no longer has
// to scan for changes, and only updates when told to. If changedEdges is nil or of len 0, no
// updates will be performed. If changedEdges is not nil, it will update the internal
// representation. If newCostFunc is non-nil it will be swapped with dStar's current cost function
// if and only if changedEdges is non-nil/len>0, however, newCostFunc is not required to be non-nil
// if updates are present. DStar will continue using the current cost function if that is the case.
type DStarGraph interface {
Graph
Move(target Node)
ChangedEdges() (newCostFunc func(Node, Node) float64, changedEdges []Edge)
}
// A function that returns the cost from one node to another
// A function that returns the cost from one node to another.
type CostFunc func(Node, Node) float64

View File

@@ -14,19 +14,24 @@ type AllPathFunc func(start, goal gr.Node) (path [][]gr.Node, cost float64, err
// Finds one path between start and goal, which it finds is arbitrary
type PathFunc func(start, goal gr.Node) (path []gr.Node, cost float64, err error)
// This function returns two functions: one that will generate all shortest paths between two nodes with ids i and j, and one that will generate just one path.
// This function returns two functions: one that will generate all shortest paths between two
// nodes with ids i and j, and one that will generate just one path.
//
// This algorithm requires the CrunchGraph interface which means it only works on graphs with dense node ids since it uses an adjacency matrix.
// This algorithm requires the CrunchGraph interface which means it only works on graphs with
// dense node ids since it uses an adjacency matrix.
//
// This algorithm isn't blazingly fast, but is relatively fast for the domain. It runs at O((number of vertices)^3) in best, worst, and average case,
// and successfully computes the cost between all pairs of vertices.
// This algorithm isn't blazingly fast, but is relatively fast for the domain. It runs at
// O((number of vertices)^3) in best, worst, and average case, and successfully computes the cost
// between all pairs of vertices.
//
// This function operates slightly differently from the others for convenience -- rather than generating paths and returning them to you,
// it gives you the option of calling one of two functions for each start/goal pair you need info for. One will return the path, cost,
// or an error if no path exists.
// This function operates slightly differently from the others for convenience -- rather than
// generating paths and returning them to you, it gives you the option of calling one of two
// functions for each start/goal pair you need info for. One will return the path, cost, or an
// error if no path exists.
//
// The other will return the cost and an error if no path exists, but it will also return ALL possible shortest paths between start and goal.
// This is not too much more expensive than generating one path, but it does obviously increase with the number of paths.
// The other will return the cost and an error if no path exists, but it will also return ALL
// possible shortest paths between start and goal. This is not too much more expensive than
// generating one path, but it does obviously increase with the number of paths.
func FloydWarshall(graph gr.CrunchGraph, cost gr.CostFunc) (AllPathFunc, PathFunc) {
graph.Crunch()
sf := setupFuncs(graph, cost, nil)
@@ -58,15 +63,16 @@ func FloydWarshall(graph gr.CrunchGraph, cost gr.CostFunc) (AllPathFunc, PathFun
if dist[i+j*numNodes] > dist[i+k*numNodes]+dist[k+j*numNodes] {
dist[i+j*numNodes] = dist[i+k*numNodes] + dist[k+j*numNodes]
// Avoid generating too much garbage by reusing the memory in the list if we've allocated one already
// Avoid generating too much garbage by reusing the memory
// in the list if we've allocated one already
if next[i+j*numNodes] == nil {
next[i+j*numNodes] = []int{k}
} else {
next[i+j*numNodes] = next[i+j*numNodes][:1]
next[i+j*numNodes][0] = k
}
// If the cost between the nodes happens to be the same cost as what we know, add the approriate
// intermediary to the list
// If the cost between the nodes happens to be the same cost
// as what we know, add the approriate intermediary to the list
} else if math.Abs(dist[i+k*numNodes]+dist[k+j*numNodes]-dist[i+j*numNodes]) < 0.00001 && i != k && i != j && j != k {
next[i+j*numNodes] = append(next[i+j*numNodes], k)
}

View File

@@ -12,24 +12,34 @@ import (
"github.com/gonum/graph/xifo"
)
// Returns an ordered list consisting of the nodes between start and goal. The path will be the shortest path assuming the function heuristicCost is admissible.
// The second return value is the cost, and the third is the number of nodes expanded while searching (useful info for tuning heuristics). Negative Costs will cause
// bad things to happen, as well as negative heuristic estimates.
// Returns an ordered list consisting of the nodes between start and goal. The path will be the
// shortest path assuming the function heuristicCost is admissible. The second return value is the
// cost, and the third is the number of nodes expanded while searching (useful info for tuning
// heuristics). Negative Costs will cause bad things to happen, as well as negative heuristic
// estimates.
//
// A heuristic is admissible if, for any node in the graph, the heuristic estimate of the cost between the node and the goal is less than or equal to the true cost.
// A heuristic is admissible if, for any node in the graph, the heuristic estimate of the cost
// between the node and the goal is less than or equal to the true cost.
//
// Performance may be improved by providing a consistent heuristic (though one is not needed to find the optimal path), a heuristic is consistent if its value for a given node is less than (or equal to) the
// actual cost of reaching its neighbors + the heuristic estimate for the neighbor itself. You can force consistency by making your HeuristicCost function
// return max(NonConsistentHeuristicCost(neighbor,goal), NonConsistentHeuristicCost(self,goal) - Cost(self,neighbor)). If there are multiple neighbors, take the max of all of them.
// Performance may be improved by providing a consistent heuristic (though one is not needed to
// find the optimal path), a heuristic is consistent if its value for a given node is less than
// (or equal to) the actual cost of reaching its neighbors + the heuristic estimate for the
// neighbor itself. You can force consistency by making your HeuristicCost function return
// max(NonConsistentHeuristicCost(neighbor,goal), NonConsistentHeuristicCost(self,goal) -
// Cost(self,neighbor)). If there are multiple neighbors, take the max of all of them.
//
// Cost and HeuristicCost take precedence for evaluating cost/heuristic distance. If one is not present (i.e. nil) the function will check the graph's interface for the respective interface:
// Coster for Cost and HeuristicCoster for HeuristicCost. If the correct one is present, it will use the graph's function for evaluation.
// Cost and HeuristicCost take precedence for evaluating cost/heuristic distance. If one is not
// present (i.e. nil) the function will check the graph's interface for the respective interface:
// Coster for Cost and HeuristicCoster for HeuristicCost. If the correct one is present, it will
// use the graph's function for evaluation.
//
// Finally, if neither the argument nor the interface is present, the function will assume discrete.UniformCost for Cost and discrete.NullHeuristic for HeuristicCost
// Finally, if neither the argument nor the interface is present, the function will assume
// UniformCost for Cost and NullHeuristic for HeuristicCost.
//
// To run Uniform Cost Search, run A* with the NullHeuristic
// To run Uniform Cost Search, run A* with the NullHeuristic.
//
// To run Breadth First Search, run A* with both the NullHeuristic and UniformCost (or any cost function that returns a uniform positive value)
// To run Breadth First Search, run A* with both the NullHeuristic and UniformCost (or any cost
// function that returns a uniform positive value.)
func AStar(start, goal gr.Node, graph gr.Graph, cost, heuristicCost gr.CostFunc) (path []gr.Node, pathCost float64, nodesExpanded int) {
sf := setupFuncs(graph, cost, heuristicCost)
successors, cost, heuristicCost := sf.successors, sf.cost, sf.heuristicCost
@@ -82,14 +92,18 @@ func BreadthFirstSearch(start, goal gr.Node, graph gr.Graph) ([]gr.Node, int) {
return path, visited
}
// Dijkstra's Algorithm is essentially a goalless Uniform Cost Search. That is, its results are roughly equivalent to
// running A* with the Null Heuristic from a single node to every other node in the graph -- though it's a fair bit faster
// because running A* in that way will recompute things it's already computed every call. Note that you won't necessarily get the same path
// you would get for A*, but the cost is guaranteed to be the same (that is, if multiple shortest paths exist, you may get a different shortest path).
// Dijkstra's Algorithm is essentially a goalless Uniform Cost Search. That is, its results are
// roughly equivalent to running A* with the Null Heuristic from a single node to every other node
// in the graph -- though it's a fair bit faster because running A* in that way will recompute
// things it's already computed every call. Note that you won't necessarily get the same path
// you would get for A*, but the cost is guaranteed to be the same (that is, if multiple shortest
// paths exist, you may get a different shortest path).
//
// Like A*, Dijkstra's Algorithm likely won't run correctly with negative edge weights -- use Bellman-Ford for that instead
// Like A*, Dijkstra's Algorithm likely won't run correctly with negative edge weights -- use
// Bellman-Ford for that instead.
//
// Dijkstra's algorithm usually only returns a cost map, however, since the data is available this version will also reconstruct the path to every node
// Dijkstra's algorithm usually only returns a cost map, however, since the data is available
// this version will also reconstruct the path to every node.
func Dijkstra(source gr.Node, graph gr.Graph, cost gr.CostFunc) (paths map[int][]gr.Node, costs map[int]float64) {
sf := setupFuncs(graph, cost, nil)
@@ -134,14 +148,19 @@ func Dijkstra(source gr.Node, graph gr.Graph, cost gr.CostFunc) (paths map[int][
return paths, costs
}
// The Bellman-Ford Algorithm is the same as Dijkstra's Algorithm with a key difference. They both take a single source and find the shortest path to every other
// (reachable) node in the graph. Bellman-Ford, however, will detect negative edge loops and abort if one is present. A negative edge loop occurs when there is a cycle in the graph
// such that it can take an edge with a negative cost over and over. A -(-2)> B -(2)> C isn't a loop because A->B can only be taken once, but A<-(-2)->B-(2)>C is one because
// A and B have a bi-directional edge, and algorithms like Dijkstra's will infinitely flail between them getting progressively lower costs.
// The Bellman-Ford Algorithm is the same as Dijkstra's Algorithm with a key difference. They both
// take a single source and find the shortest path to every other (reachable) node in the graph.
// Bellman-Ford, however, will detect negative edge loops and abort if one is present. A negative
// edge loop occurs when there is a cycle in the graph such that it can take an edge with a
// negative cost over and over. A -(-2)> B -(2)> C isn't a loop because A->B can only be taken once,
// but A<-(-2)->B-(2)>C is one because A and B have a bi-directional edge, and algorithms like
// Dijkstra's will infinitely flail between them getting progressively lower costs.
//
// That said, if you do not have a negative edge weight, use Dijkstra's Algorithm instead, because it's faster.
// That said, if you do not have a negative edge weight, use Dijkstra's Algorithm instead, because
// it's faster.
//
// Like Dijkstra's, along with the costs this implementation will also construct all the paths for you. In addition, it has a third return value which will be true if the algorithm was aborted
// Like Dijkstra's, along with the costs this implementation will also construct all the paths for
// you. In addition, it has a third return value which will be true if the algorithm was aborted
// due to the presence of a negative edge weight cycle.
func BellmanFord(source gr.Node, graph gr.Graph, cost gr.CostFunc) (paths map[int][]gr.Node, costs map[int]float64, err error) {
sf := setupFuncs(graph, cost, nil)
@@ -189,16 +208,21 @@ func BellmanFord(source gr.Node, graph gr.Graph, cost gr.CostFunc) (paths map[in
// Johnson's Algorithm generates the lowest cost path between every pair of nodes in the graph.
//
// It makes use of Bellman-Ford and a dummy graph. It creates a dummy node containing edges with a cost of zero to every other node. Then it runs Bellman-Ford with this
// dummy node as the source. It then modifies the all the nodes' edge weights (which gets rid of all negative weights).
// It makes use of Bellman-Ford and a dummy graph. It creates a dummy node containing edges with a
// cost of zero to every other node. Then it runs Bellman-Ford with this dummy node as the source.
// It then modifies the all the nodes' edge weights (which gets rid of all negative weights).
//
// Finally, it removes the dummy node and runs Dijkstra's starting at every node.
//
// This algorithm is fairly slow. Its purpose is to remove negative edge weights to allow Dijkstra's to function properly. It's probably not worth it to run this algorithm if you have
// all non-negative edge weights. Also note that this implementation copies your whole graph into a GonumGraph (so it can add/remove the dummy node and edges and reweight the graph).
// This algorithm is fairly slow. Its purpose is to remove negative edge weights to allow
// Dijkstra's to function properly. It's probably not worth it to run this algorithm if you have
// all non-negative edge weights. Also note that this implementation copies your whole graph into
// a GonumGraph (so it can add/remove the dummy node and edges and reweight the graph).
//
// Its return values are, in order: a map from the source node, to the destination node, to the path between them; a map from the source node, to the destination node, to the cost of the path between them;
// and a bool that is true if Bellman-Ford detected a negative edge weight cycle -- thus causing it (and this algorithm) to abort (if aborted is true, both maps will be nil).
// Its return values are, in order: a map from the source node, to the destination node, to the
// path between them; a map from the source node, to the destination node, to the cost of the path
// between them; and a bool that is true if Bellman-Ford detected a negative edge weight cycle --
// thus causing it (and this algorithm) to abort (if aborted is true, both maps will be nil).
func Johnson(graph gr.Graph, cost gr.CostFunc) (nodePaths map[int]map[int][]gr.Node, nodeCosts map[int]map[int]float64, err error) {
sf := setupFuncs(graph, cost, nil)
successors, cost := sf.successors, sf.cost
@@ -252,8 +276,9 @@ func Johnson(graph gr.Graph, cost gr.CostFunc) (nodePaths map[int]map[int][]gr.N
return nodePaths, nodeCosts, nil
}
// Expands the first node it sees trying to find the destination. Depth First Search is *not* guaranteed to find the shortest path,
// however, if a path exists DFS is guaranteed to find it (provided you don't find a way to implement a Graph with an infinite depth)
// Expands the first node it sees trying to find the destination. Depth First Search is *not*
// guaranteed to find the shortest path, however, if a path exists DFS is guaranteed to find it
// (provided you don't find a way to implement a Graph with an infinite depth.)
func DepthFirstSearch(start, goal gr.Node, graph gr.Graph) []gr.Node {
sf := setupFuncs(graph, nil, nil)
successors := sf.successors
@@ -327,13 +352,19 @@ func CopyGraph(dst gr.MutableGraph, src gr.Graph) {
/* Basic Graph tests */
// Also known as Tarjan's Strongly Connected Components Algorithm. This returns all the strongly connected components in the graph.
// Also known as Tarjan's Strongly Connected Components Algorithm. This returns all the strongly
// connected components in the graph.
//
// A strongly connected component of a graph is a set of vertices where it's possible to reach any vertex in the set from any other (meaning there's a cycle between them)
// A strongly connected component of a graph is a set of vertices where it's possible to reach any
// vertex in the set from any other (meaning there's a cycle between them.)
//
// Generally speaking, a directed graph where the number of strongly connected components is equal to the number of nodes is acyclic, unless you count reflexive edges as a cycle (which requires only a little extra testing)
// Generally speaking, a directed graph where the number of strongly connected components is equal
// to the number of nodes is acyclic, unless you count reflexive edges as a cycle (which requires
// only a little extra testing.)
//
// An undirected graph should end up with as many SCCs as there are "islands" (or subgraphs) of connections, meaning having more than one strongly connected component implies that your graph is not fully connected.
// An undirected graph should end up with as many SCCs as there are "islands" (or subgraphs) of
// connections, meaning having more than one strongly connected component implies that your graph
// is not fully connected.
func Tarjan(graph gr.Graph) (sccs [][]gr.Node) {
index := 0
vStack := &xifo.GonumStack{}
@@ -416,11 +447,13 @@ func IsPath(path []gr.Node, graph gr.Graph) bool {
return true
}
/* Implements minimum-spanning tree algorithms; puts the resulting minimum spanning tree in the dst graph */
/* Implements minimum-spanning tree algorithms;
puts the resulting minimum spanning tree in the dst graph */
// Generates a minimum spanning tree with sets.
//
// As with other algorithms that use Cost, the order of precedence is Argument > Interface > UniformCost
// As with other algorithms that use Cost, the order of precedence is
// Argument > Interface > UniformCost.
func Prim(dst gr.MutableGraph, graph gr.EdgeListGraph, cost gr.CostFunc) {
cost = setupFuncs(graph, cost, nil).cost
@@ -466,9 +499,9 @@ func Prim(dst gr.MutableGraph, graph gr.EdgeListGraph, cost gr.CostFunc) {
}
// Generates a minimum spanning tree for a graph using discrete.DisjointSet
// Generates a minimum spanning tree for a graph using discrete.DisjointSet.
//
// As with other algorithms with Cost, the precedence goes Argument > Interface > UniformCost
// As with other algorithms with Cost, the precedence goes Argument > Interface > UniformCost.
func Kruskal(dst gr.MutableGraph, graph gr.EdgeListGraph, cost func(gr.Node, gr.Node) float64) {
cost = setupFuncs(graph, cost, nil).cost
@@ -489,8 +522,8 @@ func Kruskal(dst gr.MutableGraph, graph gr.EdgeListGraph, cost func(gr.Node, gr.
}
for _, edge := range edgeWeights {
// The disjoint set doesn't really care for which is head and which is tail so this should work fine
// without checking both ways
// The disjoint set doesn't really care for which is head and which is tail so this
// should work fine without checking both ways
if s1, s2 := ds.Find(edge.Edge.Head().ID()), ds.Find(edge.Edge.Tail().ID); s1 != s2 {
ds.Union(s1, s2)
if !dst.NodeExists(edge.Edge.Head()) {
@@ -505,11 +538,12 @@ func Kruskal(dst gr.MutableGraph, graph gr.EdgeListGraph, cost func(gr.Node, gr.
/* Control flow graph stuff */
// A dominates B if and only if the only path through B travels through A
// A dominates B if and only if the only path through B travels through A.
//
// This returns all possible dominators for all nodes, it does not prune for strict dominators, immediate dominators etc
// This returns all possible dominators for all nodes, it does not prune for strict dominators,
// immediate dominators etc.
//
// The int map[int]*set.Set is the node's ID
// The int map[int]*set.Set is the node's ID.
func Dominators(start gr.Node, graph gr.Graph) map[int]*set.Set {
allNodes := set.NewSet()
nlist := graph.NodeList()
@@ -558,9 +592,10 @@ func Dominators(start gr.Node, graph gr.Graph) map[int]*set.Set {
return dominators
}
// A Postdominates B if and only if all paths from B travel through A
// A Postdominates B if and only if all paths from B travel through A.
//
// This returns all possible post-dominators for all nodes, it does not prune for strict postdominators, immediate postdominators etc
// This returns all possible post-dominators for all nodes, it does not prune for strict
// postdominators, immediate postdominators etc.
func PostDominators(end gr.Node, graph gr.Graph) map[int]*set.Set {
successors := setupFuncs(graph, nil, nil).successors

View File

@@ -12,9 +12,9 @@ type searchFuncs struct {
cost, heuristicCost gr.CostFunc
}
// Sets up the cost functions and successor functions so I don't have to do a type switch every time.
// This almost always does more work than is necessary, but since it's only executed once per function, and graph functions are rather costly, the "extra work"
// should be negligible.
// Sets up the cost functions and successor functions so I don't have to do a type switch every
// time. This almost always does more work than is necessary, but since it's only executed once
// per function, and graph functions are rather costly, the "extra work" should be negligible.
func setupFuncs(graph gr.Graph, cost, heuristicCost gr.CostFunc) searchFuncs {
sf := searchFuncs{}
@@ -61,7 +61,8 @@ func setupFuncs(graph gr.Graph, cost, heuristicCost gr.CostFunc) searchFuncs {
/* Purely internal data structures and functions (mostly for sorting) */
// A package that contains an edge (as from EdgeList), and a Weight (as if Cost(Edge.Head(), Edge.Tail()) had been called)
// A package that contains an edge (as from EdgeList), and a Weight (as if Cost(Edge.Head(),
// Edge.Tail()) had been called.)
type WeightedEdge struct {
gr.Edge
Weight float64
@@ -97,7 +98,9 @@ type aStarPriorityQueue struct {
}
func (pq *aStarPriorityQueue) Less(i, j int) bool {
return pq.nodes[i].fscore < pq.nodes[j].fscore // As the heap documentation says, a priority queue is listed if the actual values are treated as if they were negative
// As the heap documentation says, a priority queue is listed if the actual values
// are treated as if they were negative
return pq.nodes[i].fscore < pq.nodes[j].fscore
}
func (pq *aStarPriorityQueue) Swap(i, j int) {

View File

@@ -1,13 +1,18 @@
package set
// A disjoint set is a collection of non-overlapping sets. That is, for any two sets in the disjoint set, their intersection is the empty set
// A disjoint set is a collection of non-overlapping sets. That is, for any two sets in the
// disjoint set, their intersection is the empty set.
//
// A disjoint set has three principle operations: Make Set, Find, and Union.
//
// Make set creates a new set for an element (presuming it does not already exist in any set in the disjoint set), Find finds the set containing that element (if any),
// and Union merges two sets in the disjoint set. In general, algorithms operating on disjoint sets are "union-find" algorithms, where two sets are found with Find, and then joined with Union.
// Make set creates a new set for an element (presuming it does not already exist in any set in
// the disjoint set), Find finds the set containing that element (if any), and Union merges two
// sets in the disjoint set. In general, algorithms operating on disjoint sets are "union-find"
// algorithms, where two sets are found with Find, and then joined with Union.
//
// A concrete example of a union-find algorithm can be found as discrete.Kruskal -- which unions two sets when an edge is created between two vertices, and refuses to make an edge between two vertices if they're part of the same set.
// A concrete example of a union-find algorithm can be found as discrete.Kruskal -- which unions
// two sets when an edge is created between two vertices, and refuses to make an edge between two
// vertices if they're part of the same set.
type DisjointSet struct {
master map[interface{}]*DisjointSetNode
}
@@ -21,7 +26,7 @@ func NewDisjointSet() *DisjointSet {
return &DisjointSet{master: make(map[interface{}]*DisjointSetNode)}
}
// If the element isn't already somewhere in there, adds it to the master set and its own tiny set
// If the element isn't already somewhere in there, adds it to the master set and its own tiny set.
func (ds *DisjointSet) MakeSet(el interface{}) {
if _, ok := ds.master[el]; ok {
return
@@ -31,7 +36,7 @@ func (ds *DisjointSet) MakeSet(el interface{}) {
ds.master[el] = dsNode
}
// Returns the set the element belongs to, or nil if none
// Returns the set the element belongs to, or nil if none.
func (ds *DisjointSet) Find(el interface{}) *DisjointSetNode {
dsNode, ok := ds.master[el]
if !ok {
@@ -49,9 +54,10 @@ func find(dsNode *DisjointSetNode) *DisjointSetNode {
return dsNode.parent
}
// Unions two subsets within the DisjointSet
// Unions two subsets within the DisjointSet.
//
// If x or y are not in this disjoint set, the behavior is undefined. If either pointer is nil, this function will panic
// If x or y are not in this disjoint set, the behavior is undefined. If either pointer is nil,
// this function will panic.
func (ds *DisjointSet) Union(x, y *DisjointSetNode) {
if x == nil || y == nil {
panic("Disjoint Set union on nil sets")

View File

@@ -4,14 +4,19 @@ import ()
// On one hand, using an interface{} as a key works on some levels.
// On the other hand, from experience, I can say that working with interface{} is a pain
// so I don't like it in an API. An alternate idea is to make Set an interface with a method that allows you to GRAB a map[interface{}]struct{} from
// the implementation, but that adds a lot of calls and needless operations, making the library slower
// so I don't like it in an API. An alternate idea is to make Set an interface with a method
// that allows you to GRAB a map[interface{}]struct{} from the implementation, but that adds
// a lot of calls and needless operations, making the library slower.
//
// Another point, using an interface{} may be pointless because a map key MUST have == and != defined, limiting the possible keys anyway (for instance, if you had a set of [3]floats I don't think it will do a deep
// comparison, making it rather pointless). Also, keying with a float will mean it does a strict == with the floats, possibly causing bad behavior. It may be best to just make it a map[int]struct{}. Thoughts?
// Another point, using an interface{} may be pointless because a map key MUST have == and !=
// defined, limiting the possible keys anyway (for instance, if you had a set of [3]floats I don't
// think it will do a deep comparison, making it rather pointless). Also, keying with a float will
// mean it does a strict == with the floats, possibly causing bad behavior. It may be best to just
// make it a map[int]struct{}. Thoughts?
type Set map[interface{}]struct{}
// I highly doubt we have to worry about running out of IDs, but we could add a little reclaimID function if we're worried
// I highly doubt we have to worry about running out of IDs, but we could add a little reclaimID
// function if we're worried
var globalid uint64 = 0
// For cleanliness
@@ -49,7 +54,7 @@ func (dst *Set) Copy(src *Set) *Set {
return dst
}
// If every element in s1 is also in s2 (and vice versa), the sets are deemed equal
// If every element in s1 is also in s2 (and vice versa), the sets are deemed equal.
func Equal(s1, s2 *Set) bool {
if s1 == s2 {
return true
@@ -72,7 +77,8 @@ func Equal(s1, s2 *Set) bool {
//
// {a,b,c} UNION {d,e,f} = {a,b,c,d,e,f}
//
// Since sets may not have repetition, unions of two sets that overlap do not contain repeat elements, that is:
// Since sets may not have repetition, unions of two sets that overlap do not contain repeat
// elements, that is:
//
// {a,b,c} UNION {b,c,d} = {a,b,c,d}
func (dst *Set) Union(s1, s2 *Set) *Set {
@@ -101,7 +107,8 @@ func (dst *Set) Union(s1, s2 *Set) *Set {
// Takes the intersection of s1 and s2, and stores it in dst
//
// The intersection of two sets, s1 and s2, is the set containing all the elements shared between the two sets, for instance
// The intersection of two sets, s1 and s2, is the set containing all the elements shared between
// the two sets, for instance:
//
// {a,b,c} INTERSECT {b,c,d} = {b,c}
//
@@ -147,7 +154,8 @@ func (dst *Set) Intersection(s1, s2 *Set) *Set {
// Takes the difference (-) of s1 and s2 and stores it in dst.
//
// The difference (-) between two sets, s1 and s2, is all the elements in s1 that are NOT also in s2.
// The difference (-) between two sets, s1 and s2, is all the elements in s1 that are NOT also
// in s2.
//
// {a,b,c} - {b,c,d} = {a}
//
@@ -155,12 +163,13 @@ func (dst *Set) Intersection(s1, s2 *Set) *Set {
//
// {a,b,c} - {a,b,c} = {}
//
// The difference between two sets with no overlapping elements is s1
// The difference between two sets with no overlapping elements is s1:
//
// {a,b,c} - {d,e,f} = {a,b,c}
//
// Implementation note: if dst == s2 (meaning they have identical pointers), a temporary set must be used to store the data
// and then copied over, thus s2.Diff(s1,s2) has an extra allocation and may cause worse performance in some cases.
// Implementation note: if dst == s2 (meaning they have identical pointers), a temporary set must
// be used to store the data and then copied over, thus s2.Diff(s1,s2) has an extra allocation and
// may cause worse performance in some cases.
func (dst *Set) Diff(s1, s2 *Set) *Set {
if s1 == s2 {
return dst.Clear()
@@ -197,12 +206,12 @@ func (dst *Set) Diff(s1, s2 *Set) *Set {
// {c,d} SUBSET {a,b,c} = false
// {a,b,c,d} SUBSET {a,b,c} = false
//
// Special case: The empty set is a subset of everything
// Special case: The empty set is a subset of everything:
//
// {} SUBSET {a,b} = true
// {} SUBSET {} = true
//
// In the case where one needs to test if s1 is smaller than s2, but not equal, use ProperSubset
// In the case where one needs to test if s1 is smaller than s2, but not equal, use ProperSubset.
func Subset(s1, s2 *Set) bool {
if len(*s1) > len(*s2) {
return false
@@ -222,7 +231,8 @@ func Subset(s1, s2 *Set) bool {
}
// Returns true if s1 is a proper subset of s2.
// A proper subset is when every element of s1 is in s2, but s1 is smaller than s2 (i.e. they are not equal):
// A proper subset is when every element of s1 is in s2, but s1 is smaller than s2 (i.e. they are
// not equal):
//
// {a,b,c} PROPER SUBSET {a,b,c} = false
// {a,b} PROPER SUBSET {a,b,c} = true
@@ -234,7 +244,7 @@ func Subset(s1, s2 *Set) bool {
// {} PROPER SUBSET {a,b} = true
// {} PROPER SUBSET {} = false
//
// When equality is allowed, use Subset
// When equality is allowed, use Subset.
func ProperSubset(s1, s2 *Set) bool {
if len(*s1) >= len(*s2) { // implicitly tests if s1 and s2 are both the empty set
return false
@@ -257,7 +267,7 @@ func (s *Set) Contains(el interface{}) bool {
return ok
}
// Adds the element el to s1
// Adds the element el to s1.
func (s1 *Set) Add(element interface{}) {
(*s1)[element] = flag
}
@@ -268,12 +278,12 @@ func (s1 *Set) AddAll(elements ...interface{}) {
}
}
// Removes the element el from s1
// Removes the element el from s1.
func (s1 *Set) Remove(element interface{}) {
delete(*s1, element)
}
// Returns the number of elements in s1
// Returns the number of elements in s1.
func (s1 *Set) Cardinality() int {
return len(*s1)
}

View File

@@ -85,7 +85,8 @@ func (q *GonumQueue) IsEmpty() bool {
return len(*q) == 0
}
// Deque is a stack/queue hybrid (from "deck"), I'm not sure if the type conversions will hurt performance or not (I suspect not)
// Deque is a stack/queue hybrid (from "deck"), I'm not sure if the type conversions will hurt
// performance or not (I suspect not.)
type GonumDeque []interface{}
func (d *GonumDeque) IsEmpty() bool {
@@ -101,7 +102,7 @@ func (d *GonumDeque) Pop() interface{} {
return a.Pop()
}
// Poll is a queue-pop
// Poll is a queue-pop.
func (d *GonumDeque) Poll() interface{} {
a := (*GonumQueue)(d)
return a.Poll()