act/vendor/github.com/soniakeys/graph/adj.go

407 lines
12 KiB
Go
Raw Normal View History

// Copyright 2014 Sonia Keys
// License MIT: https://opensource.org/licenses/MIT
package graph
// adj.go contains methods on AdjacencyList and LabeledAdjacencyList.
//
// AdjacencyList methods are placed first and are alphabetized.
// LabeledAdjacencyList methods follow, also alphabetized.
// Only exported methods need be alphabetized; non-exported methods can
// be left near their use.
import (
"sort"
"github.com/soniakeys/bits"
)
// NI is a "node int"
//
// It is a node number or node ID. NIs are used extensively as slice indexes.
// NIs typically account for a significant fraction of the memory footprint of
// a graph.
type NI int32
// AnyParallel identifies if a graph contains parallel arcs, multiple arcs
// that lead from a node to the same node.
//
// If the graph has parallel arcs, the results fr and to represent an example
// where there are parallel arcs from node `fr` to node `to`.
//
// If there are no parallel arcs, the method returns false -1 -1.
//
// Multiple loops on a node count as parallel arcs.
//
// See also alt.AnyParallelMap, which can perform better for some large
// or dense graphs.
func (g AdjacencyList) AnyParallel() (has bool, fr, to NI) {
var t []NI
for n, to := range g {
if len(to) == 0 {
continue
}
// different code in the labeled version, so no code gen.
t = append(t[:0], to...)
sort.Slice(t, func(i, j int) bool { return t[i] < t[j] })
t0 := t[0]
for _, to := range t[1:] {
if to == t0 {
return true, NI(n), t0
}
t0 = to
}
}
return false, -1, -1
}
// Complement returns the arc-complement of a simple graph.
//
// The result will have an arc for every pair of distinct nodes where there
// is not an arc in g. The complement is valid for both directed and
// undirected graphs. If g is undirected, the complement will be undirected.
// The result will always be a simple graph, having no loops or parallel arcs.
func (g AdjacencyList) Complement() AdjacencyList {
c := make(AdjacencyList, len(g))
b := bits.New(len(g))
for n, to := range g {
b.ClearAll()
for _, to := range to {
b.SetBit(int(to), 1)
}
b.SetBit(n, 1)
ct := make([]NI, len(g)-b.OnesCount())
i := 0
b.IterateZeros(func(to int) bool {
ct[i] = NI(to)
i++
return true
})
c[n] = ct
}
return c
}
// IsUndirected returns true if g represents an undirected graph.
//
// Returns true when all non-loop arcs are paired in reciprocal pairs.
// Otherwise returns false and an example unpaired arc.
func (g AdjacencyList) IsUndirected() (u bool, from, to NI) {
// similar code in dot/writeUndirected
unpaired := make(AdjacencyList, len(g))
for fr, to := range g {
arc: // for each arc in g
for _, to := range to {
if to == NI(fr) {
continue // loop
}
// search unpaired arcs
ut := unpaired[to]
for i, u := range ut {
if u == NI(fr) { // found reciprocal
last := len(ut) - 1
ut[i] = ut[last]
unpaired[to] = ut[:last]
continue arc
}
}
// reciprocal not found
unpaired[fr] = append(unpaired[fr], to)
}
}
for fr, to := range unpaired {
if len(to) > 0 {
return false, NI(fr), to[0]
}
}
return true, -1, -1
}
// SortArcLists sorts the arc lists of each node of receiver g.
//
// Nodes are not relabeled and the graph remains equivalent.
func (g AdjacencyList) SortArcLists() {
for _, to := range g {
sort.Slice(to, func(i, j int) bool { return to[i] < to[j] })
}
}
// ------- Labeled methods below -------
// ArcsAsEdges constructs an edge list with an edge for each arc, including
// reciprocals.
//
// This is a simple way to construct an edge list for algorithms that allow
// the duplication represented by the reciprocal arcs. (e.g. Kruskal)
//
// See also LabeledUndirected.Edges for the edge list without this duplication.
func (g LabeledAdjacencyList) ArcsAsEdges() (el []LabeledEdge) {
for fr, to := range g {
for _, to := range to {
el = append(el, LabeledEdge{Edge{NI(fr), to.To}, to.Label})
}
}
return
}
// DistanceMatrix constructs a distance matrix corresponding to the arcs
// of graph g and weight function w.
//
// An arc from f to t with WeightFunc return w is represented by d[f][t] == w.
// In case of parallel arcs, the lowest weight is stored. The distance from
// any node to itself d[n][n] is 0, unless the node has a loop with a negative
// weight. If g has no arc from f to distinct t, +Inf is stored for d[f][t].
//
// The returned DistanceMatrix is suitable for DistanceMatrix.FloydWarshall.
func (g LabeledAdjacencyList) DistanceMatrix(w WeightFunc) (d DistanceMatrix) {
d = newDM(len(g))
for fr, to := range g {
for _, to := range to {
// < to pick min of parallel arcs (also nicely ignores NaN)
if wt := w(to.Label); wt < d[fr][to.To] {
d[fr][to.To] = wt
}
}
}
return
}
// HasArcLabel returns true if g has any arc from node `fr` to node `to`
// with label `l`.
//
// Also returned is the index within the slice of arcs from node `fr`.
// If no arc from `fr` to `to` with label `l` is present, HasArcLabel returns
// false, -1.
func (g LabeledAdjacencyList) HasArcLabel(fr, to NI, l LI) (bool, int) {
t := Half{to, l}
for x, h := range g[fr] {
if h == t {
return true, x
}
}
return false, -1
}
// AnyParallel identifies if a graph contains parallel arcs, multiple arcs
// that lead from a node to the same node.
//
// If the graph has parallel arcs, the results fr and to represent an example
// where there are parallel arcs from node `fr` to node `to`.
//
// If there are no parallel arcs, the method returns -1 -1.
//
// Multiple loops on a node count as parallel arcs.
//
// See also alt.AnyParallelMap, which can perform better for some large
// or dense graphs.
func (g LabeledAdjacencyList) AnyParallel() (has bool, fr, to NI) {
var t []NI
for n, to := range g {
if len(to) == 0 {
continue
}
// slightly different code needed here compared to AdjacencyList
t = t[:0]
for _, to := range to {
t = append(t, to.To)
}
sort.Slice(t, func(i, j int) bool { return t[i] < t[j] })
t0 := t[0]
for _, to := range t[1:] {
if to == t0 {
return true, NI(n), t0
}
t0 = to
}
}
return false, -1, -1
}
// AnyParallelLabel identifies if a graph contains parallel arcs with the same
// label.
//
// If the graph has parallel arcs with the same label, the results fr and to
// represent an example where there are parallel arcs from node `fr`
// to node `to`.
//
// If there are no parallel arcs, the method returns false -1 Half{}.
//
// Multiple loops on a node count as parallel arcs.
func (g LabeledAdjacencyList) AnyParallelLabel() (has bool, fr NI, to Half) {
var t []Half
for n, to := range g {
if len(to) == 0 {
continue
}
// slightly different code needed here compared to AdjacencyList
t = t[:0]
for _, to := range to {
t = append(t, to)
}
sort.Slice(t, func(i, j int) bool {
return t[i].To < t[j].To ||
t[i].To == t[j].To && t[i].Label < t[j].Label
})
t0 := t[0]
for _, to := range t[1:] {
if to == t0 {
return true, NI(n), t0
}
t0 = to
}
}
return false, -1, Half{}
}
// IsUndirected returns true if g represents an undirected graph.
//
// Returns true when all non-loop arcs are paired in reciprocal pairs with
// matching labels. Otherwise returns false and an example unpaired arc.
//
// Note the requirement that reciprocal pairs have matching labels is
// an additional test not present in the otherwise equivalent unlabeled version
// of IsUndirected.
func (g LabeledAdjacencyList) IsUndirected() (u bool, from NI, to Half) {
// similar code in LabeledAdjacencyList.Edges
unpaired := make(LabeledAdjacencyList, len(g))
for fr, to := range g {
arc: // for each arc in g
for _, to := range to {
if to.To == NI(fr) {
continue // loop
}
// search unpaired arcs
ut := unpaired[to.To]
for i, u := range ut {
if u.To == NI(fr) && u.Label == to.Label { // found reciprocal
last := len(ut) - 1
ut[i] = ut[last]
unpaired[to.To] = ut[:last]
continue arc
}
}
// reciprocal not found
unpaired[fr] = append(unpaired[fr], to)
}
}
for fr, to := range unpaired {
if len(to) > 0 {
return false, NI(fr), to[0]
}
}
return true, -1, to
}
// ArcLabels constructs the multiset of LIs present in g.
func (g LabeledAdjacencyList) ArcLabels() map[LI]int {
s := map[LI]int{}
for _, to := range g {
for _, to := range to {
s[to.Label]++
}
}
return s
}
// NegativeArc returns true if the receiver graph contains a negative arc.
func (g LabeledAdjacencyList) NegativeArc(w WeightFunc) bool {
for _, nbs := range g {
for _, nb := range nbs {
if w(nb.Label) < 0 {
return true
}
}
}
return false
}
// ParallelArcsLabel identifies all arcs from node `fr` to node `to` with label `l`.
//
// The returned slice contains an element for each arc from node `fr` to node `to`
// with label `l`. The element value is the index within the slice of arcs from node
// `fr`.
//
// See also the method HasArcLabel, which stops after finding a single arc.
func (g LabeledAdjacencyList) ParallelArcsLabel(fr, to NI, l LI) (p []int) {
t := Half{to, l}
for x, h := range g[fr] {
if h == t {
p = append(p, x)
}
}
return
}
// Unlabeled constructs the unlabeled graph corresponding to g.
func (g LabeledAdjacencyList) Unlabeled() AdjacencyList {
a := make(AdjacencyList, len(g))
for n, nbs := range g {
to := make([]NI, len(nbs))
for i, nb := range nbs {
to[i] = nb.To
}
a[n] = to
}
return a
}
// WeightedArcsAsEdges constructs a WeightedEdgeList object from the receiver.
//
// Internally it calls g.ArcsAsEdges() to obtain the Edges member.
// See LabeledAdjacencyList.ArcsAsEdges().
func (g LabeledAdjacencyList) WeightedArcsAsEdges(w WeightFunc) *WeightedEdgeList {
return &WeightedEdgeList{
Order: g.Order(),
WeightFunc: w,
Edges: g.ArcsAsEdges(),
}
}
// WeightedInDegree computes the weighted in-degree of each node in g
// for a given weight function w.
//
// The weighted in-degree of a node is the sum of weights of arcs going to
// the node.
//
// A weighted degree of a node is often termed the "strength" of a node.
//
// See note for undirected graphs at LabeledAdjacencyList.WeightedOutDegree.
func (g LabeledAdjacencyList) WeightedInDegree(w WeightFunc) []float64 {
ind := make([]float64, len(g))
for _, to := range g {
for _, to := range to {
ind[to.To] += w(to.Label)
}
}
return ind
}
// WeightedOutDegree computes the weighted out-degree of the specified node
// for a given weight function w.
//
// The weighted out-degree of a node is the sum of weights of arcs going from
// the node.
//
// A weighted degree of a node is often termed the "strength" of a node.
//
// Note for undirected graphs, the WeightedOutDegree result for a node will
// equal the WeightedInDegree for the node. You can use WeightedInDegree if
// you have need for the weighted degrees of all nodes or use WeightedOutDegree
// to compute the weighted degrees of individual nodes. In either case loops
// are counted just once, unlike the (unweighted) UndirectedDegree methods.
func (g LabeledAdjacencyList) WeightedOutDegree(n NI, w WeightFunc) (d float64) {
for _, to := range g[n] {
d += w(to.Label)
}
return
}
// More about loops and strength: I didn't see consensus on this especially
// in the case of undirected graphs. Some sources said to add in-degree and
// out-degree, which would seemingly double both loops and non-loops.
// Some said to double loops. Some said sum the edge weights and had no
// comment on loops. R of course makes everything an option. The meaning
// of "strength" where loops exist is unclear. So while I could write an
// UndirectedWeighted degree function that doubles loops but not edges,
// I'm going to just leave this for now.