replace parser with actions/workflow-parser

This commit is contained in:
Casey Lee committed 2019-02-06 22:36:13 -08:00
1 parent 72fbefcedc
commit 5d0a8d26ae
43 files changed
+12749 -634

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+73
View File
@@ -0,0 +1,73 @@
package model
// Configuration is a parsed main.workflow file
type Configuration struct {
Actions []*Action
Workflows []*Workflow
}
// Action represents a single "action" stanza in a .workflow file.
type Action struct {
Identifier string
Uses Uses
Runs, Args ActionCommand
Needs []string
Env map[string]string
Secrets []string
}
// ActionCommand represents the optional "runs" and "args" attributes.
// Each one takes one of two forms:
// - runs="entrypoint arg1 arg2 ..."
// - runs=[ "entrypoint", "arg1", "arg2", ... ]
// If the user uses the string form, "Raw" contains that value, and
// "Parsed" contains an array of the string value split at whitespace.
// If the user uses the array form, "Raw" is empty, and "Parsed" contains
// the array.
type ActionCommand struct {
Raw string
Parsed []string
}
// Workflow represents a single "workflow" stanza in a .workflow file.
type Workflow struct {
Identifier string
On string
Resolves []string
}
// GetAction looks up action by identifier.
//
// If the action is not found, nil is returned.
func (c *Configuration) GetAction(id string) *Action {
for _, action := range c.Actions {
if action.Identifier == id {
return action
}
}
return nil
}
// GetWorkflow looks up a workflow by identifier.
//
// If the workflow is not found, nil is returned.
func (c *Configuration) GetWorkflow(id string) *Workflow {
for _, workflow := range c.Workflows {
if workflow.Identifier == id {
return workflow
}
}
return nil
}
// GetWorkflows gets all Workflow structures that match a given type of event.
// e.g., GetWorkflows("push")
func (c *Configuration) GetWorkflows(eventType string) []*Workflow {
var ret []*Workflow
for _, workflow := range c.Workflows {
if IsMatchingEventType(workflow.On, eventType) {
ret = append(ret, workflow)
}
}
return ret
}
+55
View File
@@ -0,0 +1,55 @@
package model
import (
"strings"
)
// IsAllowedEventType returns true if the event type is supported.
func IsAllowedEventType(eventType string) bool {
_, ok := eventTypeWhitelist[strings.ToLower(eventType)]
return ok
}
// IsMatchingEventType checks to see if the "flowOn" string from a flow's on attribute matches the incoming webhook of type eventType.
func IsMatchingEventType(flowOn, eventType string) bool {
return strings.ToLower(flowOn) == strings.ToLower(eventType)
}
// https://developer.github.com/actions/creating-workflows/workflow-configuration-options/#events-supported-in-workflow-files
var eventTypeWhitelist = map[string]struct{}{
"check_run": {},
"check_suite": {},
"commit_comment": {},
"create": {},
"delete": {},
"deployment": {},
"deployment_status": {},
"fork": {},
"gollum": {},
"issue_comment": {},
"issues": {},
"label": {},
"member": {},
"milestone": {},
"page_build": {},
"project_card": {},
"project_column": {},
"project": {},
"public": {},
"pull_request_review_comment": {},
"pull_request_review": {},
"pull_request": {},
"push": {},
"release": {},
"repository_dispatch": {},
"status": {},
"watch": {},
}
func AddAllowedEventType(s string) {
eventTypeWhitelist[s] = struct{}{}
}
func RemoveAllowedEventType(s string) {
delete(eventTypeWhitelist, s)
}
+57
View File
@@ -0,0 +1,57 @@
package model
import (
"fmt"
)
type Uses interface {
fmt.Stringer
isUses()
}
// UsesDockerImage represents `uses = "docker://<image>"`
type UsesDockerImage struct {
Image string
}
// UsesRepository represents `uses = "<owner>/<repo>[/<path>]@<ref>"`
type UsesRepository struct {
Repository string
Path string
Ref string
}
// UsesPath represents `uses = "./<path>"`
type UsesPath struct {
Path string
}
// UsesInvalid represents any invalid `uses = "<raw>"` value
type UsesInvalid struct {
Raw string
}
func (u *UsesDockerImage) isUses() {}
func (u *UsesRepository) isUses() {}
func (u *UsesPath) isUses() {}
func (u *UsesInvalid) isUses() {}
func (u *UsesDockerImage) String() string {
return fmt.Sprintf("docker://%s", u.Image)
}
func (u *UsesRepository) String() string {
if u.Path == "" {
return fmt.Sprintf("%s@%s", u.Repository, u.Ref)
}
return fmt.Sprintf("%s/%s@%s", u.Repository, u.Path, u.Ref)
}
func (u *UsesPath) String() string {
return fmt.Sprintf("./%s", u.Path)
}
func (u *UsesInvalid) String() string {
return u.Raw
}
+129
View File
@@ -0,0 +1,129 @@
package parser
import (
"fmt"
"sort"
"strconv"
"strings"
"github.com/actions/workflow-parser/model"
)
type ParserError struct {
message string
Errors ErrorList
Actions []*model.Action
Workflows []*model.Workflow
}
func (p *ParserError) Error() string {
return p.message
}
// Error represents an error identified by the parser, either syntactic
// (HCL) or semantic (.workflow) in nature. There are fields for location
// (File, Line, Column), severity, and base error string. The `Error()`
// function on this type concatenates whatever bits of the location are
// available with the message. The severity is only used for filtering.
type Error struct {
message string
Pos ErrorPos
Severity Severity
}
// ErrorPos represents the location of an error in a user's workflow
// file(s).
type ErrorPos struct {
File string
Line int
Column int
}
// newFatal creates a new error at the FATAL level, indicating that the
// file is so broken it should not be displayed.
func newFatal(pos ErrorPos, format string, a ...interface{}) *Error {
return &Error{
message: fmt.Sprintf(format, a...),
Pos: pos,
Severity: FATAL,
}
}
// newError creates a new error at the ERROR level, indicating that the
// file can be displayed but cannot be run.
func newError(pos ErrorPos, format string, a ...interface{}) *Error {
return &Error{
message: fmt.Sprintf(format, a...),
Pos: pos,
Severity: ERROR,
}
}
// newWarning creates a new error at the WARNING level, indicating that
// the file might be runnable but might not execute as intended.
func newWarning(pos ErrorPos, format string, a ...interface{}) *Error {
return &Error{
message: fmt.Sprintf(format, a...),
Pos: pos,
Severity: WARNING,
}
}
func (e *Error) Error() string {
var sb strings.Builder
if e.Pos.Line != 0 {
sb.WriteString("Line ") // nolint: errcheck
sb.WriteString(strconv.Itoa(e.Pos.Line)) // nolint: errcheck
sb.WriteString(": ") // nolint: errcheck
}
if sb.Len() > 0 {
sb.WriteString(e.message) // nolint: errcheck
return sb.String()
}
return e.message
}
const (
_ = iota
// WARNING indicates a mistake that might affect correctness
WARNING
// ERROR indicates a mistake that prevents execution of any workflows in the file
ERROR
// FATAL indicates a mistake that prevents even drawing the file
FATAL
)
// Severity represents the level of an error encountered while parsing a
// workflow file. See the comments for WARNING, ERROR, and FATAL, above.
type Severity int
// FirstError searches a Configuration for the first error at or above a
// given severity level. Checking the return value against nil is a good
// way to see if the file has any errors at or above the given severity.
// A caller intending to execute the file might check for
// `errors.FirstError(parser.WARNING)`, while a caller intending to
// display the file might check for `errors.FirstError(parser.FATAL)`.
func (errors ErrorList) FirstError(severity Severity) error {
for _, e := range errors {
if e.Severity >= severity {
return e
}
}
return nil
}
type ErrorList []*Error
func (a ErrorList) Len() int { return len(a) }
func (a ErrorList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ErrorList) Less(i, j int) bool { return a[i].Pos.Line < a[j].Pos.Line }
// sortErrors sorts the errors reported by the parser. Do this after
// parsing is complete. The sort is stable, so order is preserved within
// a single line: left to right, syntax errors before validation errors.
func (errors ErrorList) sort() {
sort.Stable(errors)
}
+15
View File
@@ -0,0 +1,15 @@
package parser
type OptionFunc func(*parseState)
func WithSuppressWarnings() OptionFunc {
return func(ps *parseState) {
ps.suppressSeverity = WARNING
}
}
func WithSuppressErrors() OptionFunc {
return func(ps *parseState) {
ps.suppressSeverity = ERROR
}
}
File diff suppressed because it is too large. Load diff
+9
View File
@@ -0,0 +1,9 @@
sudo: false
language: go
go: master
before_script:
- go vet
- go get github.com/client9/misspell/cmd/misspell
- misspell -error *
- go get github.com/soniakeys/vetc
- vetc
+463
View File
@@ -0,0 +1,463 @@
// Copyright 2017 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
// Bits implements methods on a bit array type.
//
// The Bits type holds a fixed size array of bits, numbered consecutively
// from zero. Some set-like operations are possible, but the API is more
// array-like or register-like.
package bits
import (
"fmt"
mb "math/bits"
)
// Bits holds a fixed number of bits.
//
// Bit number 0 is stored in the LSB, or bit 0, of the word indexed at 0.
//
// When Num is not a multiple of 64, the last element of Bits will hold some
// bits beyond Num. These bits are undefined. They are not required to be
// zero but do not have any meaning. Bits methods are not required to leave
// them undisturbed.
type Bits struct {
Num int // number of bits
Bits []uint64
}
// New constructs a Bits value with the given number of bits.
//
// It panics if num is negative.
func New(num int) Bits {
if num < 0 {
panic("negative number of bits")
}
return Bits{num, make([]uint64, (num+63)>>6)}
}
// NewGivens constructs a Bits value with the given bits nums set to 1.
//
// The number of bits will be just enough to hold the largest bit value
// listed. That is, the number of bits will be the max bit number plus one.
//
// It panics if any bit number is negative.
func NewGivens(nums ...int) Bits {
max := -1
for _, p := range nums {
if p > max {
max = p
}
}
b := New(max + 1)
for _, p := range nums {
b.SetBit(p, 1)
}
return b
}
// AllOnes returns true if all Num bits are 1.
func (b Bits) AllOnes() bool {
last := len(b.Bits) - 1
for _, w := range b.Bits[:last] {
if w != ^uint64(0) {
return false
}
}
return ^b.Bits[last]<<uint(64*len(b.Bits)-b.Num) == 0
}
// AllZeros returns true if all Num bits are 0.
func (b Bits) AllZeros() bool {
last := len(b.Bits) - 1
for _, w := range b.Bits[:last] {
if w != 0 {
return false
}
}
return b.Bits[last]<<uint(64*len(b.Bits)-b.Num) == 0
}
// And sets z = x & y.
//
// It panics if x and y do not have the same Num.
func (z *Bits) And(x, y Bits) {
if x.Num != y.Num {
panic("arguments have different number of bits")
}
if z.Num != x.Num {
*z = New(x.Num)
}
for i, w := range y.Bits {
z.Bits[i] = x.Bits[i] & w
}
}
// AndNot sets z = x &^ y.
//
// It panics if x and y do not have the same Num.
func (z *Bits) AndNot(x, y Bits) {
if x.Num != y.Num {
panic("arguments have different number of bits")
}
if z.Num != x.Num {
*z = New(x.Num)
}
for i, w := range y.Bits {
z.Bits[i] = x.Bits[i] &^ w
}
}
// Bit returns the value of the n'th bit of receiver b.
func (b Bits) Bit(n int) int {
if n < 0 || n >= b.Num {
panic("bit number out of range")
}
return int(b.Bits[n>>6] >> uint(n&63) & 1)
}
// ClearAll sets all bits to 0.
func (b Bits) ClearAll() {
for i := range b.Bits {
b.Bits[i] = 0
}
}
// ClearBits sets the given bits to 0 in receiver b.
//
// Other bits of b are left unchanged.
//
// It panics if any bit number is out of range.
// That is, negative or >= the number of bits.
func (b Bits) ClearBits(nums ...int) {
for _, p := range nums {
b.SetBit(p, 0)
}
}
// Equal returns true if all Num bits of a and b are equal.
//
// It panics if a and b have different Num.
func (a Bits) Equal(b Bits) bool {
if a.Num != b.Num {
panic("receiver and argument have different number of bits")
}
if a.Num == 0 {
return true
}
last := len(a.Bits) - 1
for i, w := range a.Bits[:last] {
if w != b.Bits[i] {
return false
}
}
return (a.Bits[last]^b.Bits[last])<<uint(len(a.Bits)*64-a.Num) == 0
}
// IterateOnes calls visitor function v for each bit with a value of 1, in order
// from lowest bit to highest bit.
//
// Iteration continues to the highest bit as long as v returns true.
// It stops if v returns false.
//
// IterateOnes returns true normally. It returns false if v returns false.
//
// IterateOnes may not be sensitive to changes if bits are changed during
// iteration, by the vistor function for example.
// See OneFrom for an iteration method sensitive to changes during iteration.
func (b Bits) IterateOnes(v func(int) bool) bool {
for x, w := range b.Bits {
if w != 0 {
t := mb.TrailingZeros64(w)
i := t // index in w of next 1 bit
for {
n := x<<6 | i
if n >= b.Num {
return true
}
if !v(x<<6 | i) {
return false
}
w >>= uint(t + 1)
if w == 0 {
break
}
t = mb.TrailingZeros64(w)
i += 1 + t
}
}
}
return true
}
// IterateZeros calls visitor function v for each bit with a value of 0,
// in order from lowest bit to highest bit.
//
// Iteration continues to the highest bit as long as v returns true.
// It stops if v returns false.
//
// IterateZeros returns true normally. It returns false if v returns false.
//
// IterateZeros may not be sensitive to changes if bits are changed during
// iteration, by the vistor function for example.
// See ZeroFrom for an iteration method sensitive to changes during iteration.
func (b Bits) IterateZeros(v func(int) bool) bool {
for x, w := range b.Bits {
w = ^w
if w != 0 {
t := mb.TrailingZeros64(w)
i := t // index in w of next 1 bit
for {
n := x<<6 | i
if n >= b.Num {
return true
}
if !v(x<<6 | i) {
return false
}
w >>= uint(t + 1)
if w == 0 {
break
}
t = mb.TrailingZeros64(w)
i += 1 + t
}
}
}
return true
}
// Not sets receiver z to the complement of b.
func (z *Bits) Not(b Bits) {
if z.Num != b.Num {
*z = New(b.Num)
}
for i, w := range b.Bits {
z.Bits[i] = ^w
}
}
// OneFrom returns the number of the first 1 bit at or after (from) bit num.
//
// It returns -1 if there is no one bit at or after num.
//
// This provides one way to iterate over one bits.
// To iterate over the one bits, call OneFrom with n = 0 to get the the first
// one bit, then call with the result + 1 to get successive one bits.
// Unlike the Iterate method, this technique is stateless and so allows
// bits to be changed between successive calls.
//
// There is no panic for calling OneFrom with an argument >= b.Num.
// In this case OneFrom simply returns -1.
//
// See also Iterate.
func (b Bits) OneFrom(num int) int {
if num >= b.Num {
return -1
}
x := num >> 6
// test for 1 in this word at or after n
if wx := b.Bits[x] >> uint(num&63); wx != 0 {
num += mb.TrailingZeros64(wx)
if num >= b.Num {
return -1
}
return num
}
x++
for y, wy := range b.Bits[x:] {
if wy != 0 {
num = (x+y)<<6 | mb.TrailingZeros64(wy)
if num >= b.Num {
return -1
}
return num
}
}
return -1
}
// Or sets z = x | y.
//
// It panics if x and y do not have the same Num.
func (z *Bits) Or(x, y Bits) {
if x.Num != y.Num {
panic("arguments have different number of bits")
}
if z.Num != x.Num {
*z = New(x.Num)
}
for i, w := range y.Bits {
z.Bits[i] = x.Bits[i] | w
}
}
// OnesCount returns the number of 1 bits.
func (b Bits) OnesCount() (c int) {
if b.Num == 0 {
return 0
}
last := len(b.Bits) - 1
for _, w := range b.Bits[:last] {
c += mb.OnesCount64(w)
}
c += mb.OnesCount64(b.Bits[last] << uint(len(b.Bits)*64-b.Num))
return
}
// Set sets the bits of z to the bits of x.
func (z *Bits) Set(b Bits) {
if z.Num != b.Num {
*z = New(b.Num)
}
copy(z.Bits, b.Bits)
}
// SetAll sets z to have all 1 bits.
func (b Bits) SetAll() {
for i := range b.Bits {
b.Bits[i] = ^uint64(0)
}
}
// SetBit sets the n'th bit to x, where x is a 0 or 1.
//
// It panics if n is out of range
func (b Bits) SetBit(n, x int) {
if n < 0 || n >= b.Num {
panic("bit number out of range")
}
if x == 0 {
b.Bits[n>>6] &^= 1 << uint(n&63)
} else {
b.Bits[n>>6] |= 1 << uint(n&63)
}
}
// SetBits sets the given bits to 1 in receiver b.
//
// Other bits of b are left unchanged.
//
// It panics if any bit number is out of range, negative or >= the number
// of bits.
func (b Bits) SetBits(nums ...int) {
for _, p := range nums {
b.SetBit(p, 1)
}
}
// Single returns true if b has exactly one 1 bit.
func (b Bits) Single() bool {
// like OnesCount, but stop as soon as two are found
if b.Num == 0 {
return false
}
c := 0
last := len(b.Bits) - 1
for _, w := range b.Bits[:last] {
c += mb.OnesCount64(w)
if c > 1 {
return false
}
}
c += mb.OnesCount64(b.Bits[last] << uint(len(b.Bits)*64-b.Num))
return c == 1
}
// Slice returns a slice with the bit numbers of each 1 bit.
func (b Bits) Slice() (s []int) {
for x, w := range b.Bits {
if w == 0 {
continue
}
t := mb.TrailingZeros64(w)
i := t // index in w of next 1 bit
for {
n := x<<6 | i
if n >= b.Num {
break
}
s = append(s, n)
w >>= uint(t + 1)
if w == 0 {
break
}
t = mb.TrailingZeros64(w)
i += 1 + t
}
}
return
}
// String returns a readable representation.
//
// The returned string is big-endian, with the highest number bit first.
//
// If Num is 0, an empty string is returned.
func (b Bits) String() (s string) {
if b.Num == 0 {
return ""
}
last := len(b.Bits) - 1
for _, w := range b.Bits[:last] {
s = fmt.Sprintf("%064b", w) + s
}
lb := b.Num - 64*last
return fmt.Sprintf("%0*b", lb,
b.Bits[last]&(^uint64(0)>>uint(64-lb))) + s
}
// Xor sets z = x ^ y.
func (z *Bits) Xor(x, y Bits) {
if x.Num != y.Num {
panic("arguments have different number of bits")
}
if z.Num != x.Num {
*z = New(x.Num)
}
for i, w := range y.Bits {
z.Bits[i] = x.Bits[i] ^ w
}
}
// ZeroFrom returns the number of the first 0 bit at or after (from) bit num.
//
// It returns -1 if there is no zero bit at or after num.
//
// This provides one way to iterate over zero bits.
// To iterate over the zero bits, call ZeroFrom with n = 0 to get the the first
// zero bit, then call with the result + 1 to get successive zero bits.
// Unlike the IterateZeros method, this technique is stateless and so allows
// bits to be changed between successive calls.
//
// There is no panic for calling ZeroFrom with an argument >= b.Num.
// In this case ZeroFrom simply returns -1.
//
// See also IterateZeros.
func (b Bits) ZeroFrom(num int) int {
// code much like OneFrom except words are negated before testing
if num >= b.Num {
return -1
}
x := num >> 6
// negate word to test for 0 at or after n
if wx := ^b.Bits[x] >> uint(num&63); wx != 0 {
num += mb.TrailingZeros64(wx)
if num >= b.Num {
return -1
}
return num
}
x++
for y, wy := range b.Bits[x:] {
wy = ^wy
if wy != 0 {
num = (x+y)<<6 | mb.TrailingZeros64(wy)
if num >= b.Num {
return -1
}
return num
}
}
return -1
}
+1
View File
@@ -0,0 +1 @@
module "github.com/soniakeys/bits"
+38
View File
@@ -0,0 +1,38 @@
= Bits
Bits provides methods on a bit array type.
The Bits type holds a fixed size array of bits, numbered consecutively
from zero. Some set-like operations are possible, but the API is more
array-like or register-like.
image:https://godoc.org/github.com/soniakeys/bits?status.svg[link=https://godoc.org/github.com/soniakeys/bits] image:https://travis-ci.org/soniakeys/bits.svg[link=https://travis-ci.org/soniakeys/bits]
== Motivation and history
This package evolved from needs of my library of
https://github.com/soniakeys/graph[graph algorithms]. For graph algorithms
a common need is to store a single bit of information per node in a way that
is both fast and memory efficient. I began by using `big.Int` from the standard
library, then wrapped big.Int in a type. From time to time I considered
other publicly available bit array or bit set packages, such as Will
Fitzgerald's popular https://github.com/willf/bitset[bitset], but there were
always little reasons I preferred my own type and methods. My type that
wrapped `big.Int` met my needs until some simple benchmarks indicated it
might be causing performance problems. Some further experiments supported
this hypothesis so I ran further tests with a prototype bit array written
from scratch. Then satisfied that my custom bit array was solving the graph
performance problems, I decided to move it to a separate package with the
idea it might have more general utility. For the initial version of this
package I did the following:
- implemented a few tests to demonstrate fundamental correctness
- brought over most methods of my type that wrapped big.Int
- changed the index type from the graph-specific node index to a general `int`
- replaced some custom bit-twiddling with use of the new `math/bits` package
in the standard library
- renamed a few methods for clarity
- added a few methods for symmetry
- added a few new methods I had seen a need for in my graph library
- added doc, examples, tests, and more tests for 100% coverage
- added this readme
+2
View File
@@ -0,0 +1,2 @@
*.dot
anecdote/anecdote
+11
View File
@@ -0,0 +1,11 @@
sudo: false
language: go
go:
- "1.9.x"
- master
before_script:
- go tool vet -composites=false -printf=false -shift=false .
- go get github.com/client9/misspell/cmd/misspell
- go get github.com/soniakeys/vetc
- misspell -error * */* */*/*
- vetc
+406
View File
@@ -0,0 +1,406 @@
// 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.
+417
View File
@@ -0,0 +1,417 @@
// Copyright 2014 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
package graph
// adj_RO.go is code generated from adj_cg.go by directives in graph.go.
// Editing adj_cg.go is okay.
// DO NOT EDIT adj_RO.go. The RO is for Read Only.
import (
"errors"
"fmt"
"math/rand"
"github.com/soniakeys/bits"
)
// ArcDensity returns density for an simple directed graph.
//
// See also ArcDensity function.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) ArcDensity() float64 {
return ArcDensity(len(g), g.ArcSize())
}
// ArcSize returns the number of arcs in g.
//
// Note that for an undirected graph without loops, the number of undirected
// edges -- the traditional meaning of graph size -- will be ArcSize()/2.
// On the other hand, if g is an undirected graph that has or may have loops,
// g.ArcSize()/2 is not a meaningful quantity.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) ArcSize() int {
m := 0
for _, to := range g {
m += len(to)
}
return m
}
// BoundsOk validates that all arcs in g stay within the slice bounds of g.
//
// BoundsOk returns true when no arcs point outside the bounds of g.
// Otherwise it returns false and an example arc that points outside of g.
//
// Most methods of this package assume the BoundsOk condition and may
// panic when they encounter an arc pointing outside of the graph. This
// function can be used to validate a graph when the BoundsOk condition
// is unknown.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) BoundsOk() (ok bool, fr NI, to NI) {
for fr, to := range g {
for _, to := range to {
if to < 0 || to >= NI(len(g)) {
return false, NI(fr), to
}
}
}
return true, -1, to
}
// BreadthFirst traverses a directed or undirected graph in breadth
// first order.
//
// Traversal starts at node start and visits the nodes reachable from
// start. The function visit is called for each node visited. Nodes
// not reachable from start are not visited.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also alt.BreadthFirst, a variant with more options, and
// alt.BreadthFirst2, a direction optimizing variant.
func (g AdjacencyList) BreadthFirst(start NI, visit func(NI)) {
v := bits.New(len(g))
v.SetBit(int(start), 1)
visit(start)
var next []NI
for frontier := []NI{start}; len(frontier) > 0; {
for _, n := range frontier {
for _, nb := range g[n] {
if v.Bit(int(nb)) == 0 {
v.SetBit(int(nb), 1)
visit(nb)
next = append(next, nb)
}
}
}
frontier, next = next, frontier[:0]
}
}
// Copy makes a deep copy of g.
// Copy also computes the arc size ma, the number of arcs.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) Copy() (c AdjacencyList, ma int) {
c = make(AdjacencyList, len(g))
for n, to := range g {
c[n] = append([]NI{}, to...)
ma += len(to)
}
return
}
// DepthFirst traverses a directed or undirected graph in depth
// first order.
//
// Traversal starts at node start and visits the nodes reachable from
// start. The function visit is called for each node visited. Nodes
// not reachable from start are not visited.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also alt.DepthFirst, a variant with more options.
func (g AdjacencyList) DepthFirst(start NI, visit func(NI)) {
v := bits.New(len(g))
var f func(NI)
f = func(n NI) {
visit(n)
v.SetBit(int(n), 1)
for _, to := range g[n] {
if v.Bit(int(to)) == 0 {
f(to)
}
}
}
f(start)
}
// HasArc returns true if g has any arc from node `fr` to node `to`.
//
// Also returned is the index within the slice of arcs from node `fr`.
// If no arc from `fr` to `to` is present, HasArc returns false, -1.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also the method ParallelArcs, which finds all parallel arcs from
// `fr` to `to`.
func (g AdjacencyList) HasArc(fr, to NI) (bool, int) {
for x, h := range g[fr] {
if h == to {
return true, x
}
}
return false, -1
}
// AnyLoop identifies if a graph contains a loop, an arc that leads from a
// a node back to the same node.
//
// If g contains a loop, the method returns true and an example of a node
// with a loop. If there are no loops in g, the method returns false, -1.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) AnyLoop() (bool, NI) {
for fr, to := range g {
for _, to := range to {
if NI(fr) == to {
return true, to
}
}
}
return false, -1
}
// AddNode maps a node in a supergraph to a subgraph node.
//
// Argument p must be an NI in supergraph s.Super. AddNode panics if
// p is not a valid node index of s.Super.
//
// AddNode is idempotent in that it does not add a new node to the subgraph if
// a subgraph node already exists mapped to supergraph node p.
//
// The mapped subgraph NI is returned.
func (s *Subgraph) AddNode(p NI) (b NI) {
if int(p) < 0 || int(p) >= s.Super.Order() {
panic(fmt.Sprint("AddNode: NI ", p, " not in supergraph"))
}
if b, ok := s.SubNI[p]; ok {
return b
}
a := s.AdjacencyList
b = NI(len(a))
s.AdjacencyList = append(a, nil)
s.SuperNI = append(s.SuperNI, p)
s.SubNI[p] = b
return
}
// AddArc adds an arc to a subgraph.
//
// Arguments fr, to must be NIs in supergraph s.Super. As with AddNode,
// AddArc panics if fr and to are not valid node indexes of s.Super.
//
// The arc specfied by fr, to must exist in s.Super. Further, the number of
// parallel arcs in the subgraph cannot exceed the number of corresponding
// parallel arcs in the supergraph. That is, each arc already added to the
// subgraph counts against the arcs available in the supergraph. If a matching
// arc is not available, AddArc returns an error.
//
// If a matching arc is available, subgraph nodes are added as needed, the
// subgraph arc is added, and the method returns nil.
func (s *Subgraph) AddArc(fr NI, to NI) error {
// verify supergraph NIs first, but without adding subgraph nodes just yet.
if int(fr) < 0 || int(fr) >= s.Super.Order() {
panic(fmt.Sprint("AddArc: NI ", fr, " not in supergraph"))
}
if int(to) < 0 || int(to) >= s.Super.Order() {
panic(fmt.Sprint("AddArc: NI ", to, " not in supergraph"))
}
// count existing matching arcs in subgraph
n := 0
a := s.AdjacencyList
if bf, ok := s.SubNI[fr]; ok {
if bt, ok := s.SubNI[to]; ok {
// both NIs already exist in subgraph, need to count arcs
bTo := to
bTo = bt
for _, t := range a[bf] {
if t == bTo {
n++
}
}
}
}
// verify matching arcs are available in supergraph
for _, t := range (*s.Super)[fr] {
if t == to {
if n > 0 {
n-- // match existing arc
continue
}
// no more existing arcs need to be matched. nodes can finally
// be added as needed and then the arc can be added.
bf := s.AddNode(fr)
to = s.AddNode(to)
s.AdjacencyList[bf] = append(s.AdjacencyList[bf], to)
return nil // success
}
}
return errors.New("arc not available in supergraph")
}
func (super AdjacencyList) induceArcs(sub map[NI]NI, sup []NI) AdjacencyList {
s := make(AdjacencyList, len(sup))
for b, p := range sup {
var a []NI
for _, to := range super[p] {
if bt, ok := sub[to]; ok {
to = bt
a = append(a, to)
}
}
s[b] = a
}
return s
}
// InduceList constructs a node-induced subgraph.
//
// The subgraph is induced on receiver graph g. Argument l must be a list of
// NIs in receiver graph g. Receiver g becomes the supergraph of the induced
// subgraph.
//
// Duplicate NIs are allowed in list l. The duplicates are effectively removed
// and only a single corresponding node is created in the subgraph. Subgraph
// NIs are mapped in the order of list l, execpt for ignoring duplicates.
// NIs in l that are not in g will panic.
//
// Returned is the constructed Subgraph object containing the induced subgraph
// and the mappings to the supergraph.
func (g *AdjacencyList) InduceList(l []NI) *Subgraph {
sub, sup := mapList(l)
return &Subgraph{
Super: g,
SubNI: sub,
SuperNI: sup,
AdjacencyList: g.induceArcs(sub, sup)}
}
// InduceBits constructs a node-induced subgraph.
//
// The subgraph is induced on receiver graph g. Argument t must be a bitmap
// representing NIs in receiver graph g. Receiver g becomes the supergraph
// of the induced subgraph. NIs in t that are not in g will panic.
//
// Returned is the constructed Subgraph object containing the induced subgraph
// and the mappings to the supergraph.
func (g *AdjacencyList) InduceBits(t bits.Bits) *Subgraph {
sub, sup := mapBits(t)
return &Subgraph{
Super: g,
SubNI: sub,
SuperNI: sup,
AdjacencyList: g.induceArcs(sub, sup)}
}
// IsSimple checks for loops and parallel arcs.
//
// A graph is "simple" if it has no loops or parallel arcs.
//
// IsSimple returns true, -1 for simple graphs. If a loop or parallel arc is
// found, simple returns false and a node that represents a counterexample
// to the graph being simple.
//
// See also separate methods AnyLoop and AnyParallel.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) IsSimple() (ok bool, n NI) {
if lp, n := g.AnyLoop(); lp {
return false, n
}
if pa, n, _ := g.AnyParallel(); pa {
return false, n
}
return true, -1
}
// IsolatedNodes returns a bitmap of isolated nodes in receiver graph g.
//
// An isolated node is one with no arcs going to or from it.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) IsolatedNodes() (i bits.Bits) {
i = bits.New(len(g))
i.SetAll()
for fr, to := range g {
if len(to) > 0 {
i.SetBit(fr, 0)
for _, to := range to {
i.SetBit(int(to), 0)
}
}
}
return
}
// Order is the number of nodes in receiver g.
//
// It is simply a wrapper method for the Go builtin len().
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) Order() int {
// Why a wrapper for len()? Mostly for Directed and Undirected.
// u.Order() is a little nicer than len(u.LabeledAdjacencyList).
return len(g)
}
// ParallelArcs identifies all arcs from node `fr` to node `to`.
//
// The returned slice contains an element for each arc from node `fr` to node `to`.
// The element value is the index within the slice of arcs from node `fr`.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also the method HasArc, which stops after finding a single arc.
func (g AdjacencyList) ParallelArcs(fr, to NI) (p []int) {
for x, h := range g[fr] {
if h == to {
p = append(p, x)
}
}
return
}
// Permute permutes the node labeling of receiver g.
//
// Argument p must be a permutation of the node numbers of the graph,
// 0 through len(g)-1. A permutation returned by rand.Perm(len(g)) for
// example is acceptable.
//
// The graph is permuted in place. The graph keeps the same underlying
// memory but values of the graph representation are permuted to produce
// an isomorphic graph. The node previously labeled 0 becomes p[0] and so on.
// See example (or the code) for clarification.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) Permute(p []int) {
old := append(AdjacencyList{}, g...) // shallow copy
for fr, arcs := range old {
for i, to := range arcs {
arcs[i] = NI(p[to])
}
g[p[fr]] = arcs
}
}
// ShuffleArcLists shuffles the arc lists of each node of receiver g.
//
// For example a node with arcs leading to nodes 3 and 7 might have an
// arc list of either [3 7] or [7 3] after calling this method. The
// connectivity of the graph is not changed. The resulting graph stays
// equivalent but a traversal will encounter arcs in a different
// order.
//
// If Rand r is nil, the rand package default shared source is used.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) ShuffleArcLists(r *rand.Rand) {
ri := rand.Intn
if r != nil {
ri = r.Intn
}
// Knuth-Fisher-Yates
for _, to := range g {
for i := len(to); i > 1; {
j := ri(i)
i--
to[i], to[j] = to[j], to[i]
}
}
}
+417
View File
@@ -0,0 +1,417 @@
// Copyright 2014 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
package graph
// adj_RO.go is code generated from adj_cg.go by directives in graph.go.
// Editing adj_cg.go is okay.
// DO NOT EDIT adj_RO.go. The RO is for Read Only.
import (
"errors"
"fmt"
"math/rand"
"github.com/soniakeys/bits"
)
// ArcDensity returns density for an simple directed graph.
//
// See also ArcDensity function.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) ArcDensity() float64 {
return ArcDensity(len(g), g.ArcSize())
}
// ArcSize returns the number of arcs in g.
//
// Note that for an undirected graph without loops, the number of undirected
// edges -- the traditional meaning of graph size -- will be ArcSize()/2.
// On the other hand, if g is an undirected graph that has or may have loops,
// g.ArcSize()/2 is not a meaningful quantity.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) ArcSize() int {
m := 0
for _, to := range g {
m += len(to)
}
return m
}
// BoundsOk validates that all arcs in g stay within the slice bounds of g.
//
// BoundsOk returns true when no arcs point outside the bounds of g.
// Otherwise it returns false and an example arc that points outside of g.
//
// Most methods of this package assume the BoundsOk condition and may
// panic when they encounter an arc pointing outside of the graph. This
// function can be used to validate a graph when the BoundsOk condition
// is unknown.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) BoundsOk() (ok bool, fr NI, to Half) {
for fr, to := range g {
for _, to := range to {
if to.To < 0 || to.To >= NI(len(g)) {
return false, NI(fr), to
}
}
}
return true, -1, to
}
// BreadthFirst traverses a directed or undirected graph in breadth
// first order.
//
// Traversal starts at node start and visits the nodes reachable from
// start. The function visit is called for each node visited. Nodes
// not reachable from start are not visited.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also alt.BreadthFirst, a variant with more options, and
// alt.BreadthFirst2, a direction optimizing variant.
func (g LabeledAdjacencyList) BreadthFirst(start NI, visit func(NI)) {
v := bits.New(len(g))
v.SetBit(int(start), 1)
visit(start)
var next []NI
for frontier := []NI{start}; len(frontier) > 0; {
for _, n := range frontier {
for _, nb := range g[n] {
if v.Bit(int(nb.To)) == 0 {
v.SetBit(int(nb.To), 1)
visit(nb.To)
next = append(next, nb.To)
}
}
}
frontier, next = next, frontier[:0]
}
}
// Copy makes a deep copy of g.
// Copy also computes the arc size ma, the number of arcs.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) Copy() (c LabeledAdjacencyList, ma int) {
c = make(LabeledAdjacencyList, len(g))
for n, to := range g {
c[n] = append([]Half{}, to...)
ma += len(to)
}
return
}
// DepthFirst traverses a directed or undirected graph in depth
// first order.
//
// Traversal starts at node start and visits the nodes reachable from
// start. The function visit is called for each node visited. Nodes
// not reachable from start are not visited.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also alt.DepthFirst, a variant with more options.
func (g LabeledAdjacencyList) DepthFirst(start NI, visit func(NI)) {
v := bits.New(len(g))
var f func(NI)
f = func(n NI) {
visit(n)
v.SetBit(int(n), 1)
for _, to := range g[n] {
if v.Bit(int(to.To)) == 0 {
f(to.To)
}
}
}
f(start)
}
// HasArc returns true if g has any arc from node `fr` to node `to`.
//
// Also returned is the index within the slice of arcs from node `fr`.
// If no arc from `fr` to `to` is present, HasArc returns false, -1.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also the method ParallelArcs, which finds all parallel arcs from
// `fr` to `to`.
func (g LabeledAdjacencyList) HasArc(fr, to NI) (bool, int) {
for x, h := range g[fr] {
if h.To == to {
return true, x
}
}
return false, -1
}
// AnyLoop identifies if a graph contains a loop, an arc that leads from a
// a node back to the same node.
//
// If g contains a loop, the method returns true and an example of a node
// with a loop. If there are no loops in g, the method returns false, -1.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) AnyLoop() (bool, NI) {
for fr, to := range g {
for _, to := range to {
if NI(fr) == to.To {
return true, to.To
}
}
}
return false, -1
}
// AddNode maps a node in a supergraph to a subgraph node.
//
// Argument p must be an NI in supergraph s.Super. AddNode panics if
// p is not a valid node index of s.Super.
//
// AddNode is idempotent in that it does not add a new node to the subgraph if
// a subgraph node already exists mapped to supergraph node p.
//
// The mapped subgraph NI is returned.
func (s *LabeledSubgraph) AddNode(p NI) (b NI) {
if int(p) < 0 || int(p) >= s.Super.Order() {
panic(fmt.Sprint("AddNode: NI ", p, " not in supergraph"))
}
if b, ok := s.SubNI[p]; ok {
return b
}
a := s.LabeledAdjacencyList
b = NI(len(a))
s.LabeledAdjacencyList = append(a, nil)
s.SuperNI = append(s.SuperNI, p)
s.SubNI[p] = b
return
}
// AddArc adds an arc to a subgraph.
//
// Arguments fr, to must be NIs in supergraph s.Super. As with AddNode,
// AddArc panics if fr and to are not valid node indexes of s.Super.
//
// The arc specfied by fr, to must exist in s.Super. Further, the number of
// parallel arcs in the subgraph cannot exceed the number of corresponding
// parallel arcs in the supergraph. That is, each arc already added to the
// subgraph counts against the arcs available in the supergraph. If a matching
// arc is not available, AddArc returns an error.
//
// If a matching arc is available, subgraph nodes are added as needed, the
// subgraph arc is added, and the method returns nil.
func (s *LabeledSubgraph) AddArc(fr NI, to Half) error {
// verify supergraph NIs first, but without adding subgraph nodes just yet.
if int(fr) < 0 || int(fr) >= s.Super.Order() {
panic(fmt.Sprint("AddArc: NI ", fr, " not in supergraph"))
}
if int(to.To) < 0 || int(to.To) >= s.Super.Order() {
panic(fmt.Sprint("AddArc: NI ", to.To, " not in supergraph"))
}
// count existing matching arcs in subgraph
n := 0
a := s.LabeledAdjacencyList
if bf, ok := s.SubNI[fr]; ok {
if bt, ok := s.SubNI[to.To]; ok {
// both NIs already exist in subgraph, need to count arcs
bTo := to
bTo.To = bt
for _, t := range a[bf] {
if t == bTo {
n++
}
}
}
}
// verify matching arcs are available in supergraph
for _, t := range (*s.Super)[fr] {
if t == to {
if n > 0 {
n-- // match existing arc
continue
}
// no more existing arcs need to be matched. nodes can finally
// be added as needed and then the arc can be added.
bf := s.AddNode(fr)
to.To = s.AddNode(to.To)
s.LabeledAdjacencyList[bf] = append(s.LabeledAdjacencyList[bf], to)
return nil // success
}
}
return errors.New("arc not available in supergraph")
}
func (super LabeledAdjacencyList) induceArcs(sub map[NI]NI, sup []NI) LabeledAdjacencyList {
s := make(LabeledAdjacencyList, len(sup))
for b, p := range sup {
var a []Half
for _, to := range super[p] {
if bt, ok := sub[to.To]; ok {
to.To = bt
a = append(a, to)
}
}
s[b] = a
}
return s
}
// InduceList constructs a node-induced subgraph.
//
// The subgraph is induced on receiver graph g. Argument l must be a list of
// NIs in receiver graph g. Receiver g becomes the supergraph of the induced
// subgraph.
//
// Duplicate NIs are allowed in list l. The duplicates are effectively removed
// and only a single corresponding node is created in the subgraph. Subgraph
// NIs are mapped in the order of list l, execpt for ignoring duplicates.
// NIs in l that are not in g will panic.
//
// Returned is the constructed Subgraph object containing the induced subgraph
// and the mappings to the supergraph.
func (g *LabeledAdjacencyList) InduceList(l []NI) *LabeledSubgraph {
sub, sup := mapList(l)
return &LabeledSubgraph{
Super: g,
SubNI: sub,
SuperNI: sup,
LabeledAdjacencyList: g.induceArcs(sub, sup)}
}
// InduceBits constructs a node-induced subgraph.
//
// The subgraph is induced on receiver graph g. Argument t must be a bitmap
// representing NIs in receiver graph g. Receiver g becomes the supergraph
// of the induced subgraph. NIs in t that are not in g will panic.
//
// Returned is the constructed Subgraph object containing the induced subgraph
// and the mappings to the supergraph.
func (g *LabeledAdjacencyList) InduceBits(t bits.Bits) *LabeledSubgraph {
sub, sup := mapBits(t)
return &LabeledSubgraph{
Super: g,
SubNI: sub,
SuperNI: sup,
LabeledAdjacencyList: g.induceArcs(sub, sup)}
}
// IsSimple checks for loops and parallel arcs.
//
// A graph is "simple" if it has no loops or parallel arcs.
//
// IsSimple returns true, -1 for simple graphs. If a loop or parallel arc is
// found, simple returns false and a node that represents a counterexample
// to the graph being simple.
//
// See also separate methods AnyLoop and AnyParallel.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) IsSimple() (ok bool, n NI) {
if lp, n := g.AnyLoop(); lp {
return false, n
}
if pa, n, _ := g.AnyParallel(); pa {
return false, n
}
return true, -1
}
// IsolatedNodes returns a bitmap of isolated nodes in receiver graph g.
//
// An isolated node is one with no arcs going to or from it.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) IsolatedNodes() (i bits.Bits) {
i = bits.New(len(g))
i.SetAll()
for fr, to := range g {
if len(to) > 0 {
i.SetBit(fr, 0)
for _, to := range to {
i.SetBit(int(to.To), 0)
}
}
}
return
}
// Order is the number of nodes in receiver g.
//
// It is simply a wrapper method for the Go builtin len().
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) Order() int {
// Why a wrapper for len()? Mostly for Directed and Undirected.
// u.Order() is a little nicer than len(u.LabeledAdjacencyList).
return len(g)
}
// ParallelArcs identifies all arcs from node `fr` to node `to`.
//
// The returned slice contains an element for each arc from node `fr` to node `to`.
// The element value is the index within the slice of arcs from node `fr`.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also the method HasArc, which stops after finding a single arc.
func (g LabeledAdjacencyList) ParallelArcs(fr, to NI) (p []int) {
for x, h := range g[fr] {
if h.To == to {
p = append(p, x)
}
}
return
}
// Permute permutes the node labeling of receiver g.
//
// Argument p must be a permutation of the node numbers of the graph,
// 0 through len(g)-1. A permutation returned by rand.Perm(len(g)) for
// example is acceptable.
//
// The graph is permuted in place. The graph keeps the same underlying
// memory but values of the graph representation are permuted to produce
// an isomorphic graph. The node previously labeled 0 becomes p[0] and so on.
// See example (or the code) for clarification.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) Permute(p []int) {
old := append(LabeledAdjacencyList{}, g...) // shallow copy
for fr, arcs := range old {
for i, to := range arcs {
arcs[i].To = NI(p[to.To])
}
g[p[fr]] = arcs
}
}
// ShuffleArcLists shuffles the arc lists of each node of receiver g.
//
// For example a node with arcs leading to nodes 3 and 7 might have an
// arc list of either [3 7] or [7 3] after calling this method. The
// connectivity of the graph is not changed. The resulting graph stays
// equivalent but a traversal will encounter arcs in a different
// order.
//
// If Rand r is nil, the rand package default shared source is used.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) ShuffleArcLists(r *rand.Rand) {
ri := rand.Intn
if r != nil {
ri = r.Intn
}
// Knuth-Fisher-Yates
for _, to := range g {
for i := len(to); i > 1; {
j := ri(i)
i--
to[i], to[j] = to[j], to[i]
}
}
}
+1059
View File
File diff suppressed because it is too large. Load diff
+1091
View File
File diff suppressed because it is too large. Load diff
+1091
View File
File diff suppressed because it is too large. Load diff
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2014 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
// Graph algorithms: Dijkstra, A*, Bellman Ford, Floyd Warshall;
// Kruskal and Prim minimal spanning tree; topological sort and DAG longest
// and shortest paths; Eulerian cycle and path; degeneracy and k-cores;
// Bron Kerbosch clique finding; connected components; dominance; and others.
//
// This is a graph library of integer indexes. To use it with application
// data, you associate data with integer indexes, perform searches or other
// operations with the library, and then use the integer index results to refer
// back to your application data.
//
// Thus it does not store application data, pointers to application data,
// or require you to implement an interface on your application data.
// The idea is to keep the library methods fast and lean.
//
// Representation overview
//
// The package defines a type for a node index (NI) which is just an integer
// type. It defines types for a number of number graph representations using
// NI. The fundamental graph type is AdjacencyList, which is the
// common "list of lists" graph representation. It is a list as a slice
// with one element for each node of the graph. Each element is a list
// itself, a list of neighbor nodes, implemented as an NI slice. Methods
// on an AdjacencyList generally work on any representable graph, including
// directed or undirected graphs, simple graphs or multigraphs.
//
// The type Undirected embeds an AdjacencyList adding methods specific to
// undirected graphs. Similarly the type Directed adds methods meaningful
// for directed graphs.
//
// Similar to NI, the type LI is a "label index" which labels a
// node-to-neighbor "arc" or edge. Just as an NI can index arbitrary node
// data, an LI can index arbitrary arc or edge data. A number of algorithms
// use a "weight" associated with an arc. This package does not represent
// weighted arcs explicitly, but instead uses the LI as a more general
// mechanism allowing not only weights but arbitrary data to be associated
// with arcs. While AdjacencyList represents an arc with simply an NI,
// the type LabeledAdjacencyList uses a type that pairs an NI with an LI.
// This type is named Half, for half-arc. (A full arc would represent
// both ends.) Types LabeledDirected and LabeledUndirected embed a
// LabeledAdjacencyList.
//
// In contrast to Half, the type Edge represents both ends of an edge (but
// no label.) The type LabeledEdge adds the label. The type WeightedEdgeList
// bundles a list of LabeledEdges with a WeightFunc. (WeightedEdgeList has
// few methods. It exists primarily to support the Kruskal algorithm.)
//
// FromList is a compact rooted tree (or forest) respresentation. Like
// AdjacencyList and LabeledAdjacencyList, it is a list with one element for
// each node of the graph. Each element contains only a single neighbor
// however, its parent in the tree, the "from" node.
//
// Code generation
//
// A number of methods on AdjacencyList, Directed, and Undirected are
// applicable to LabeledAdjacencyList, LabeledDirected, and LabeledUndirected
// simply by ignoring the label. In these cases code generation provides
// methods on both types from a single source implementation. These methods
// are documented with the sentence "There are equivalent labeled and unlabeled
// versions of this method."
//
// Terminology
//
// This package uses the term "node" rather than "vertex." It uses "arc"
// to mean a directed edge, and uses "from" and "to" to refer to the ends
// of an arc. It uses "start" and "end" to refer to endpoints of a search
// or traversal.
//
// The usage of "to" and "from" is perhaps most strange. In common speech
// they are prepositions, but throughout this package they are used as
// adjectives, for example to refer to the "from node" of an arc or the
// "to node". The type "FromList" is named to indicate it stores a list of
// "from" values.
//
// A "half arc" refers to just one end of an arc, either the to or from end.
//
// Two arcs are "reciprocal" if they connect two distinct nodes n1 and n2,
// one arc leading from n1 to n2 and the other arc leading from n2 to n1.
// Undirected graphs are represented with reciprocal arcs.
//
// A node with an arc to itself represents a "loop." Duplicate arcs, where
// a node has multiple arcs to another node, are termed "parallel arcs."
// A graph with no loops or parallel arcs is "simple." A graph that allows
// parallel arcs is a "multigraph"
//
// The "size" of a graph traditionally means the number of undirected edges.
// This package uses "arc size" to mean the number of arcs in a graph. For an
// undirected graph without loops, arc size is 2 * size.
//
// The "order" of a graph is the number of nodes. An "ordering" though means
// an ordered list of nodes.
//
// A number of graph search algorithms use a concept of arc "weights."
// The sum of arc weights along a path is a "distance." In contrast, the
// number of nodes in a path, including start and end nodes, is the path's
// "length." (Yes, mixing weights and lengths would be nonsense physically,
// but the terms used here are just distinct terms for abstract values.
// The actual meaning to an application is likely to be something else
// entirely and is not relevant within this package.)
//
// Finally, this package documentation takes back the word "object" in some
// places to refer to a Go value, especially a value of a type with methods.
//
// Shortest path searches
//
// This package implements a number of shortest path searches. Most work
// with weighted graphs that are directed or undirected, and with graphs
// that may have loops or parallel arcs. For weighted graphs, "shortest"
// is defined as the path distance (sum of arc weights) with path length
// (number of nodes) breaking ties. If multiple paths have the same minimum
// distance with the same minimum length, search methods are free to return
// any of them.
//
// Algorithm Description
// Dijkstra Non-negative arc weights, single or all paths.
// AStar Non-negative arc weights, heuristic guided, single path.
// BellmanFord Negative arc weights allowed, no negative cycles, all paths.
// DAGPath O(n) algorithm for DAGs, arc weights of any sign.
// FloydWarshall all pairs distances, no negative cycles.
package graph
+498
View File
@@ -0,0 +1,498 @@
// Copyright 2014 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
package graph
import "github.com/soniakeys/bits"
// FromList represents a rooted tree (or forest) where each node is associated
// with a half arc identifying an arc "from" another node.
//
// Other terms for this data structure include "parent list",
// "predecessor list", "in-tree", "inverse arborescence", and
// "spaghetti stack."
//
// The Paths member represents the tree structure. Leaves and MaxLen are
// not always needed. Where Leaves is used it serves as a bitmap where
// Leaves.Bit(n) == 1 for each leaf n of the tree. Where MaxLen is used it is
// provided primarily as a convenience for functions that might want to
// anticipate the maximum path length that would be encountered traversing
// the tree.
//
// Various graph search methods use a FromList to returns search results.
// For a start node of a search, From will be -1 and Len will be 1. For other
// nodes reached by the search, From represents a half arc in a path back to
// start and Len represents the number of nodes in the path. For nodes not
// reached by the search, From will be -1 and Len will be 0.
//
// A single FromList can also represent a forest. In this case paths from
// all leaves do not return to a single root node, but multiple root nodes.
//
// While a FromList generally encodes a tree or forest, it is technically
// possible to encode a cyclic graph. A number of FromList methods require
// the receiver to be acyclic. Graph methods documented to return a tree or
// forest will never return a cyclic FromList. In other cases however,
// where a FromList is not known to by cyclic, the Cyclic method can be
// useful to validate the acyclic property.
type FromList struct {
Paths []PathEnd // tree representation
Leaves bits.Bits // leaves of tree
MaxLen int // length of longest path, max of all PathEnd.Len values
}
// PathEnd associates a half arc and a path length.
//
// A PathEnd list is an element type of FromList.
type PathEnd struct {
From NI // a "from" half arc, the node the arc comes from
Len int // number of nodes in path from start
}
/* NewFromList could be confusing now with bits also needing allocation.
maybe best to not have this function. Maybe a more useful new would be
one that took a PathEnd slice and intitialized everything including roots
and max len. Maybe its time for a separate []PathEnd type when that's
all that's needed. (and reconsider the name PathEnd)
*/
// NewFromList creates a FromList object of given order.
//
// The Paths member is allocated to the specified order n but other members
// are left as zero values.
func NewFromList(n int) FromList {
return FromList{Paths: make([]PathEnd, n)}
}
// BoundsOk validates the "from" values in the list.
//
// Negative values are allowed as they indicate root nodes.
//
// BoundsOk returns true when all from values are less than len(t).
// Otherwise it returns false and a node with a from value >= len(t).
func (f FromList) BoundsOk() (ok bool, n NI) {
for n, e := range f.Paths {
if int(e.From) >= len(f.Paths) {
return false, NI(n)
}
}
return true, -1
}
// CommonStart returns the common start node of minimal paths to a and b.
//
// It returns -1 if a and b cannot be traced back to a common node.
//
// The method relies on populated PathEnd.Len members. Use RecalcLen if
// the Len members are not known to be present and correct.
func (f FromList) CommonStart(a, b NI) NI {
p := f.Paths
if p[a].Len < p[b].Len {
a, b = b, a
}
for bl := p[b].Len; p[a].Len > bl; {
a = p[a].From
if a < 0 {
return -1
}
}
for a != b {
a = p[a].From
if a < 0 {
return -1
}
b = p[b].From
}
return a
}
// Cyclic determines if f contains a cycle, a non-empty path from a node
// back to itself.
//
// Cyclic returns true if g contains at least one cycle. It also returns
// an example of a node involved in a cycle.
//
// Cyclic returns (false, -1) in the normal case where f is acyclic.
// Note that the bool is not an "ok" return. A cyclic FromList is usually
// not okay.
func (f FromList) Cyclic() (cyclic bool, n NI) {
p := f.Paths
vis := bits.New(len(p))
for i := range p {
path := bits.New(len(p))
for n := i; vis.Bit(n) == 0; {
vis.SetBit(n, 1)
path.SetBit(n, 1)
if n = int(p[n].From); n < 0 {
break
}
if path.Bit(n) == 1 {
return true, NI(n)
}
}
}
return false, -1
}
// IsolatedNodeBits returns a bitmap of isolated nodes in receiver graph f.
//
// An isolated node is one with no arcs going to or from it.
func (f FromList) IsolatedNodes() (iso bits.Bits) {
p := f.Paths
iso = bits.New(len(p))
iso.SetAll()
for n, e := range p {
if e.From >= 0 {
iso.SetBit(n, 0)
iso.SetBit(int(e.From), 0)
}
}
return
}
// PathTo decodes a FromList, recovering a single path.
//
// The path is returned as a list of nodes where the first element will be
// a root node and the last element will be the specified end node.
//
// Only the Paths member of the receiver is used. Other members of the
// FromList do not need to be valid, however the MaxLen member can be useful
// for allocating argument p.
//
// Argument p can provide the result slice. If p has capacity for the result
// it will be used, otherwise a new slice is created for the result.
//
// See also function PathTo.
func (f FromList) PathTo(end NI, p []NI) []NI {
return PathTo(f.Paths, end, p)
}
// PathTo decodes a single path from a PathEnd list.
//
// A PathEnd list is the main data representation in a FromList. See FromList.
//
// PathTo returns a list of nodes where the first element will be
// a root node and the last element will be the specified end node.
//
// Argument p can provide the result slice. If p has capacity for the result
// it will be used, otherwise a new slice is created for the result.
//
// See also method FromList.PathTo.
func PathTo(paths []PathEnd, end NI, p []NI) []NI {
n := paths[end].Len
if n == 0 {
return p[:0]
}
if cap(p) >= n {
p = p[:n]
} else {
p = make([]NI, n)
}
for {
n--
p[n] = end
if n == 0 {
return p
}
end = paths[end].From
}
}
// PathToLabeled decodes a FromList, recovering a single path.
//
// The start of the returned path will be a root node of the FromList.
//
// Only the Paths member of the receiver is used. Other members of the
// FromList do not need to be valid, however the MaxLen member can be useful
// for allocating argument p.
//
// Argument p can provide the result slice. If p has capacity for the result
// it will be used, otherwise a new slice is created for the result.
//
// See also function PathTo.
func (f FromList) PathToLabeled(end NI, labels []LI, p []Half) LabeledPath {
n := f.Paths[end].Len - 1
if n <= 0 {
return LabeledPath{end, p[:0]}
}
if cap(p) >= n {
p = p[:n]
} else {
p = make([]Half, n)
}
for {
n--
p[n] = Half{To: end, Label: labels[end]}
end = f.Paths[end].From
if n == 0 {
return LabeledPath{end, p}
}
}
}
// Preorder traverses a FromList in preorder.
//
// Nodes are visited in order such that for any node n with from node fr,
// fr is visited before n. Where f represents a tree, the visit ordering
// corresponds to a preordering, or depth first traversal of the tree.
// Where f represents a forest, the preorderings of the trees can be
// intermingled.
//
// Leaves must be set correctly first. Use RecalcLeaves if leaves are not
// known to be set correctly. FromList f cannot be cyclic.
//
// Traversal continues while visitor function v returns true. It terminates
// if v returns false. Preorder returns true if it completes without v
// returning false. Preorder returns false if traversal is terminated by v
// returning false.
func (f FromList) Preorder(v func(NI) bool) bool {
p := f.Paths
done := bits.New(len(p))
var df func(NI) bool
df = func(n NI) bool {
done.SetBit(int(n), 1)
if fr := p[n].From; fr >= 0 && done.Bit(int(fr)) == 0 {
df(fr)
}
return v(n)
}
for n := range f.Paths {
p[n].Len = 0
}
return f.Leaves.IterateOnes(func(n int) bool {
return df(NI(n))
})
}
// RecalcLeaves recomputes the Leaves member of f.
func (f *FromList) RecalcLeaves() {
p := f.Paths
lv := &f.Leaves
if lv.Num != len(p) {
*lv = bits.New(len(p))
}
lv.SetAll()
for n := range f.Paths {
if fr := p[n].From; fr >= 0 {
lv.SetBit(int(fr), 0)
}
}
}
// RecalcLen recomputes Len for each path end, and recomputes MaxLen.
//
// RecalcLen relies on the Leaves member being valid. If it is not known
// to be valid, call RecalcLeaves before calling RecalcLen.
//
// RecalcLen will panic if the FromList is cyclic. Use the Cyclic method
// if needed to verify that the FromList is acyclic.
func (f *FromList) RecalcLen() {
p := f.Paths
var setLen func(NI) int
setLen = func(n NI) int {
switch {
case p[n].Len > 0:
return p[n].Len
case p[n].From < 0:
p[n].Len = 1
return 1
}
l := 1 + setLen(p[n].From)
p[n].Len = l
return l
}
for n := range f.Paths {
p[n].Len = 0
}
f.MaxLen = 0
f.Leaves.IterateOnes(func(n int) bool {
if l := setLen(NI(n)); l > f.MaxLen {
f.MaxLen = l
}
return true
})
}
// ReRoot reorients the tree containing n to make n the root node.
//
// It keeps the tree connected by "reversing" the path from n to the old root.
//
// After ReRoot, the Leaves and Len members are invalid.
// Call RecalcLeaves or RecalcLen as needed.
func (f *FromList) ReRoot(n NI) {
p := f.Paths
fr := p[n].From
if fr < 0 {
return
}
p[n].From = -1
for {
ff := p[fr].From
p[fr].From = n
if ff < 0 {
return
}
n = fr
fr = ff
}
}
// Root finds the root of a node in a FromList.
func (f FromList) Root(n NI) NI {
for p := f.Paths; ; {
fr := p[n].From
if fr < 0 {
return n
}
n = fr
}
}
// Transpose constructs the directed graph corresponding to FromList f
// but with arcs in the opposite direction. That is, from roots toward leaves.
//
// If non-nil argrument roots is passed, Transpose populates it as roots of
// the resulting forest and returns nRoots as a count of the roots.
//
// The method relies only on the From member of f.Paths. Other members of
// the FromList are not used.
func (f FromList) Transpose(roots *bits.Bits) (forest Directed, nRoots int) {
p := f.Paths
g := make(AdjacencyList, len(p))
if roots != nil {
nRoots = len(p)
if roots.Num != nRoots {
*roots = bits.New(nRoots)
}
roots.SetAll()
}
for i, e := range p {
if e.From == -1 {
continue
}
g[e.From] = append(g[e.From], NI(i))
if roots != nil && roots.Bit(i) == 1 {
roots.SetBit(i, 0)
nRoots--
}
}
return Directed{g}, nRoots
}
// TransposeLabeled constructs the labeled directed graph corresponding
// to FromList f but with arcs in the opposite direction. That is, from
// roots toward leaves.
//
// The argument labels can be nil. In this case labels are generated matching
// the path indexes. This corresponds to the "to", or child node.
//
// If labels is non-nil, it must be the same length as t.Paths and is used
// to look up label numbers by the path index.
//
// If non-nil argrument roots is passed, Transpose populates it as roots of
// the resulting forest and returns nRoots as a count of the roots.
//
// The method relies only on the From member of f.Paths. Other members of
// the FromList are not used.
func (f FromList) TransposeLabeled(labels []LI, roots *bits.Bits) (forest LabeledDirected, nRoots int) {
p := f.Paths
g := make(LabeledAdjacencyList, len(p))
if roots != nil {
nRoots = len(p)
if roots.Num != nRoots {
*roots = bits.New(nRoots)
}
roots.SetAll()
}
for i, p := range f.Paths {
if p.From == -1 {
continue
}
l := LI(i)
if labels != nil {
l = labels[i]
}
g[p.From] = append(g[p.From], Half{NI(i), l})
if roots != nil && roots.Bit(i) == 1 {
roots.SetBit(i, 0)
nRoots--
}
}
return LabeledDirected{g}, nRoots
}
// Undirected constructs the undirected graph corresponding to FromList f.
//
// The resulting graph will be a tree or forest.
//
// If non-nil argrument roots is passed, Transpose populates it as roots of
// the resulting forest and returns nRoots as a count of the roots.
//
// The method relies only on the From member of f.Paths. Other members of
// the FromList are not used.
func (f FromList) Undirected(roots *bits.Bits) (forest Undirected, nRoots int) {
p := f.Paths
g := make(AdjacencyList, len(p))
if roots != nil {
nRoots = len(p)
if roots.Num != nRoots {
*roots = bits.New(nRoots)
}
roots.SetAll()
}
for i, e := range p {
if e.From == -1 {
continue
}
g[i] = append(g[i], e.From)
g[e.From] = append(g[e.From], NI(i))
if roots != nil && roots.Bit(i) == 1 {
roots.SetBit(i, 0)
nRoots--
}
}
return Undirected{g}, nRoots
}
// LabeledUndirected constructs the labeled undirected graph corresponding
// to FromList f.
//
// The resulting graph will be a tree or forest.
//
// The argument labels can be nil. In this case labels are generated matching
// the path indexes. This corresponds to the "to", or child node.
//
// If labels is non-nil, it must be the same length as t.Paths and is used
// to look up label numbers by the path index.
//
// If non-nil argrument roots is passed, LabeledUndirected populates it as
// roots of the resulting forest and returns nRoots as a count of the roots.
//
// The method relies only on the From member of f.Paths. Other members of
// the FromList are not used.
func (f FromList) LabeledUndirected(labels []LI, roots *bits.Bits) (forest LabeledUndirected, nRoots int) {
p := f.Paths
g := make(LabeledAdjacencyList, len(p))
if roots != nil {
nRoots = len(p)
if roots.Num != nRoots {
*roots = bits.New(nRoots)
}
roots.SetAll()
}
for i, p := range f.Paths {
if p.From == -1 {
continue
}
l := LI(i)
if labels != nil {
l = labels[i]
}
g[i] = append(g[i], Half{p.From, l})
g[p.From] = append(g[p.From], Half{NI(i), l})
if roots != nil && roots.Bit(i) == 1 {
roots.SetBit(i, 0)
nRoots--
}
}
return LabeledUndirected{g}, nRoots
}
+3
View File
@@ -0,0 +1,3 @@
module "github.com/soniakeys/graph"
require "github.com/soniakeys/bits" v1.0.0
+767
View File
File diff suppressed because it is too large. Load diff
+135
View File
@@ -0,0 +1,135 @@
= Hacking
== Get, install
Basic use of the package is just go get, or git clone; go install. There are
no dependencies outside the standard library.
== Build
CI is currently on travis-ci.org.
The build runs go vet with a few exceptions for things I'm not a big fan of.
https://github.com/client9/misspell has been valuable.
Also I wrote https://github.com/soniakeys/vetc to validate that each source
file has copyright/license statement.
Then, it’s not in the ci script, but I wrote https://github.com/soniakeys/rcv
to put coverage stats in the readme. Maybe it could be commit hook or
something but for now I’ll try just running it manually now and then.
Go fmt is not in the ci script, but I have at least one editor set up to run
it on save, so code should stay formatted pretty well.
== Examples with random output
The math/rand generators with constant seeds used to give consistent numbers
across Go versions and so some examples relied on this. Sometime after Go 1.9
though the numbers changed. The technique for now is to go ahead and write
those examples, get them working, then change the `// Output:` line to
`// Random output:`. This keeps them showing in go doc but keeps them from
being run by go test. This works for now. It might be revisited at some
point.
== Plans
The primary to-do list is the issue tracker on Github.
== Direction, focus, features
The project started with no real goal or purpose, just as a place for some code
that might be useful. Here are some elements that characterize the direction.
* The focus has been on algorithms on adjacency lists. That is, adjacency list
is the fundamental representation for most implemented algorithms. There are
many other interesting representations, many reasons to use them, but
adjacency list is common in literature and practice. It has been useful to
focus on this data representation, at first anyway.
* The focus has been on single threaded algorithms. Again, there is much new
and interesting work being done with concurrent, parallel, and distributed
graph algorithms, and Go might be an excellent language to implement some of
these algorithms. But as a preliminary step, more traditional
single-threaded algorithms are implemented.
* The focus has been on static finite graphs. Again there is much interesting
work in online algorithms, dynamic graphs, and infinite graphs, but these
are not generally considered here.
* Algorithms selected for implementation are generally ones commonly appearing
in beginning graph theory discussions and in general purpose graph libraries
in other programming languages. With these as drivers, there's a big risk
developing a library of curiosities and academic exercises rather than a
library of practical utility. But well, it's a start. The hope is that
there are some practical drivers behind graph theory and behind other graph
libraries.
* There is active current research going on in graph algorithm development.
One goal for this library is to implement newer and faster algorithms.
In some cases where it seems not too much work, older/classic/traditional
algorithms may be implemented for comparison. These generally go in the
alt subdirectory.
== General principles
* The API is rather low level.
* Slices instead of maps. Maps are pretty efficient, and the property of
unique keys can be useful, But slices are still faster and more efficient,
and the unique key property is not always needed or wanted. The Adjacency
list implementation of this library is all done in slices. Slices are used
in algorithms where possible, in preference to maps. Maps are still used in
some cases where uniqueness is needed.
* Interfaces not generally used. Algorithms are implemented directly on
concrete data types and not on interfaces describing the capabilities of
the data types. The abstraction of interfaces is a nice match to graph
theory and the convenience of running graph algorithms on any type that
implements an interface is appealing, but the costs seem too high to me.
Slices are rich with capababilites that get hidden behind interfaces and
direct slice manipulation is always faster than going through interfaces.
An impedance for programs using the library is that they will generally
have to implement a mapping from slice indexes to their application data,
often including for example, some other form of node ID. This seems fair
to push this burden outside the graph library; the library cannot know
the needs of this mapping.
* Bitsets are widely used, particularly to store one bit of information per
node of a graph. I used math/big at first but then moved to a dense bitset
of my own. Yes, I considered other third-party bitsets but had my own
feature set I wanted. A slice of bools is another alternative. Bools will
be faster in almost all cases but the bitset will use less memory. I'm
chosing size over speed for now.
* Code generation is used to provide methods that work on both labeled and
unlabeled graphs. Code is written to labeled types, then transformations
generate the unlabled equivalents.
* Methods are named for what they return rather than what they do, where
reasonable anyway.
* Consistency in method signature and behavior across corresponding methods,
for example directed/undirected, labeled/unlabeled, again, as long as it's
reasonable.
* Sometimes in tension with the consistency principle, methods are lazy about
datatypes of parameters and return values. Sometimes a vale might have
different reasonable representations, a set might be a bitset, map, slice
of bools, or slice of set members for example. Methods will take and return
whatever is convenient for them and not convert the form just for consistency
or to try to guess what a caller would prefer.
* Methods return multiple results for whatever the algorithm produces that
might be of interest. Sometimes an algorithm will have a primary result but
then some secondary values that also might be of interest. If they are
already computed as a byproduct of the algorithm, or can be computed at
negligible cost, return them.
* Sometimes in conflict with the multiple result principle, methods will not
speculatively compute secondary results if there is any significant cost
and if the secondary result can be just as easily computed later.
== Code Maintenance
There are tons of cut and paste variants. There's the basic AdjacencyList,
then Directed and Undirected variants, then Labeled variants of each of those.
Code gen helps avoid some cut and paste but there's a bunch that doesn't
code gen very well and so is duplicated with cut and paste. In particular
the testable examples in the _test files don't cg well and so are pretty much
all duplicated by hand. If you change code, think about where there should
be variants and go look to see if the variants need similar changes.
+254
View File
@@ -0,0 +1,254 @@
// Copyright 2014 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
package graph
import (
"container/heap"
"sort"
"github.com/soniakeys/bits"
)
type dsElement struct {
from NI
rank int
}
type disjointSet struct {
set []dsElement
}
func newDisjointSet(n int) disjointSet {
set := make([]dsElement, n)
for i := range set {
set[i].from = -1
}
return disjointSet{set}
}
// return true if disjoint trees were combined.
// false if x and y were already in the same tree.
func (ds disjointSet) union(x, y NI) bool {
xr := ds.find(x)
yr := ds.find(y)
if xr == yr {
return false
}
switch xe, ye := &ds.set[xr], &ds.set[yr]; {
case xe.rank < ye.rank:
xe.from = yr
case xe.rank == ye.rank:
xe.rank++
fallthrough
default:
ye.from = xr
}
return true
}
func (ds disjointSet) find(n NI) NI {
// fast paths for n == root or from root.
// no updates need in these cases.
s := ds.set
fr := s[n].from
if fr < 0 { // n is root
return n
}
n, fr = fr, s[fr].from
if fr < 0 { // n is from root
return n
}
// otherwise updates needed.
// two iterative passes (rather than recursion or stack)
// pass 1: find root
r := fr
for {
f := s[r].from
if f < 0 {
break
}
r = f
}
// pass 2: update froms
for {
s[n].from = r
if fr == r {
return r
}
n = fr
fr = s[n].from
}
}
// Kruskal implements Kruskal's algorithm for constructing a minimum spanning
// forest on an undirected graph.
//
// The forest is returned as an undirected graph.
//
// Also returned is a total distance for the returned forest.
//
// This method is a convenience wrapper for LabeledEdgeList.Kruskal.
// If you have no need for the input graph as a LabeledUndirected, it may be
// more efficient to construct a LabeledEdgeList directly.
func (g LabeledUndirected) Kruskal(w WeightFunc) (spanningForest LabeledUndirected, dist float64) {
return g.WeightedArcsAsEdges(w).Kruskal()
}
// Kruskal implements Kruskal's algorithm for constructing a minimum spanning
// forest on an undirected graph.
//
// The algorithm allows parallel edges, thus it is acceptable to construct
// the receiver with LabeledUndirected.WeightedArcsAsEdges. It may be more
// efficient though, if you can construct the receiver WeightedEdgeList
// directly without parallel edges.
//
// The forest is returned as an undirected graph.
//
// Also returned is a total distance for the returned forest.
//
// The edge list of the receiver is sorted in place as a side effect of this
// method. See KruskalSorted for a version that relies on the edge list being
// already sorted. This method is a wrapper for KruskalSorted. If you can
// generate the input graph sorted as required for KruskalSorted, you can
// call that method directly and avoid the overhead of the sort.
func (l WeightedEdgeList) Kruskal() (g LabeledUndirected, dist float64) {
e := l.Edges
w := l.WeightFunc
sort.Slice(e, func(i, j int) bool { return w(e[i].LI) < w(e[j].LI) })
return l.KruskalSorted()
}
// KruskalSorted implements Kruskal's algorithm for constructing a minimum
// spanning tree on an undirected graph.
//
// When called, the edge list of the receiver must be already sorted by weight.
// See the Kruskal method for a version that accepts an unsorted edge list.
// As with Kruskal, parallel edges are allowed.
//
// The forest is returned as an undirected graph.
//
// Also returned is a total distance for the returned forest.
func (l WeightedEdgeList) KruskalSorted() (g LabeledUndirected, dist float64) {
ds := newDisjointSet(l.Order)
g.LabeledAdjacencyList = make(LabeledAdjacencyList, l.Order)
for _, e := range l.Edges {
if ds.union(e.N1, e.N2) {
g.AddEdge(Edge{e.N1, e.N2}, e.LI)
dist += l.WeightFunc(e.LI)
}
}
return
}
// Prim implements the Jarník-Prim-Dijkstra algorithm for constructing
// a minimum spanning tree on an undirected graph.
//
// Prim computes a minimal spanning tree on the connected component containing
// the given start node. The tree is returned in FromList f. Argument f
// cannot be a nil pointer although it can point to a zero value FromList.
//
// If the passed FromList.Paths has the len of g though, it will be reused.
// In the case of a graph with multiple connected components, this allows a
// spanning forest to be accumulated by calling Prim successively on
// representative nodes of the components. In this case if leaves for
// individual trees are of interest, pass a non-nil zero-value for the argument
// componentLeaves and it will be populated with leaves for the single tree
// spanned by the call.
//
// If argument labels is non-nil, it must have the same length as g and will
// be populated with labels corresponding to the edges of the tree.
//
// Returned are the number of nodes spanned for the single tree (which will be
// the order of the connected component) and the total spanned distance for the
// single tree.
func (g LabeledUndirected) Prim(start NI, w WeightFunc, f *FromList, labels []LI, componentLeaves *bits.Bits) (numSpanned int, dist float64) {
al := g.LabeledAdjacencyList
if len(f.Paths) != len(al) {
*f = NewFromList(len(al))
}
if f.Leaves.Num != len(al) {
f.Leaves = bits.New(len(al))
}
b := make([]prNode, len(al)) // "best"
for n := range b {
b[n].nx = NI(n)
b[n].fx = -1
}
rp := f.Paths
var frontier prHeap
rp[start] = PathEnd{From: -1, Len: 1}
numSpanned = 1
fLeaves := &f.Leaves
fLeaves.SetBit(int(start), 1)
if componentLeaves != nil {
if componentLeaves.Num != len(al) {
*componentLeaves = bits.New(len(al))
}
componentLeaves.SetBit(int(start), 1)
}
for a := start; ; {
for _, nb := range al[a] {
if rp[nb.To].Len > 0 {
continue // already in MST, no action
}
switch bp := &b[nb.To]; {
case bp.fx == -1: // new node for frontier
bp.from = fromHalf{From: a, Label: nb.Label}
bp.wt = w(nb.Label)
heap.Push(&frontier, bp)
case w(nb.Label) < bp.wt: // better arc
bp.from = fromHalf{From: a, Label: nb.Label}
bp.wt = w(nb.Label)
heap.Fix(&frontier, bp.fx)
}
}
if len(frontier) == 0 {
break // done
}
bp := heap.Pop(&frontier).(*prNode)
a = bp.nx
rp[a].Len = rp[bp.from.From].Len + 1
rp[a].From = bp.from.From
if len(labels) != 0 {
labels[a] = bp.from.Label
}
dist += bp.wt
fLeaves.SetBit(int(bp.from.From), 0)
fLeaves.SetBit(int(a), 1)
if componentLeaves != nil {
componentLeaves.SetBit(int(bp.from.From), 0)
componentLeaves.SetBit(int(a), 1)
}
numSpanned++
}
return
}
type prNode struct {
nx NI
from fromHalf
wt float64 // p.Weight(from.Label)
fx int
}
type prHeap []*prNode
func (h prHeap) Len() int { return len(h) }
func (h prHeap) Less(i, j int) bool { return h[i].wt < h[j].wt }
func (h prHeap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
h[i].fx = i
h[j].fx = j
}
func (p *prHeap) Push(x interface{}) {
nd := x.(*prNode)
nd.fx = len(*p)
*p = append(*p, nd)
}
func (p *prHeap) Pop() interface{} {
r := *p
last := len(r) - 1
*p = r[:last]
return r[last]
}
+708
View File
File diff suppressed because it is too large. Load diff
+50
View File
@@ -0,0 +1,50 @@
= Graph
A graph library with goals of speed and simplicity, Graph implements
graph algorithms on graphs of zero-based integer node IDs.
image:https://godoc.org/github.com/soniakeys/graph?status.svg[link=https://godoc.org/github.com/soniakeys/graph]
image:http://gowalker.org/api/v1/badge[link=https://gowalker.org/github.com/soniakeys/graph]
image:http://go-search.org/badge?id=github.com%2Fsoniakeys%2Fgraph[link=http://go-search.org/view?id=github.com%2Fsoniakeys%2Fgraph]
image:https://travis-ci.org/soniakeys/graph.svg?branch=master[link=https://travis-ci.org/soniakeys/graph]
The library provides efficient graph representations and many methods on
graph types. It can be imported and used directly in many applications that
require or can benefit from graph algorithms.
The library should also be considered as library of source code that can serve
as starting material for coding variant or more complex algorithms.
== Ancillary material of interest
The directory link:tutorials[tutorials] is a work in progress - there are only
a few tutorials there yet - but the concept is to provide some topical
walk-throughs to supplement godoc. The source-based godoc documentation
remains the primary documentation.
The directory link:anecdote[anecdote] contains a stand-alone program that
performs single runs of a number of methods, collecting one-off or "anecdotal"
timings. It currently runs only a small fraction of the library methods but
may still be of interest for giving a general idea of how fast some methods
run.
The directory link:bench[bench] is another work in progress. The concept is
to present some plots showing benchmark performance approaching some
theoretical asymptote.
link:hacking.adoc[hacking.adoc] has some information about how the library is
developed, built, and tested. It might be of interest if for example you
plan to fork or contribute to the the repository.
== Test coverage
1 Jul 2017
....
graph 93.7%
graph/alt 88.0%
graph/dot 77.7%
graph/treevis 79.4%
....
== License
All files in the repository are licensed with the MIT License,
https://opensource.org/licenses/MIT.
+761
View File
File diff suppressed because it is too large. Load diff
+817
View File
File diff suppressed because it is too large. Load diff
+1138
View File
File diff suppressed because it is too large. Load diff
+1138
View File
File diff suppressed because it is too large. Load diff
+9 -2
View File
@@ -1,5 +1,8 @@
# github.com/Microsoft/go-winio v0.4.11
github.com/Microsoft/go-winio
# github.com/actions/workflow-parser v0.0.0-20190130154146-aac54e2ba131
github.com/actions/workflow-parser/model
github.com/actions/workflow-parser/parser
# github.com/containerd/continuity v0.0.0-20181203112020-004b46473808
github.com/containerd/continuity/pathdriver
# github.com/davecgh/go-spew v1.1.1
@@ -55,11 +58,11 @@ github.com/gogo/protobuf/proto
# github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/hcl
github.com/hashicorp/hcl/hcl/ast
github.com/hashicorp/hcl/hcl/token
github.com/hashicorp/hcl/hcl/parser
github.com/hashicorp/hcl/hcl/token
github.com/hashicorp/hcl/json/parser
github.com/hashicorp/hcl/hcl/strconv
github.com/hashicorp/hcl/hcl/scanner
github.com/hashicorp/hcl/hcl/strconv
github.com/hashicorp/hcl/json/scanner
github.com/hashicorp/hcl/json/token
# github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
@@ -92,6 +95,10 @@ github.com/pmezard/go-difflib/difflib
github.com/sergi/go-diff/diffmatchpatch
# github.com/sirupsen/logrus v1.3.0
github.com/sirupsen/logrus
# github.com/soniakeys/bits v1.0.0
github.com/soniakeys/bits
# github.com/soniakeys/graph v0.0.0
github.com/soniakeys/graph
# github.com/spf13/cobra v0.0.3
github.com/spf13/cobra
# github.com/spf13/pflag v1.0.3