Skip to content

Commit

Permalink
Added higher-level API for querying a node's degree in a graph (#75)
Browse files Browse the repository at this point in the history
* improved degree api and added tests

* fixed typo in `TotalDegree()` documentation
  • Loading branch information
denk0403 authored Dec 13, 2023
1 parent cf19d89 commit 9f8e121
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 0 deletions.
36 changes: 36 additions & 0 deletions cgraph/cgraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -1117,10 +1117,46 @@ func (g *Graph) NumberSubGraph() int {
return ccall.Agnsubg(g.Agraph)
}

// Returns the degree of the given node in the graph, where arguments "in" and
// "out" are C-like booleans that select which edge sets to query.
//
// g.Degree(node, 0, 0) // always returns 0
// g.Degree(node, 0, 1) // returns the node's outdegree
// g.Degree(node, 1, 0) // returns the node's indegree
// g.Degree(node, 1, 1) // returns the node's total degree (indegree + outdegree)
func (g *Graph) Degree(n *Node, in, out int) int {
return ccall.Agdegree(g.Agraph, n.Agnode, in, out)
}

// Returns the indegree of the given node in the graph.
//
// Note: While undirected graphs don't normally have a
// notion of indegrees, calling this method on an
// undirected graph will treat it as if it's directed.
// As a result, it's best to avoid calling this method
// on an undirected graph.
func (g *Graph) Indegree(n *Node) int {
return ccall.Agdegree(g.Agraph, n.Agnode, 1, 0)
}

// Returns the outdegree of the given node in the graph.
//
// Note: While undirected graphs don't normally have a
// notion of outdegrees, calling this method on an
// undirected graph will treat it as if it's directed.
// As a result, it's best to avoid calling this method
// on an undirected graph.
func (g *Graph) Outdegree(n *Node) int {
return ccall.Agdegree(g.Agraph, n.Agnode, 0, 1)
}

// Returns the total degree of the given node in the graph.
// This can be thought of as the total number of edges coming
// in and out of a node.
func (g *Graph) TotalDegree(n *Node) int {
return ccall.Agdegree(g.Agraph, n.Agnode, 1, 1)
}

func (g *Graph) CountUniqueEdges(n *Node, in, out int) int {
return ccall.Agcountuniqedges(g.Agraph, n.Agnode, in, out)
}
Expand Down
65 changes: 65 additions & 0 deletions graphviz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,68 @@ func TestParseFile(t *testing.T) {
}
}
}

func TestNodeDegree(t *testing.T) {
type test struct {
node_name string
expected_indegree int
expected_outdegree int
expected_total_degree int
}

type graphtest struct {
input string
tests []test
}

graphtests := []graphtest{
{input: "digraph test { a -> b }", tests: []test{
{node_name: "a", expected_indegree: 0, expected_outdegree: 1, expected_total_degree: 1},
{node_name: "b", expected_indegree: 1, expected_outdegree: 0, expected_total_degree: 1},
}},
{input: "digraph test { a -> b; a -> b; a -> a; c -> a }", tests: []test{
{node_name: "a", expected_indegree: 2, expected_outdegree: 3, expected_total_degree: 5},
{node_name: "b", expected_indegree: 2, expected_outdegree: 0, expected_total_degree: 2},
{node_name: "c", expected_indegree: 0, expected_outdegree: 1, expected_total_degree: 1},
}},
{input: "graph test { a -- b; a -- b; a -- a; c -- a }", tests: []test{
{node_name: "a", expected_indegree: 2, expected_outdegree: 3, expected_total_degree: 5},
{node_name: "b", expected_indegree: 2, expected_outdegree: 0, expected_total_degree: 2},
{node_name: "c", expected_indegree: 0, expected_outdegree: 1, expected_total_degree: 1},
}},
{input: "strict graph test { a -- b; b -- a; a -- a; c -- a }", tests: []test{
{node_name: "a", expected_indegree: 2, expected_outdegree: 2, expected_total_degree: 4},
{node_name: "b", expected_indegree: 1, expected_outdegree: 0, expected_total_degree: 1},
{node_name: "c", expected_indegree: 0, expected_outdegree: 1, expected_total_degree: 1},
}},
}

for _, graphtest := range graphtests {
input := graphtest.input
graph, err := graphviz.ParseBytes([]byte(input))
if err != nil {
t.Fatalf("Input: %s. Error: %+v", input, err)
}

for _, test := range graphtest.tests {
node_name := test.node_name
node, err := graph.Node(node_name)
if err != nil || node == nil {
t.Fatalf("Unable to retrieve node '%s'. Input: %s. Error: %+v", node_name, input, err)
}

indegree := graph.Indegree(node)
if test.expected_indegree != indegree {
t.Errorf("Unexpected indegree for node '%s'. Input: %s. Expected: %d. Actual: %d.", node_name, input, test.expected_indegree, indegree)
}
outdegree := graph.Outdegree(node)
if test.expected_outdegree != outdegree {
t.Errorf("Unexpected outdegree for node '%s'. Input: %s. Expected: %d. Actual: %d.", node_name, input, test.expected_outdegree, outdegree)
}
total_degree := graph.TotalDegree(node)
if test.expected_total_degree != total_degree {
t.Errorf("Unexpected total degree for node '%s'. Input: %s. Expected: %d. Actual: %d.", node_name, input, test.expected_total_degree, total_degree)
}
}
}
}

0 comments on commit 9f8e121

Please sign in to comment.