graph

package
v2.13.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 28, 2024 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package graph implements general-purpose graph algorithms.

This package does not implement any specific graph data structures: bring your own implementation!

Many of the algorithms and definitions are thanks to CLRS "Introduction to Algorithms", 3rd edition.

Example (SolarSystem)
package main

import (
	"fmt"
	stdslices "slices"
	"strings"

	"github.com/tawesoft/golib/v2/ds/graph"
	"github.com/tawesoft/golib/v2/fun/slices"
	"github.com/tawesoft/golib/v2/iter"
	"github.com/tawesoft/golib/v2/must"
)

type Distance int

type Body struct {
	Name   string
	Orbits map[graph.VertexIndex]Distance
}

// Universe is a multigraph.
type Universe struct {
	Bodies map[graph.VertexIndex]Body
}

// Vertexes implements the graph.Iterator interface.
func (u Universe) Vertexes() func() (graph.VertexIndex, bool) {
	return iter.Keys(iter.FromMap(u.Bodies))
}

// Edges implements the graph.Iterator interface.
func (u Universe) Edges(source graph.VertexIndex) func() (target graph.VertexIndex, count int, ok bool) {
	vertex := u.Bodies[source]
	it := iter.Keys(iter.FromMap(vertex.Orbits))
	return func() (_ graph.VertexIndex, _ int, _ bool) {
		target, ok := it()
		if !ok {
			return
		}
		return target, 1, true
	}
}

// AddVertex implements the graph.Builder interface
func (u *Universe) AddVertex() graph.VertexIndex {
	index := graph.VertexIndex(len(u.Bodies))
	if u.Bodies == nil {
		u.Bodies = make(map[graph.VertexIndex]Body)
	}
	u.Bodies[index] = Body{}
	return index
}

// AddEdge implements the graph.Builder interface
func (u *Universe) AddEdge(from graph.VertexIndex, to graph.VertexIndex) {
	u.Bodies[from].Orbits[to] = Distance(0)
}

func (u *Universe) AddBody(name string) graph.VertexIndex {
	index := u.AddVertex()
	u.Bodies[index] = Body{
		Name:   name,
		Orbits: make(map[graph.VertexIndex]Distance),
	}
	return index
}

func (u *Universe) AddOrbit(center graph.VertexIndex, body graph.VertexIndex, distance Distance) {
	u.AddEdge(center, body)
	u.Bodies[center].Orbits[body] = distance
}

