Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added higher-level API for querying a node's degree in a graph #75

Merged
merged 2 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
}
}