func main() {
	// define a custom multigraph of orbit distances and solar irradiation
	var d Universe

	// Alpha Centuri is trinary star system with a binary star center
	alphaCentauri := d.AddBody("Alpha Centauri")     // Alpha Centauri AB barycenter
	proximaCentauri := d.AddBody("Proxima Centauri") // Alpha Centauri C

	sun := d.AddBody("Sol")

	mercury := d.AddBody("Mercury")
	venus := d.AddBody("Venus")
	earth := d.AddBody("The Earth")
	mars := d.AddBody("Mars")
	jupiter := d.AddBody("Jupiter")
	saturn := d.AddBody("Saturn")
	uranus := d.AddBody("Uranus")
	neptune := d.AddBody("Neptune")

	moon := d.AddBody("The Moon")
	demos := d.AddBody("Demos")
	phobos := d.AddBody("Phobos")

	// Average orbit distance (kilometres)
	// (figures from NASA)
	d.AddOrbit(alphaCentauri, proximaCentauri, 1_937_292_425_565)
	d.AddOrbit(sun, mercury, 57_000_000)
	d.AddOrbit(sun, venus, 108_000_000)
	d.AddOrbit(sun, earth, 149_000_000)
	d.AddOrbit(sun, mars, 228_000_000)
	d.AddOrbit(sun, jupiter, 780_000_000)
	d.AddOrbit(sun, saturn, 1_437_000_000)
	d.AddOrbit(sun, uranus, 2_871_000_000)
	d.AddOrbit(sun, neptune, 4_530_000_000)
	d.AddOrbit(earth, moon, 384_400)
	d.AddOrbit(mars, demos, 23_460)
	d.AddOrbit(mars, phobos, 6_000)

	// construct an adjacency matrix for efficient adjacency lookup
	orbitMatrix := graph.NewAdjacencyMatrix(nil)
	orbitMatrix.Calculate(d)

	// construct degree matrices for efficient in-/out- degree lookup
	indegreeMatrix := graph.NewDegreeMatrix()
	outdegreeMatrix := graph.NewDegreeMatrix()
	indegreeMatrix.Calculate(d.Vertexes, orbitMatrix.Indegree)
	outdegreeMatrix.Calculate(d.Vertexes, orbitMatrix.Outdegree)

	// efficiently returns true iff a orbits b
	orbits := func(a graph.VertexIndex, b graph.VertexIndex) bool {
		return orbitMatrix.Get(b, a) > 0
	}

	// efficiently returns the number of satellites orbiting a
	numSatellites := func(a graph.VertexIndex) int {
		return outdegreeMatrix.Get(a)
	}

	// produces target vertexes from orbit edges
	satellites := func(source graph.VertexIndex) func() (graph.VertexIndex, bool) {
		edges := d.Edges(source)
		return func() (graph.VertexIndex, bool) {
			for {
				targetIdx, _, ok := edges()
				return targetIdx, ok
			}
		}
	}

	must.Truef(orbits(earth, sun), "expected: earth orbits the sun")
	must.Truef(orbits(moon, earth), "expected: moon orbits the earth")
	must.Falsef(orbits(sun, earth), "expected: sun does not orbit the earth")

	// efficiently find all graph roots
	roots := iter.ToSlice(graph.Roots(orbitMatrix.Vertexes, indegreeMatrix.Get))

	vertexName := func(x graph.VertexIndex) string {
		vertex := d.Bodies[x]
		return vertex.Name
	}

	sorted := func(xs []string) []string {
		stdslices.Sort(xs)
		return xs
	}

	fmt.Printf("The graph contains data for %d solar systems: %s.\n",
		len(roots),
		strings.Join(sorted(slices.Map(vertexName, roots)), " and "))
	fmt.Printf("The sun is orbited by %d satellites.\n",
		numSatellites(sun))
	fmt.Printf("Mars is orbited by %d satellites: %s.\n",
		numSatellites(mars),
		strings.Join(sorted(slices.Map(vertexName, iter.ToSlice(satellites(mars)))), " and "))

}
Output:

The graph contains data for 2 solar systems: Alpha Centauri and Sol.
The sun is orbited by 8 satellites.
Mars is orbited by 2 satellites: Demos and Phobos.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AdjacencyMatrix

type AdjacencyMatrix struct {
	// contains filtered or unexported fields
}

AdjacencyMatrix represents the number of directed edges from a source vertex (along the x-axis) to a target vertex (along the y-axis), including self-loops, if any.

Values are indexed by VertexIndex with source vertexes in the x-axis and target vertexes in the y-axis.

As a representation of a graph itself, AdjacencyMatrix implements the graph Iterator interface.

func NewAdjacencyMatrix

func NewAdjacencyMatrix(m matrix.Constructor[int]) AdjacencyMatrix

NewAdjacencyMatrix returns an AdjacencyMatrix backed by the specified matrix implementation. If nil, defaults to matrix.NewBit.

A matrix implementation with a wider data type is needed to implement an AdjacencyMatrix for a multigraph e.g. matrix.NewGrid.

func (AdjacencyMatrix) Calculate

func (m AdjacencyMatrix) Calculate(g Iterator)

Calculate computes the adjacency matrix for a finite graph g. The created adjacency matrix is itself a graph implementing [Interface] containing only the vertexes of g with at least one inward or outward edge.

Each vertex index in the adjacency matrix corresponds to a matching index in graph g. Once an adjacency matrix has been constructed, it is not affected by future changes to graph g.

func (AdjacencyMatrix) Clear

func (m AdjacencyMatrix) Clear()

func (AdjacencyMatrix) CountEdges

func (m AdjacencyMatrix) CountEdges() int

CountEdges returns the total number of edges in the adjacency matrix.

func (AdjacencyMatrix) Degree

func (m AdjacencyMatrix) Degree(source VertexIndex) int

Degree returns the number of edges either to or from a specific vertex, including self-loops which are counted (by definition) as two edges.

This is computed with O(n) complexity; construct a DegreeMatrix for constant time.

func (AdjacencyMatrix) Edges

func (m AdjacencyMatrix) Edges(source VertexIndex) func() (VertexIndex, int, bool)

Edges implements the graph Iterator Edges method.

func (AdjacencyMatrix) Get

func (m AdjacencyMatrix) Get(source, target VertexIndex) int

Get returns the number of directed edges from a source vertex to a target vertex (including self-loops, if any).

func (AdjacencyMatrix) Indegree

func (m AdjacencyMatrix) Indegree(target VertexIndex) int

Indegree returns the number of directed edges from any vertex to the specific target vertex (including self-loops from the target to itself).

This is computed with O(n) complexity; construct a DegreeMatrix for constant time.

func (AdjacencyMatrix) Matrix

func (m AdjacencyMatrix) Matrix() matrix.Interface[int]

Matrix returns a pointer to the underlying matrix.Interface (of type int).

func (AdjacencyMatrix) Outdegree

func (m AdjacencyMatrix) Outdegree(source VertexIndex) int

Outdegree returns the number of directed edges from a specific source vertex to any other vertex (including self-loops from the source to itself).

This is computed with O(n) complexity; construct a DegreeMatrix for constant time.

func (AdjacencyMatrix) Resize

func (m AdjacencyMatrix) Resize(width int)

Resize updates the adjacency matrix, if necessary, so that it has at least capacity for width elements in each dimension. It reuses underlying memory from the existing matrix where possible. Note that this will clear the matrix.

func (AdjacencyMatrix) Set

func (m AdjacencyMatrix) Set(source, target VertexIndex, count int)

Set stores the number of directed edges from a source vertex to a target vertex (including self-loops, if any).

func (AdjacencyMatrix) Vertexes

func (m AdjacencyMatrix) Vertexes() func() (VertexIndex, bool)

Vertexes implements the graph Iterator Vertexes method. Every vertex will have at least one edge (in either direction).

func (AdjacencyMatrix) Width

func (m AdjacencyMatrix) Width() int

Width returns the width of the adjacency matrix. Note that if VertexIndexes are sparsely distributed, width may be greater the number of vertexes produced by iteration.

type DegreeMatrix

type DegreeMatrix struct {
	// contains filtered or unexported fields
}

DegreeMatrix represents the number of edges on a vertex (to or from any other vertex in aggregate). This may be the in-, out-, or undirected-, degree.

Once built, a degree matrix can be queried in constant time.

Values are indexed by VertexIndex. A DegreeMatrix is a diagonal-matrix; values off the diagonal are zero.

func NewDegreeMatrix

func NewDegreeMatrix() DegreeMatrix

NewDegreeMatrix returns a new degree matrix of undefined size.

func (DegreeMatrix) Calculate

func (m DegreeMatrix) Calculate(g func() VertexIterator, degree func(index VertexIndex) int)

Calculate computes the degree matrix from any vertex iterator and any degree function.

For example, an in-, out-, or undirected degree matrix may be constructed from an AdjacencyMatrix using its Vertexes method and its indegree, outdegree, and degree methods respectively.

Each vertex index in the degree matrix corresponds to the matching index in the input graph g. Once a degree matrix has been constructed, it is not affected by future changes to g.

func (DegreeMatrix) Clear

func (m DegreeMatrix) Clear()

func (DegreeMatrix) CountEdges

func (m DegreeMatrix) CountEdges() int

CountEdges returns the total number of edges in the degree matrix.

func (DegreeMatrix) Get

func (m DegreeMatrix) Get(source VertexIndex) int

Get returns the (in-, out-, or undirected) degree of the given vertex.

func (DegreeMatrix) Matrix

func (m DegreeMatrix) Matrix() matrix.Interface[int]

Matrix returns a pointer to the underlying matrix.Interface (of type int).

func (DegreeMatrix) Resize

func (m DegreeMatrix) Resize(width int)

Resize updates the degree matrix, if necessary, so that it has at least capacity for width elements in each dimension. It reuses underlying memory from the existing matrix where possible. Note that this will clear the matrix.

func (DegreeMatrix) Set

func (m DegreeMatrix) Set(source VertexIndex, count int)

Set stores the (in-, out-, or undirected) degree of the given vertex.

func (DegreeMatrix) Width

func (m DegreeMatrix) Width() int

Width returns the width of the degree matrix. Note that if VertexIndexes are sparsely distributed, width may be greater the number of vertexes produced by iteration.

type EdgeIndex

type EdgeIndex int

EdgeIndex optionally uniquely identifies a directed edge from a source vertex to a target vertex, for a unique (source, target) vertex pair in a multigraph. Edge indexes do not have to be consecutive or sorted, may be sparse, and do not have to start at zero, but it may be more efficient where that is the case.

An EdgeIndex is only particularly useful in a multigraph. In normal graphs, each edge from a source can always be uniquely identified by the target. In this case, EdgeIndex is equal to the VertexIndex of the target.

In some graph representations, unique information about each edge is lost. By convention, use -1 to represent a missing index.

Note that while edges are always directed, an undirected graph can be implemented efficiently by representing an undirected edge as a directed edge from a lower-indexed vertex to a higher-indexed vertex, or vice-versa.

type EdgeIterator

type EdgeIterator = func() (target VertexIndex, count int, ok bool)

type FilterEdges

type FilterEdges struct {
	Parent Iterator
	Filter func(source VertexIndex, target VertexIndex) bool
}

FilterEdges implements the graph Iterator interface and represents a subgraph of a parent graph of only edges that satisfy the given filter function.

The Iterator implemented by FilterEdges is only a view of the parent graph and is computed on-the-fly as the parent changes.

func (FilterEdges) Edges

func (f FilterEdges) Edges(source VertexIndex) func() (VertexIndex, int, bool)

func (FilterEdges) Vertexes

func (f FilterEdges) Vertexes() func() (VertexIndex, bool)

type FilterVertexes

type FilterVertexes struct {
	Parent Iterator
	Filter func(vertex VertexIndex) bool
}

FilterVertexes implements the graph Iterator interface and represents a subgraph of a parent graph of only vertexes that satisfy the given filter function.

The Iterator implemented by FilterVertexes is only a view of the parent graph and is computed on-the-fly as the parent changes.

func (FilterVertexes) Edges

func (f FilterVertexes) Edges(source VertexIndex) func() (VertexIndex, int, bool)

func (FilterVertexes) Vertexes

func (f FilterVertexes) Vertexes() func() (VertexIndex, bool)

type Iterator

type Iterator interface {
	Vertexes() VertexIterator
	Edges(source VertexIndex) EdgeIterator
}

Iterator is the basic interface that a graph data type must implement to be able to use many of the algorithms in this package.

The mapping between this general-purpose graph interface and a given graph implementation is achieved through iterator-style functions that generate a VertexIndex. The Edges iterator function generates a count of the number of edges between the two vertexes. If the graph is not a multigraph, this count will always be one. Each iterator-style function returns false as the last argument iff they have completed.

A graph can have many equivalent implementations: a data structure, perhaps an adjacency list or an adjacency matrix; or an algorithm, for example an algorithm that generates an infinite line of vertexes with a directed edge from each vertex to its successor.

Graphs may contain cycles, loops, be disjoint, be multigraphs, be infinite, etc. without restriction except where noted. These properties can be tested for by algorithms implemented in this package.

type Number

type Number interface {
	constraints.Integer | constraints.Float
}

Number represents any integer or float type.

type VertexIndex

type VertexIndex int

VertexIndex is a non-negative index that uniquely identifies each vertex in a graph. Vertex indexes do not have to be consecutive or sorted, may be sparse, and do not have to start at zero, but it may be more efficient where that is the case.

type VertexIterator

type VertexIterator = func() (vertex VertexIndex, ok bool)

func Leaves

func Leaves(
	vertexes func() VertexIterator,
	indegree func(VertexIndex) int,
	outdegree func(index VertexIndex) int,
) VertexIterator

Leaves returns an iterator that generates indexes of vertexes that have exactly one parent and exactly zero children in a directed graph.

A degree function can be made using an AdjacencyMatrix or DegreeMatrix.

func Roots

func Roots(
	vertexes func() VertexIterator,
	indegree func(VertexIndex) int,
) VertexIterator

Roots returns an iterator that generates only the indexes of vertexes that have no parents i.e. root vertexes in a directed graph.

A degree function can be made using an AdjacencyMatrix or DegreeMatrix.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL