replace parser with actions/workflow-parser
This commit is contained in:
parent
72fbefcedc
commit
5d0a8d26ae
|
@ -2,81 +2,46 @@ package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"log"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nektos/act/common"
|
"github.com/actions/workflow-parser/model"
|
||||||
log "github.com/sirupsen/logrus"
|
"github.com/howeyc/gopass"
|
||||||
)
|
)
|
||||||
|
|
||||||
// imageURL is the directory where a `Dockerfile` should exist
|
var secretCache map[string]string
|
||||||
func parseImageLocal(workingDir string, contextDir string) (contextDirOut string, tag string, ok bool) {
|
|
||||||
if !strings.HasPrefix(contextDir, "./") {
|
|
||||||
return "", "", false
|
|
||||||
}
|
|
||||||
contextDir = filepath.Join(workingDir, contextDir)
|
|
||||||
if _, err := os.Stat(filepath.Join(contextDir, "Dockerfile")); os.IsNotExist(err) {
|
|
||||||
log.Debugf("Ignoring missing Dockerfile '%s/Dockerfile'", contextDir)
|
|
||||||
return "", "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
sha, _, err := common.FindGitRevision(contextDir)
|
type actionEnvironmentApplier struct {
|
||||||
if err != nil {
|
*model.Action
|
||||||
log.Warnf("Unable to determine git revision: %v", err)
|
|
||||||
sha = "latest"
|
|
||||||
}
|
|
||||||
return contextDir, fmt.Sprintf("%s:%s", filepath.Base(contextDir), sha), true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// imageURL is the URL for a docker repo
|
func newActionEnvironmentApplier(action *model.Action) environmentApplier {
|
||||||
func parseImageReference(image string) (ref string, ok bool) {
|
return &actionEnvironmentApplier{action}
|
||||||
imageURL, err := url.Parse(image)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Unable to parse image as url: %v", err)
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
if imageURL.Scheme != "docker" {
|
|
||||||
log.Debugf("Ignoring non-docker ref '%s'", imageURL.String())
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s%s", imageURL.Host, imageURL.Path), true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// imageURL is the directory where a `Dockerfile` should exist
|
func (action *actionEnvironmentApplier) applyEnvironment(env map[string]string) {
|
||||||
func parseImageGithub(image string) (cloneURL *url.URL, ref string, path string, ok bool) {
|
for envKey, envValue := range action.Env {
|
||||||
re := regexp.MustCompile("^([^/@]+)/([^/@]+)(/([^@]*))?(@(.*))?$")
|
env[envKey] = envValue
|
||||||
matches := re.FindStringSubmatch(image)
|
|
||||||
|
|
||||||
if matches == nil {
|
|
||||||
return nil, "", "", false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cloneURL, err := url.Parse(fmt.Sprintf("https://github.com/%s/%s", matches[1], matches[2]))
|
for _, secret := range action.Secrets {
|
||||||
if err != nil {
|
if secretVal, ok := os.LookupEnv(secret); ok {
|
||||||
log.Debugf("Unable to parse as URL: %v", err)
|
env[secret] = secretVal
|
||||||
return nil, "", "", false
|
} else {
|
||||||
}
|
if secretCache == nil {
|
||||||
|
secretCache = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := http.Head(cloneURL.String())
|
if _, ok := secretCache[secret]; !ok {
|
||||||
if resp.StatusCode >= 400 || err != nil {
|
fmt.Printf("Provide value for '%s': ", secret)
|
||||||
log.Debugf("Unable to HEAD URL %s status=%v err=%v", cloneURL.String(), resp.StatusCode, err)
|
val, err := gopass.GetPasswdMasked()
|
||||||
return nil, "", "", false
|
if err != nil {
|
||||||
}
|
log.Fatal("abort")
|
||||||
|
}
|
||||||
|
|
||||||
ref = matches[6]
|
secretCache[secret] = string(val)
|
||||||
if ref == "" {
|
}
|
||||||
ref = "master"
|
env[secret] = secretCache[secret]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
path = matches[4]
|
|
||||||
if path == "" {
|
|
||||||
path = "."
|
|
||||||
}
|
|
||||||
|
|
||||||
return cloneURL, ref, path, true
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/actions/workflow-parser/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// return a pipeline that is run in series. pipeline is a list of steps to run in parallel
|
||||||
|
func newExecutionGraph(workflowConfig *model.Configuration, actionNames ...string) [][]string {
|
||||||
|
// first, build a list of all the necessary actions to run, and their dependencies
|
||||||
|
actionDependencies := make(map[string][]string)
|
||||||
|
for len(actionNames) > 0 {
|
||||||
|
newActionNames := make([]string, 0)
|
||||||
|
for _, aName := range actionNames {
|
||||||
|
// make sure we haven't visited this action yet
|
||||||
|
if _, ok := actionDependencies[aName]; !ok {
|
||||||
|
action := workflowConfig.GetAction(aName)
|
||||||
|
if action != nil {
|
||||||
|
actionDependencies[aName] = action.Needs
|
||||||
|
newActionNames = append(newActionNames, action.Needs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actionNames = newActionNames
|
||||||
|
}
|
||||||
|
|
||||||
|
// next, build an execution graph
|
||||||
|
graph := make([][]string, 0)
|
||||||
|
for len(actionDependencies) > 0 {
|
||||||
|
stage := make([]string, 0)
|
||||||
|
for aName, aDeps := range actionDependencies {
|
||||||
|
// make sure all deps are in the graph already
|
||||||
|
if listInLists(aDeps, graph...) {
|
||||||
|
stage = append(stage, aName)
|
||||||
|
delete(actionDependencies, aName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(stage) == 0 {
|
||||||
|
log.Fatalf("Unable to build dependency graph!")
|
||||||
|
}
|
||||||
|
graph = append(graph, stage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph
|
||||||
|
}
|
||||||
|
|
||||||
|
// return true iff all strings in srcList exist in at least one of the searchLists
|
||||||
|
func listInLists(srcList []string, searchLists ...[]string) bool {
|
||||||
|
for _, src := range srcList {
|
||||||
|
found := false
|
||||||
|
for _, searchList := range searchLists {
|
||||||
|
for _, search := range searchList {
|
||||||
|
if src == search {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
129
actions/model.go
129
actions/model.go
|
@ -1,129 +0,0 @@
|
||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/howeyc/gopass"
|
|
||||||
)
|
|
||||||
|
|
||||||
type workflowModel struct {
|
|
||||||
On string
|
|
||||||
Resolves []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type actionModel struct {
|
|
||||||
Needs []string
|
|
||||||
Uses string
|
|
||||||
Runs []string
|
|
||||||
Args []string
|
|
||||||
Env map[string]string
|
|
||||||
Secrets []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type workflowsFile struct {
|
|
||||||
Workflow map[string]workflowModel
|
|
||||||
Action map[string]actionModel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wFile *workflowsFile) getWorkflow(eventName string) (*workflowModel, string, error) {
|
|
||||||
var rtn workflowModel
|
|
||||||
for wName, w := range wFile.Workflow {
|
|
||||||
if w.On == eventName {
|
|
||||||
rtn = w
|
|
||||||
return &rtn, wName, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, "", fmt.Errorf("unsupported event: %v", eventName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wFile *workflowsFile) getAction(actionName string) (*actionModel, error) {
|
|
||||||
if a, ok := wFile.Action[actionName]; ok {
|
|
||||||
return &a, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unsupported action: %v", actionName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// return a pipeline that is run in series. pipeline is a list of steps to run in parallel
|
|
||||||
func (wFile *workflowsFile) newExecutionGraph(actionNames ...string) [][]string {
|
|
||||||
// first, build a list of all the necessary actions to run, and their dependencies
|
|
||||||
actionDependencies := make(map[string][]string)
|
|
||||||
for len(actionNames) > 0 {
|
|
||||||
newActionNames := make([]string, 0)
|
|
||||||
for _, aName := range actionNames {
|
|
||||||
// make sure we haven't visited this action yet
|
|
||||||
if _, ok := actionDependencies[aName]; !ok {
|
|
||||||
actionDependencies[aName] = wFile.Action[aName].Needs
|
|
||||||
newActionNames = append(newActionNames, wFile.Action[aName].Needs...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
actionNames = newActionNames
|
|
||||||
}
|
|
||||||
|
|
||||||
// next, build an execution graph
|
|
||||||
graph := make([][]string, 0)
|
|
||||||
for len(actionDependencies) > 0 {
|
|
||||||
stage := make([]string, 0)
|
|
||||||
for aName, aDeps := range actionDependencies {
|
|
||||||
// make sure all deps are in the graph already
|
|
||||||
if listInLists(aDeps, graph...) {
|
|
||||||
stage = append(stage, aName)
|
|
||||||
delete(actionDependencies, aName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(stage) == 0 {
|
|
||||||
log.Fatalf("Unable to build dependency graph!")
|
|
||||||
}
|
|
||||||
graph = append(graph, stage)
|
|
||||||
}
|
|
||||||
|
|
||||||
return graph
|
|
||||||
}
|
|
||||||
|
|
||||||
// return true iff all strings in srcList exist in at least one of the searchLists
|
|
||||||
func listInLists(srcList []string, searchLists ...[]string) bool {
|
|
||||||
for _, src := range srcList {
|
|
||||||
found := false
|
|
||||||
for _, searchList := range searchLists {
|
|
||||||
for _, search := range searchList {
|
|
||||||
if src == search {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var secretCache map[string]string
|
|
||||||
|
|
||||||
func (action *actionModel) applyEnvironment(env map[string]string) {
|
|
||||||
for envKey, envValue := range action.Env {
|
|
||||||
env[envKey] = envValue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, secret := range action.Secrets {
|
|
||||||
if secretVal, ok := os.LookupEnv(secret); ok {
|
|
||||||
env[secret] = secretVal
|
|
||||||
} else {
|
|
||||||
if secretCache == nil {
|
|
||||||
secretCache = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := secretCache[secret]; !ok {
|
|
||||||
fmt.Printf("Provide value for '%s': ", secret)
|
|
||||||
val, err := gopass.GetPasswdMasked()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("abort")
|
|
||||||
}
|
|
||||||
|
|
||||||
secretCache[secret] = string(val)
|
|
||||||
}
|
|
||||||
env[secret] = secretCache[secret]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl"
|
|
||||||
"github.com/hashicorp/hcl/hcl/ast"
|
|
||||||
"github.com/hashicorp/hcl/hcl/token"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseWorkflowsFile(workflowReader io.Reader) (*workflowsFile, error) {
|
|
||||||
// TODO: add validation logic
|
|
||||||
// - check for circular dependencies
|
|
||||||
// - check for valid local path refs
|
|
||||||
// - check for valid dependencies
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
_, err := buf.ReadFrom(workflowReader)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
workflows := new(workflowsFile)
|
|
||||||
|
|
||||||
astFile, err := hcl.ParseBytes(buf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rootNode := ast.Walk(astFile.Node, cleanWorkflowsAST)
|
|
||||||
err = hcl.DecodeObject(workflows, rootNode)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return workflows, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanWorkflowsAST(node ast.Node) (ast.Node, bool) {
|
|
||||||
if objectItem, ok := node.(*ast.ObjectItem); ok {
|
|
||||||
key := objectItem.Keys[0].Token.Value()
|
|
||||||
|
|
||||||
// handle condition where value is a string but should be a list
|
|
||||||
switch key {
|
|
||||||
case "args", "runs":
|
|
||||||
if literalType, ok := objectItem.Val.(*ast.LiteralType); ok {
|
|
||||||
listType := new(ast.ListType)
|
|
||||||
parts, err := parseCommand(literalType.Token.Value().(string))
|
|
||||||
if err != nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
quote := literalType.Token.Text[0]
|
|
||||||
for _, part := range parts {
|
|
||||||
part = fmt.Sprintf("%c%s%c", quote, strings.Replace(part, "\\", "\\\\", -1), quote)
|
|
||||||
listType.Add(&ast.LiteralType{
|
|
||||||
Token: token.Token{
|
|
||||||
Type: token.STRING,
|
|
||||||
Text: part,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
objectItem.Val = listType
|
|
||||||
|
|
||||||
}
|
|
||||||
case "resolves", "needs":
|
|
||||||
if literalType, ok := objectItem.Val.(*ast.LiteralType); ok {
|
|
||||||
listType := new(ast.ListType)
|
|
||||||
listType.Add(literalType)
|
|
||||||
objectItem.Val = listType
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// reused from: https://github.com/laurent22/massren/blob/ae4c57da1e09a95d9383f7eb645a9f69790dec6c/main.go#L172
|
|
||||||
// nolint: gocyclo
|
|
||||||
func parseCommand(cmd string) ([]string, error) {
|
|
||||||
var args []string
|
|
||||||
state := "start"
|
|
||||||
current := ""
|
|
||||||
quote := "\""
|
|
||||||
for i := 0; i < len(cmd); i++ {
|
|
||||||
c := cmd[i]
|
|
||||||
|
|
||||||
if state == "quotes" {
|
|
||||||
if string(c) != quote {
|
|
||||||
current += string(c)
|
|
||||||
} else {
|
|
||||||
args = append(args, current)
|
|
||||||
current = ""
|
|
||||||
state = "start"
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if c == '"' || c == '\'' {
|
|
||||||
state = "quotes"
|
|
||||||
quote = string(c)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if state == "arg" {
|
|
||||||
if c == ' ' || c == '\t' {
|
|
||||||
args = append(args, current)
|
|
||||||
current = ""
|
|
||||||
state = "start"
|
|
||||||
} else {
|
|
||||||
current += string(c)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if c != ' ' && c != '\t' {
|
|
||||||
state = "arg"
|
|
||||||
current += string(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if state == "quotes" {
|
|
||||||
return []string{}, fmt.Errorf("unclosed quote in command line: %s", cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
if current != "" {
|
|
||||||
args = append(args, current)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) == 0 {
|
|
||||||
return []string{}, errors.New("empty command line")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Parsed literal %+q to list %+q", cmd, args)
|
|
||||||
|
|
||||||
return args, nil
|
|
||||||
}
|
|
|
@ -1,161 +0,0 @@
|
||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseWorkflowsFile(t *testing.T) {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
|
|
||||||
conf := `
|
|
||||||
workflow "build-and-deploy" {
|
|
||||||
on = "push"
|
|
||||||
resolves = ["deploy"]
|
|
||||||
}
|
|
||||||
|
|
||||||
action "build" {
|
|
||||||
uses = "./action1"
|
|
||||||
args = "echo 'build'"
|
|
||||||
}
|
|
||||||
|
|
||||||
action "test" {
|
|
||||||
uses = "docker://ubuntu:18.04"
|
|
||||||
runs = "echo 'test'"
|
|
||||||
needs = ["build"]
|
|
||||||
}
|
|
||||||
|
|
||||||
action "deploy" {
|
|
||||||
uses = "./action2"
|
|
||||||
args = ["echo","deploy"]
|
|
||||||
needs = ["test"]
|
|
||||||
}
|
|
||||||
|
|
||||||
action "docker-login" {
|
|
||||||
uses = "docker://docker"
|
|
||||||
runs = ["sh", "-c", "echo $DOCKER_AUTH | docker login --username $REGISTRY_USER --password-stdin"]
|
|
||||||
secrets = ["DOCKER_AUTH"]
|
|
||||||
env = {
|
|
||||||
REGISTRY_USER = "username"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
action "unit-tests" {
|
|
||||||
uses = "./scripts/github_actions"
|
|
||||||
runs = "yarn test:ci-unittest || echo \"Unit tests failed, but running danger to present the results!\" 2>&1"
|
|
||||||
}
|
|
||||||
|
|
||||||
action "regex-in-args" {
|
|
||||||
uses = "actions/bin/filter@master"
|
|
||||||
args = "tag v?[0-9]+\\.[0-9]+\\.[0-9]+"
|
|
||||||
}
|
|
||||||
|
|
||||||
action "regex-in-args-array" {
|
|
||||||
uses = "actions/bin/filter@master"
|
|
||||||
args = ["tag","v?[0-9]+\\.[0-9]+\\.[0-9]+"]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
workflows, err := parseWorkflowsFile(strings.NewReader(conf))
|
|
||||||
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, 1, len(workflows.Workflow))
|
|
||||||
|
|
||||||
w, wName, _ := workflows.getWorkflow("push")
|
|
||||||
assert.Equal(t, "build-and-deploy", wName)
|
|
||||||
assert.ElementsMatch(t, []string{"deploy"}, w.Resolves)
|
|
||||||
|
|
||||||
actions := []struct {
|
|
||||||
name string
|
|
||||||
uses string
|
|
||||||
needs []string
|
|
||||||
runs []string
|
|
||||||
args []string
|
|
||||||
secrets []string
|
|
||||||
}{
|
|
||||||
{"build",
|
|
||||||
"./action1",
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
[]string{"echo", "build"},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{"test",
|
|
||||||
"docker://ubuntu:18.04",
|
|
||||||
[]string{"build"},
|
|
||||||
[]string{"echo", "test"},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{"deploy",
|
|
||||||
"./action2",
|
|
||||||
[]string{"test"},
|
|
||||||
nil,
|
|
||||||
[]string{"echo", "deploy"},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{"docker-login",
|
|
||||||
"docker://docker",
|
|
||||||
nil,
|
|
||||||
[]string{"sh", "-c", "echo $DOCKER_AUTH | docker login --username $REGISTRY_USER --password-stdin"},
|
|
||||||
nil,
|
|
||||||
[]string{"DOCKER_AUTH"},
|
|
||||||
},
|
|
||||||
{"unit-tests",
|
|
||||||
"./scripts/github_actions",
|
|
||||||
nil,
|
|
||||||
[]string{"yarn", "test:ci-unittest", "||", "echo", "Unit tests failed, but running danger to present the results!", "2>&1"},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{"regex-in-args",
|
|
||||||
"actions/bin/filter@master",
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
[]string{"tag", `v?[0-9]+\.[0-9]+\.[0-9]+`},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{"regex-in-args-array",
|
|
||||||
"actions/bin/filter@master",
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
[]string{"tag", `v?[0-9]+\.[0-9]+\.[0-9]+`},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, exp := range actions {
|
|
||||||
act, _ := workflows.getAction(exp.name)
|
|
||||||
assert.Equal(t, exp.uses, act.Uses, "[%s] Uses", exp.name)
|
|
||||||
if exp.needs == nil {
|
|
||||||
assert.Nil(t, act.Needs, "[%s] Needs", exp.name)
|
|
||||||
} else {
|
|
||||||
assert.ElementsMatch(t, exp.needs, act.Needs, "[%s] Needs", exp.name)
|
|
||||||
}
|
|
||||||
if exp.runs == nil {
|
|
||||||
assert.Nil(t, act.Runs, "[%s] Runs", exp.name)
|
|
||||||
} else {
|
|
||||||
assert.ElementsMatch(t, exp.runs, act.Runs, "[%s] Runs", exp.name)
|
|
||||||
}
|
|
||||||
if exp.args == nil {
|
|
||||||
assert.Nil(t, act.Args, "[%s] Args", exp.name)
|
|
||||||
} else {
|
|
||||||
assert.ElementsMatch(t, exp.args, act.Args, "[%s] Args", exp.name)
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if exp.env == nil {
|
|
||||||
assert.Nil(t, act.Env, "[%s] Env", exp.name)
|
|
||||||
} else {
|
|
||||||
assert.ElementsMatch(t, exp.env, act.Env, "[%s] Env", exp.name)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if exp.secrets == nil {
|
|
||||||
assert.Nil(t, act.Secrets, "[%s] Secrets", exp.name)
|
|
||||||
} else {
|
|
||||||
assert.ElementsMatch(t, exp.secrets, act.Secrets, "[%s] Secrets", exp.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +1,23 @@
|
||||||
package actions
|
package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/actions/workflow-parser/model"
|
||||||
|
"github.com/actions/workflow-parser/parser"
|
||||||
"github.com/nektos/act/common"
|
"github.com/nektos/act/common"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type runnerImpl struct {
|
type runnerImpl struct {
|
||||||
config *RunnerConfig
|
config *RunnerConfig
|
||||||
workflows *workflowsFile
|
workflowConfig *model.Configuration
|
||||||
tempDir string
|
tempDir string
|
||||||
eventJSON string
|
eventJSON string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRunner Creates a new Runner
|
// NewRunner Creates a new Runner
|
||||||
|
@ -56,7 +59,13 @@ func (runner *runnerImpl) setupWorkflows() error {
|
||||||
|
|
||||||
defer workflowReader.Close()
|
defer workflowReader.Close()
|
||||||
|
|
||||||
runner.workflows, err = parseWorkflowsFile(workflowReader)
|
runner.workflowConfig, err = parser.Parse(workflowReader)
|
||||||
|
if err != nil {
|
||||||
|
parserError := err.(*parser.ParserError)
|
||||||
|
for _, e := range parserError.Errors {
|
||||||
|
fmt.Fprintln(os.Stderr, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +97,7 @@ func (runner *runnerImpl) resolvePath(path string) string {
|
||||||
func (runner *runnerImpl) ListEvents() []string {
|
func (runner *runnerImpl) ListEvents() []string {
|
||||||
log.Debugf("Listing all events")
|
log.Debugf("Listing all events")
|
||||||
events := make([]string, 0)
|
events := make([]string, 0)
|
||||||
for _, w := range runner.workflows.Workflow {
|
for _, w := range runner.workflowConfig.Workflows {
|
||||||
events = append(events, w.On)
|
events = append(events, w.On)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,17 +112,14 @@ func (runner *runnerImpl) ListEvents() []string {
|
||||||
// GraphEvent builds an execution path
|
// GraphEvent builds an execution path
|
||||||
func (runner *runnerImpl) GraphEvent(eventName string) ([][]string, error) {
|
func (runner *runnerImpl) GraphEvent(eventName string) ([][]string, error) {
|
||||||
log.Debugf("Listing actions for event '%s'", eventName)
|
log.Debugf("Listing actions for event '%s'", eventName)
|
||||||
workflow, _, err := runner.workflows.getWorkflow(eventName)
|
resolves := runner.resolveEvent(runner.config.EventName)
|
||||||
if err != nil {
|
return newExecutionGraph(runner.workflowConfig, resolves...), nil
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return runner.workflows.newExecutionGraph(workflow.Resolves...), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunAction runs a set of actions in parallel, and their dependencies
|
// RunAction runs a set of actions in parallel, and their dependencies
|
||||||
func (runner *runnerImpl) RunActions(actionNames ...string) error {
|
func (runner *runnerImpl) RunActions(actionNames ...string) error {
|
||||||
log.Debugf("Running actions %+q", actionNames)
|
log.Debugf("Running actions %+q", actionNames)
|
||||||
graph := runner.workflows.newExecutionGraph(actionNames...)
|
graph := newExecutionGraph(runner.workflowConfig, actionNames...)
|
||||||
|
|
||||||
pipeline := make([]common.Executor, 0)
|
pipeline := make([]common.Executor, 0)
|
||||||
for _, actions := range graph {
|
for _, actions := range graph {
|
||||||
|
@ -131,15 +137,32 @@ func (runner *runnerImpl) RunActions(actionNames ...string) error {
|
||||||
// RunEvent runs the actions for a single event
|
// RunEvent runs the actions for a single event
|
||||||
func (runner *runnerImpl) RunEvent() error {
|
func (runner *runnerImpl) RunEvent() error {
|
||||||
log.Debugf("Running event '%s'", runner.config.EventName)
|
log.Debugf("Running event '%s'", runner.config.EventName)
|
||||||
workflow, _, err := runner.workflows.getWorkflow(runner.config.EventName)
|
resolves := runner.resolveEvent(runner.config.EventName)
|
||||||
if err != nil {
|
log.Debugf("Running actions %s -> %s", runner.config.EventName, resolves)
|
||||||
return err
|
return runner.RunActions(resolves...)
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Running actions %s -> %s", runner.config.EventName, workflow.Resolves)
|
|
||||||
return runner.RunActions(workflow.Resolves...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (runner *runnerImpl) Close() error {
|
func (runner *runnerImpl) Close() error {
|
||||||
return os.RemoveAll(runner.tempDir)
|
return os.RemoveAll(runner.tempDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get list of resolves for an event
|
||||||
|
func (runner *runnerImpl) resolveEvent(eventName string) []string {
|
||||||
|
workflows := runner.workflowConfig.GetWorkflows(runner.config.EventName)
|
||||||
|
resolves := make([]string, 0)
|
||||||
|
for _, workflow := range workflows {
|
||||||
|
for _, resolve := range workflow.Resolves {
|
||||||
|
found := false
|
||||||
|
for _, r := range resolves {
|
||||||
|
if r == resolve {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
resolves = append(resolves, resolve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolves
|
||||||
|
}
|
||||||
|
|
|
@ -9,19 +9,20 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/actions/workflow-parser/model"
|
||||||
"github.com/nektos/act/common"
|
"github.com/nektos/act/common"
|
||||||
"github.com/nektos/act/container"
|
"github.com/nektos/act/container"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
|
func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
|
||||||
action, err := runner.workflows.getAction(actionName)
|
action := runner.workflowConfig.GetAction(actionName)
|
||||||
if err != nil {
|
if action == nil {
|
||||||
return common.NewErrorExecutor(err)
|
return common.NewErrorExecutor(fmt.Errorf("Unable to find action named '%s'", actionName))
|
||||||
}
|
}
|
||||||
|
|
||||||
env := make(map[string]string)
|
env := make(map[string]string)
|
||||||
for _, applier := range []environmentApplier{action, runner} {
|
for _, applier := range []environmentApplier{newActionEnvironmentApplier(action), runner} {
|
||||||
applier.applyEnvironment(env)
|
applier.applyEnvironment(env)
|
||||||
}
|
}
|
||||||
env["GITHUB_ACTION"] = actionName
|
env["GITHUB_ACTION"] = actionName
|
||||||
|
@ -37,39 +38,51 @@ func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
|
||||||
|
|
||||||
var image string
|
var image string
|
||||||
executors := make([]common.Executor, 0)
|
executors := make([]common.Executor, 0)
|
||||||
if imageRef, ok := parseImageReference(action.Uses); ok {
|
switch uses := action.Uses.(type) {
|
||||||
|
|
||||||
|
case *model.UsesDockerImage:
|
||||||
|
image = uses.Image
|
||||||
executors = append(executors, container.NewDockerPullExecutor(container.NewDockerPullExecutorInput{
|
executors = append(executors, container.NewDockerPullExecutor(container.NewDockerPullExecutorInput{
|
||||||
DockerExecutorInput: in,
|
DockerExecutorInput: in,
|
||||||
Image: imageRef,
|
Image: image,
|
||||||
}))
|
}))
|
||||||
image = imageRef
|
|
||||||
} else if contextDir, imageTag, ok := parseImageLocal(runner.config.WorkingDir, action.Uses); ok {
|
case *model.UsesPath:
|
||||||
|
contextDir := filepath.Join(runner.config.WorkingDir, uses.String())
|
||||||
|
sha, _, err := common.FindGitRevision(contextDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Unable to determine git revision: %v", err)
|
||||||
|
sha = "latest"
|
||||||
|
}
|
||||||
|
image = fmt.Sprintf("%s:%s", filepath.Base(contextDir), sha)
|
||||||
|
|
||||||
executors = append(executors, container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
executors = append(executors, container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||||
DockerExecutorInput: in,
|
DockerExecutorInput: in,
|
||||||
ContextDir: contextDir,
|
ContextDir: contextDir,
|
||||||
ImageTag: imageTag,
|
ImageTag: image,
|
||||||
}))
|
}))
|
||||||
image = imageTag
|
|
||||||
} else if cloneURL, ref, path, ok := parseImageGithub(action.Uses); ok {
|
case *model.UsesRepository:
|
||||||
cloneDir := filepath.Join(os.TempDir(), "act", action.Uses)
|
image = fmt.Sprintf("%s:%s", filepath.Base(uses.Repository), uses.Ref)
|
||||||
|
cloneURL := fmt.Sprintf("https://github.com/%s", uses.Repository)
|
||||||
|
|
||||||
|
cloneDir := filepath.Join(os.TempDir(), "act", action.Uses.String())
|
||||||
executors = append(executors, common.NewGitCloneExecutor(common.NewGitCloneExecutorInput{
|
executors = append(executors, common.NewGitCloneExecutor(common.NewGitCloneExecutorInput{
|
||||||
URL: cloneURL,
|
URL: cloneURL,
|
||||||
Ref: ref,
|
Ref: uses.Ref,
|
||||||
Dir: cloneDir,
|
Dir: cloneDir,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Dryrun: runner.config.Dryrun,
|
Dryrun: runner.config.Dryrun,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
contextDir := filepath.Join(cloneDir, path)
|
contextDir := filepath.Join(cloneDir, uses.Path)
|
||||||
imageTag := fmt.Sprintf("%s:%s", filepath.Base(cloneURL.Path), ref)
|
|
||||||
|
|
||||||
executors = append(executors, container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
executors = append(executors, container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||||
DockerExecutorInput: in,
|
DockerExecutorInput: in,
|
||||||
ContextDir: contextDir,
|
ContextDir: contextDir,
|
||||||
ImageTag: imageTag,
|
ImageTag: image,
|
||||||
}))
|
}))
|
||||||
image = imageTag
|
|
||||||
} else {
|
default:
|
||||||
return common.NewErrorExecutor(fmt.Errorf("unable to determine executor type for image '%s'", action.Uses))
|
return common.NewErrorExecutor(fmt.Errorf("unable to determine executor type for image '%s'", action.Uses))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,8 +97,8 @@ func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
|
||||||
}
|
}
|
||||||
executors = append(executors, container.NewDockerRunExecutor(container.NewDockerRunExecutorInput{
|
executors = append(executors, container.NewDockerRunExecutor(container.NewDockerRunExecutorInput{
|
||||||
DockerExecutorInput: in,
|
DockerExecutorInput: in,
|
||||||
Cmd: action.Args,
|
Cmd: action.Args.Parsed,
|
||||||
Entrypoint: action.Runs,
|
Entrypoint: action.Runs.Parsed,
|
||||||
Image: image,
|
Image: image,
|
||||||
WorkingDir: "/github/workspace",
|
WorkingDir: "/github/workspace",
|
||||||
Env: envList,
|
Env: envList,
|
||||||
|
@ -105,7 +118,11 @@ func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
|
||||||
func (runner *runnerImpl) applyEnvironment(env map[string]string) {
|
func (runner *runnerImpl) applyEnvironment(env map[string]string) {
|
||||||
repoPath := runner.config.WorkingDir
|
repoPath := runner.config.WorkingDir
|
||||||
|
|
||||||
_, workflowName, _ := runner.workflows.getWorkflow(runner.config.EventName)
|
workflows := runner.workflowConfig.GetWorkflows(runner.config.EventName)
|
||||||
|
if len(workflows) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
workflowName := workflows[0].Identifier
|
||||||
|
|
||||||
env["HOME"] = "/github/home"
|
env["HOME"] = "/github/home"
|
||||||
env["GITHUB_ACTOR"] = "nektos/act"
|
env["GITHUB_ACTOR"] = "nektos/act"
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nektos/act/common"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseImageReference(t *testing.T) {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
tables := []struct {
|
|
||||||
refIn string
|
|
||||||
refOut string
|
|
||||||
ok bool
|
|
||||||
}{
|
|
||||||
{"docker://myhost.com/foo/bar", "myhost.com/foo/bar", true},
|
|
||||||
{"docker://ubuntu", "ubuntu", true},
|
|
||||||
{"docker://ubuntu:18.04", "ubuntu:18.04", true},
|
|
||||||
{"docker://cibuilds/hugo:0.53", "cibuilds/hugo:0.53", true},
|
|
||||||
{"http://google.com:8080", "", false},
|
|
||||||
{"./foo", "", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, table := range tables {
|
|
||||||
refOut, ok := parseImageReference(table.refIn)
|
|
||||||
assert.Equal(t, table.refOut, refOut)
|
|
||||||
assert.Equal(t, table.ok, ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseImageLocal(t *testing.T) {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
tables := []struct {
|
|
||||||
pathIn string
|
|
||||||
contextDir string
|
|
||||||
refTag string
|
|
||||||
ok bool
|
|
||||||
}{
|
|
||||||
{"docker://myhost.com/foo/bar", "", "", false},
|
|
||||||
{"http://google.com:8080", "", "", false},
|
|
||||||
{"example/action1", "", "", false},
|
|
||||||
{"./example/action1", "/example/action1", "action1:", true},
|
|
||||||
}
|
|
||||||
|
|
||||||
revision, _, err := common.FindGitRevision(".")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
basedir, err := filepath.Abs("..")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
for _, table := range tables {
|
|
||||||
contextDir, refTag, ok := parseImageLocal(basedir, table.pathIn)
|
|
||||||
assert.Equal(t, table.ok, ok, "ok match for %s", table.pathIn)
|
|
||||||
if ok {
|
|
||||||
assert.Equal(t, fmt.Sprintf("%s%s", basedir, table.contextDir), contextDir, "context dir doesn't match for %s", table.pathIn)
|
|
||||||
assert.Equal(t, fmt.Sprintf("%s%s", table.refTag, revision), refTag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
func TestParseImageGithub(t *testing.T) {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
tables := []struct {
|
|
||||||
image string
|
|
||||||
cloneURL string
|
|
||||||
ref string
|
|
||||||
path string
|
|
||||||
ok bool
|
|
||||||
}{
|
|
||||||
{"nektos/act", "https://github.com/nektos/act", "master", ".", true},
|
|
||||||
{"nektos/act/foo", "https://github.com/nektos/act", "master", "foo", true},
|
|
||||||
{"nektos/act@xxxxx", "https://github.com/nektos/act", "xxxxx", ".", true},
|
|
||||||
{"nektos/act/bar/baz@zzzzz", "https://github.com/nektos/act", "zzzzz", "bar/baz", true},
|
|
||||||
{"assimovt/actions-github-deploy/github-deploy@deployment-status-metadata", "https://github.com/assimovt/actions-github-deploy", "deployment-status-metadata", "github-deploy", true},
|
|
||||||
{"nektos/zzzzundefinedzzzz", "", "", "", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, table := range tables {
|
|
||||||
cloneURL, ref, path, ok := parseImageGithub(table.image)
|
|
||||||
assert.Equal(t, table.ok, ok, "ok match for %s", table.image)
|
|
||||||
if ok {
|
|
||||||
assert.Equal(t, table.cloneURL, cloneURL.String())
|
|
||||||
assert.Equal(t, table.ref, ref)
|
|
||||||
assert.Equal(t, table.path, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -185,7 +184,7 @@ func findGitDirectory(fromFile string) (string, error) {
|
||||||
|
|
||||||
// NewGitCloneExecutorInput the input for the NewGitCloneExecutor
|
// NewGitCloneExecutorInput the input for the NewGitCloneExecutor
|
||||||
type NewGitCloneExecutorInput struct {
|
type NewGitCloneExecutorInput struct {
|
||||||
URL *url.URL
|
URL string
|
||||||
Ref string
|
Ref string
|
||||||
Dir string
|
Dir string
|
||||||
Logger *log.Entry
|
Logger *log.Entry
|
||||||
|
@ -195,8 +194,8 @@ type NewGitCloneExecutorInput struct {
|
||||||
// NewGitCloneExecutor creates an executor to clone git repos
|
// NewGitCloneExecutor creates an executor to clone git repos
|
||||||
func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
|
func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
|
||||||
return func() error {
|
return func() error {
|
||||||
input.Logger.Infof("git clone '%s'", input.URL.String())
|
input.Logger.Infof("git clone '%s'", input.URL)
|
||||||
input.Logger.Debugf(" cloning %s to %s", input.URL.String(), input.Dir)
|
input.Logger.Debugf(" cloning %s to %s", input.URL, input.Dir)
|
||||||
|
|
||||||
if input.Dryrun {
|
if input.Dryrun {
|
||||||
return nil
|
return nil
|
||||||
|
@ -210,7 +209,7 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
|
||||||
r, err := git.PlainOpen(input.Dir)
|
r, err := git.PlainOpen(input.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r, err = git.PlainClone(input.Dir, false, &git.CloneOptions{
|
r, err = git.PlainClone(input.Dir, false, &git.CloneOptions{
|
||||||
URL: input.URL.String(),
|
URL: input.URL,
|
||||||
Progress: input.Logger.WriterLevel(log.DebugLevel),
|
Progress: input.Logger.WriterLevel(log.DebugLevel),
|
||||||
ReferenceName: refName,
|
ReferenceName: refName,
|
||||||
})
|
})
|
||||||
|
@ -231,7 +230,7 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
|
||||||
if err != nil && err.Error() != "already up-to-date" {
|
if err != nil && err.Error() != "already up-to-date" {
|
||||||
input.Logger.Errorf("Unable to pull %s: %v", refName, err)
|
input.Logger.Errorf("Unable to pull %s: %v", refName, err)
|
||||||
}
|
}
|
||||||
input.Logger.Debugf("Cloned %s to %s", input.URL.String(), input.Dir)
|
input.Logger.Debugf("Cloned %s to %s", input.URL, input.Dir)
|
||||||
|
|
||||||
err = w.Checkout(&git.CheckoutOptions{
|
err = w.Checkout(&git.CheckoutOptions{
|
||||||
Branch: refName,
|
Branch: refName,
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -4,6 +4,7 @@ require (
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||||
github.com/Microsoft/go-winio v0.4.11 // indirect
|
github.com/Microsoft/go-winio v0.4.11 // indirect
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||||
|
github.com/actions/workflow-parser v0.0.0-20190130154146-aac54e2ba131
|
||||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
|
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
|
||||||
github.com/docker/distribution v2.7.0+incompatible // indirect
|
github.com/docker/distribution v2.7.0+incompatible // indirect
|
||||||
github.com/docker/docker v1.13.1
|
github.com/docker/docker v1.13.1
|
||||||
|
@ -14,7 +15,7 @@ require (
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
github.com/gorilla/mux v1.6.2 // indirect
|
github.com/gorilla/mux v1.6.2 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
|
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||||
|
@ -25,6 +26,7 @@ require (
|
||||||
github.com/sirupsen/logrus v1.3.0
|
github.com/sirupsen/logrus v1.3.0
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||||
|
github.com/soniakeys/graph v0.0.0 // indirect
|
||||||
github.com/spf13/cobra v0.0.3
|
github.com/spf13/cobra v0.0.3
|
||||||
github.com/spf13/pflag v1.0.3 // indirect
|
github.com/spf13/pflag v1.0.3 // indirect
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/stretchr/testify v1.3.0
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -5,6 +5,8 @@ github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6
|
||||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||||
|
github.com/actions/workflow-parser v0.0.0-20190130154146-aac54e2ba131 h1:f81A6l1wPc9LdSZ85yJb+YuZxHiweb6D3p1l/bKDbJg=
|
||||||
|
github.com/actions/workflow-parser v0.0.0-20190130154146-aac54e2ba131/go.mod h1:jz9ZVl8zUIcjMfDQearQjvUHIBhx9l1ys4keDd6be34=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||||
|
@ -94,6 +96,10 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
|
github.com/soniakeys/bits v1.0.0 h1:Rune9VFefdJvLE0Q5iRCVGiKdSu2iDihs2I6SCm7evw=
|
||||||
|
github.com/soniakeys/bits v1.0.0/go.mod h1:7yJHB//UizrUr64VFneewK6SX5oeCf0SMbDYe2ey1JA=
|
||||||
|
github.com/soniakeys/graph v0.0.0 h1:C/Rr8rv9wbhZIsYHcWJFoI84pkipJocMYdRteE+/PQA=
|
||||||
|
github.com/soniakeys/graph v0.0.0/go.mod h1:lxpIbor/bIzWUAqvt1Dx92Hr63uWeyuEAbPnsjYbVwM=
|
||||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
|
|
|
@ -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.
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,807 @@
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/actions/workflow-parser/model"
|
||||||
|
"github.com/hashicorp/hcl"
|
||||||
|
"github.com/hashicorp/hcl/hcl/ast"
|
||||||
|
hclparser "github.com/hashicorp/hcl/hcl/parser"
|
||||||
|
"github.com/hashicorp/hcl/hcl/token"
|
||||||
|
"github.com/soniakeys/graph"
|
||||||
|
)
|
||||||
|
|
||||||
|
const minVersion = 0
|
||||||
|
const maxVersion = 0
|
||||||
|
const maxSecrets = 100
|
||||||
|
|
||||||
|
type parseState struct {
|
||||||
|
Version int
|
||||||
|
Actions []*model.Action
|
||||||
|
Workflows []*model.Workflow
|
||||||
|
Errors ErrorList
|
||||||
|
|
||||||
|
posMap map[interface{}]ast.Node
|
||||||
|
suppressSeverity Severity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses a .workflow file and return the actions and global variables found within.
|
||||||
|
func Parse(reader io.Reader, options ...OptionFunc) (*model.Configuration, error) {
|
||||||
|
// FIXME - check context for deadline?
|
||||||
|
b, err := ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
root, err := hcl.ParseBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
if pe, ok := err.(*hclparser.PosError); ok {
|
||||||
|
pos := ErrorPos{File: pe.Pos.Filename, Line: pe.Pos.Line, Column: pe.Pos.Column}
|
||||||
|
errors := ErrorList{newFatal(pos, pe.Err.Error())}
|
||||||
|
return nil, &ParserError{
|
||||||
|
message: pe.Err.Error(),
|
||||||
|
Errors: errors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := parseAndValidate(root.Node, options...)
|
||||||
|
if len(ps.Errors) > 0 {
|
||||||
|
return nil, &ParserError{
|
||||||
|
message: "unable to parse and validate",
|
||||||
|
Errors: ps.Errors,
|
||||||
|
Actions: ps.Actions,
|
||||||
|
Workflows: ps.Workflows,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Configuration{
|
||||||
|
Actions: ps.Actions,
|
||||||
|
Workflows: ps.Workflows,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseAndValidate converts a HCL AST into a parseState and validates
|
||||||
|
// high-level structure.
|
||||||
|
// Parameters:
|
||||||
|
// - root - the contents of a .workflow file, as AST
|
||||||
|
// Returns:
|
||||||
|
// - a parseState structure containing actions and workflow definitions
|
||||||
|
func parseAndValidate(root ast.Node, options ...OptionFunc) *parseState {
|
||||||
|
ps := &parseState{
|
||||||
|
posMap: make(map[interface{}]ast.Node),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
option(ps)
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.parseRoot(root)
|
||||||
|
ps.validate()
|
||||||
|
ps.Errors.sort()
|
||||||
|
|
||||||
|
return ps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *parseState) validate() {
|
||||||
|
ps.analyzeDependencies()
|
||||||
|
ps.checkCircularDependencies()
|
||||||
|
ps.checkActions()
|
||||||
|
ps.checkFlows()
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniqStrings(items []string) []string {
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
ret := make([]string, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
if !seen[item] {
|
||||||
|
seen[item] = true
|
||||||
|
ret = append(ret, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkCircularDependencies finds loops in the action graph.
|
||||||
|
// It emits a fatal error for each cycle it finds, in the order (top to
|
||||||
|
// bottom, left to right) they appear in the .workflow file.
|
||||||
|
func (ps *parseState) checkCircularDependencies() {
|
||||||
|
// make a map from action name to node ID, which is the index in the ps.Actions array
|
||||||
|
// That is, ps.Actions[actionmap[X]].Identifier == X
|
||||||
|
actionmap := make(map[string]graph.NI)
|
||||||
|
for i, action := range ps.Actions {
|
||||||
|
actionmap[action.Identifier] = graph.NI(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make an adjacency list representation of the action dependency graph
|
||||||
|
adjList := make(graph.AdjacencyList, len(ps.Actions))
|
||||||
|
for i, action := range ps.Actions {
|
||||||
|
adjList[i] = make([]graph.NI, 0, len(action.Needs))
|
||||||
|
for _, depName := range action.Needs {
|
||||||
|
if depIdx, ok := actionmap[depName]; ok {
|
||||||
|
adjList[i] = append(adjList[i], depIdx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find cycles, and print a fatal error for each one
|
||||||
|
g := graph.Directed{AdjacencyList: adjList}
|
||||||
|
g.Cycles(func(cycle []graph.NI) bool {
|
||||||
|
node := ps.posMap[&ps.Actions[cycle[len(cycle)-1]].Needs]
|
||||||
|
ps.addFatal(node, "Circular dependency on `%s'", ps.Actions[cycle[0]].Identifier)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkActions returns error if any actions are syntactically correct but
|
||||||
|
// have structural errors
|
||||||
|
func (ps *parseState) checkActions() {
|
||||||
|
secrets := make(map[string]bool)
|
||||||
|
for _, t := range ps.Actions {
|
||||||
|
// Ensure the Action has a `uses` attribute
|
||||||
|
if t.Uses == nil {
|
||||||
|
ps.addError(ps.posMap[t], "Action `%s' must have a `uses' attribute", t.Identifier)
|
||||||
|
// continue, checking other actions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure there aren't too many secrets
|
||||||
|
for _, str := range t.Secrets {
|
||||||
|
if !secrets[str] {
|
||||||
|
secrets[str] = true
|
||||||
|
if len(secrets) == maxSecrets+1 {
|
||||||
|
ps.addError(ps.posMap[&t.Secrets], "All actions combined must not have more than %d unique secrets", maxSecrets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that no environment variable or secret begins with
|
||||||
|
// "GITHUB_", unless it's "GITHUB_TOKEN".
|
||||||
|
// Also ensure that all environment variable names come from the legal
|
||||||
|
// form for environment variable names.
|
||||||
|
// Finally, ensure that the same key name isn't used more than once
|
||||||
|
// between env and secrets, combined.
|
||||||
|
for k := range t.Env {
|
||||||
|
ps.checkEnvironmentVariable(k, ps.posMap[&t.Env])
|
||||||
|
}
|
||||||
|
secretVars := make(map[string]bool)
|
||||||
|
for _, k := range t.Secrets {
|
||||||
|
ps.checkEnvironmentVariable(k, ps.posMap[&t.Secrets])
|
||||||
|
if _, found := t.Env[k]; found {
|
||||||
|
ps.addError(ps.posMap[&t.Secrets], "Secret `%s' conflicts with an environment variable with the same name", k)
|
||||||
|
}
|
||||||
|
if secretVars[k] {
|
||||||
|
ps.addWarning(ps.posMap[&t.Secrets], "Secret `%s' redefined", k)
|
||||||
|
}
|
||||||
|
secretVars[k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var envVarChecker = regexp.MustCompile(`\A[A-Za-z_][A-Za-z_0-9]*\z`)
|
||||||
|
|
||||||
|
func (ps *parseState) checkEnvironmentVariable(key string, node ast.Node) {
|
||||||
|
if key != "GITHUB_TOKEN" && strings.HasPrefix(key, "GITHUB_") {
|
||||||
|
ps.addWarning(node, "Environment variables and secrets beginning with `GITHUB_' are reserved")
|
||||||
|
}
|
||||||
|
if !envVarChecker.MatchString(key) {
|
||||||
|
ps.addWarning(node, "Environment variables and secrets must contain only A-Z, a-z, 0-9, and _ characters, got `%s'", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkFlows appends an error if any workflows are syntactically correct but
|
||||||
|
// have structural errors
|
||||||
|
func (ps *parseState) checkFlows() {
|
||||||
|
actionmap := makeActionMap(ps.Actions)
|
||||||
|
for _, f := range ps.Workflows {
|
||||||
|
// make sure there's an `on` attribute
|
||||||
|
if f.On == "" {
|
||||||
|
ps.addError(ps.posMap[f], "Workflow `%s' must have an `on' attribute", f.Identifier)
|
||||||
|
// continue, checking other workflows
|
||||||
|
} else if !model.IsAllowedEventType(f.On) {
|
||||||
|
ps.addError(ps.posMap[&f.On], "Workflow `%s' has unknown `on' value `%s'", f.Identifier, f.On)
|
||||||
|
// continue, checking other workflows
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure that the actions that are resolved all exist
|
||||||
|
for _, actionID := range f.Resolves {
|
||||||
|
_, ok := actionmap[actionID]
|
||||||
|
if !ok {
|
||||||
|
ps.addError(ps.posMap[&f.Resolves], "Workflow `%s' resolves unknown action `%s'", f.Identifier, actionID)
|
||||||
|
// continue, checking other workflows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeActionMap(actions []*model.Action) map[string]*model.Action {
|
||||||
|
actionmap := make(map[string]*model.Action)
|
||||||
|
for _, action := range actions {
|
||||||
|
actionmap[action.Identifier] = action
|
||||||
|
}
|
||||||
|
return actionmap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in Action dependencies for all actions based on explicit dependencies
|
||||||
|
// declarations.
|
||||||
|
//
|
||||||
|
// ps.Actions is an array of Action objects, as parsed. The Action objects in
|
||||||
|
// this array are mutated, by setting Action.dependencies for each.
|
||||||
|
func (ps *parseState) analyzeDependencies() {
|
||||||
|
actionmap := makeActionMap(ps.Actions)
|
||||||
|
for _, action := range ps.Actions {
|
||||||
|
// analyze explicit dependencies for each "needs" keyword
|
||||||
|
ps.analyzeNeeds(action, actionmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// uniq all the dependencies lists
|
||||||
|
for _, action := range ps.Actions {
|
||||||
|
if len(action.Needs) >= 2 {
|
||||||
|
action.Needs = uniqStrings(action.Needs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *parseState) analyzeNeeds(action *model.Action, actionmap map[string]*model.Action) {
|
||||||
|
for _, need := range action.Needs {
|
||||||
|
_, ok := actionmap[need]
|
||||||
|
if !ok {
|
||||||
|
ps.addError(ps.posMap[&action.Needs], "Action `%s' needs nonexistent action `%s'", action.Identifier, need)
|
||||||
|
// continue, checking other actions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// literalToStringMap converts a object value from the AST to a
|
||||||
|
// map[string]string. For example, the HCL `{ a="b" c="d" }` becomes the
|
||||||
|
// Go expression map[string]string{ "a": "b", "c": "d" }.
|
||||||
|
// If the value doesn't adhere to that format -- e.g.,
|
||||||
|
// if it's not an object, or it has non-assignment attributes, or if any
|
||||||
|
// of its values are anything other than a string, the function appends an
|
||||||
|
// appropriate error.
|
||||||
|
func (ps *parseState) literalToStringMap(node ast.Node) map[string]string {
|
||||||
|
obj, ok := node.(*ast.ObjectType)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
ps.addError(node, "Expected object, got %s", typename(node))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.checkAssignmentsOnly(obj.List, "")
|
||||||
|
|
||||||
|
ret := make(map[string]string)
|
||||||
|
for _, item := range obj.List.Items {
|
||||||
|
if !isAssignment(item) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
str, ok := ps.literalToString(item.Val)
|
||||||
|
if ok {
|
||||||
|
key := ps.identString(item.Keys[0].Token)
|
||||||
|
if key != "" {
|
||||||
|
if _, found := ret[key]; found {
|
||||||
|
ps.addWarning(node, "Environment variable `%s' redefined", key)
|
||||||
|
}
|
||||||
|
ret[key] = str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *parseState) identString(t token.Token) string {
|
||||||
|
switch t.Type {
|
||||||
|
case token.STRING:
|
||||||
|
return t.Value().(string)
|
||||||
|
case token.IDENT:
|
||||||
|
return t.Text
|
||||||
|
default:
|
||||||
|
ps.addErrorFromToken(t,
|
||||||
|
"Each identifier should be a string, got %s",
|
||||||
|
strings.ToLower(t.Type.String()))
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// literalToStringArray converts a list value from the AST to a []string.
|
||||||
|
// For example, the HCL `[ "a", "b", "c" ]` becomes the Go expression
|
||||||
|
// []string{ "a", "b", "c" }.
|
||||||
|
// If the value doesn't adhere to that format -- it's not a list, or it
|
||||||
|
// contains anything other than strings, the function appends an
|
||||||
|
// appropriate error.
|
||||||
|
// If promoteScalars is true, then values that are scalar strings are
|
||||||
|
// promoted to a single-entry string array. E.g., "foo" becomes the Go
|
||||||
|
// expression []string{ "foo" }.
|
||||||
|
func (ps *parseState) literalToStringArray(node ast.Node, promoteScalars bool) ([]string, bool) {
|
||||||
|
literal, ok := node.(*ast.LiteralType)
|
||||||
|
if ok {
|
||||||
|
if promoteScalars && literal.Token.Type == token.STRING {
|
||||||
|
return []string{literal.Token.Value().(string)}, true
|
||||||
|
}
|
||||||
|
ps.addError(node, "Expected list, got %s", typename(node))
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
list, ok := node.(*ast.ListType)
|
||||||
|
if !ok {
|
||||||
|
ps.addError(node, "Expected list, got %s", typename(node))
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([]string, 0, len(list.List))
|
||||||
|
for _, literal := range list.List {
|
||||||
|
str, ok := ps.literalToString(literal)
|
||||||
|
if ok {
|
||||||
|
ret = append(ret, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// literalToString converts a literal value from the AST into a string.
|
||||||
|
// If the value isn't a scalar or isn't a string, the function appends an
|
||||||
|
// appropriate error and returns "", false.
|
||||||
|
func (ps *parseState) literalToString(node ast.Node) (string, bool) {
|
||||||
|
val := ps.literalCast(node, token.STRING)
|
||||||
|
if val == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return val.(string), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// literalToInt converts a literal value from the AST into an int64.
|
||||||
|
// Supported number formats are: 123, 0x123, and 0123.
|
||||||
|
// Exponents (1e6) and floats (123.456) generate errors.
|
||||||
|
// If the value isn't a scalar or isn't a number, the function appends an
|
||||||
|
// appropriate error and returns 0, false.
|
||||||
|
func (ps *parseState) literalToInt(node ast.Node) (int64, bool) {
|
||||||
|
val := ps.literalCast(node, token.NUMBER)
|
||||||
|
if val == nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return val.(int64), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *parseState) literalCast(node ast.Node, t token.Type) interface{} {
|
||||||
|
literal, ok := node.(*ast.LiteralType)
|
||||||
|
if !ok {
|
||||||
|
ps.addError(node, "Expected %s, got %s", strings.ToLower(t.String()), typename(node))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if literal.Token.Type != t {
|
||||||
|
ps.addError(node, "Expected %s, got %s", strings.ToLower(t.String()), typename(node))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return literal.Token.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseRoot parses the root of the AST, filling in ps.Version, ps.Actions,
|
||||||
|
// and ps.Workflows.
|
||||||
|
func (ps *parseState) parseRoot(node ast.Node) {
|
||||||
|
objectList, ok := node.(*ast.ObjectList)
|
||||||
|
if !ok {
|
||||||
|
// It should be impossible for HCL to return anything other than an
|
||||||
|
// ObjectList as the root node. This error should never happen.
|
||||||
|
ps.addError(node, "Internal error: root node must be an ObjectList")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.Actions = make([]*model.Action, 0, len(objectList.Items))
|
||||||
|
ps.Workflows = make([]*model.Workflow, 0, len(objectList.Items))
|
||||||
|
identifiers := make(map[string]bool)
|
||||||
|
for idx, item := range objectList.Items {
|
||||||
|
if item.Assign.IsValid() {
|
||||||
|
ps.parseVersion(idx, item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ps.parseBlock(item, identifiers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBlock parses a single, top-level "action" or "workflow" block,
|
||||||
|
// appending it to ps.Actions or ps.Workflows as appropriate.
|
||||||
|
func (ps *parseState) parseBlock(item *ast.ObjectItem, identifiers map[string]bool) {
|
||||||
|
if len(item.Keys) != 2 {
|
||||||
|
ps.addError(item, "Invalid toplevel declaration")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := ps.identString(item.Keys[0].Token)
|
||||||
|
var id string
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "action":
|
||||||
|
action := ps.actionifyItem(item)
|
||||||
|
if action != nil {
|
||||||
|
id = action.Identifier
|
||||||
|
ps.Actions = append(ps.Actions, action)
|
||||||
|
}
|
||||||
|
case "workflow":
|
||||||
|
workflow := ps.workflowifyItem(item)
|
||||||
|
if workflow != nil {
|
||||||
|
id = workflow.Identifier
|
||||||
|
ps.Workflows = append(ps.Workflows, workflow)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ps.addError(item, "Invalid toplevel keyword, `%s'", cmd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if identifiers[id] {
|
||||||
|
ps.addError(item, "Identifier `%s' redefined", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
identifiers[id] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseVersion parses a top-level `version=N` statement, filling in
|
||||||
|
// ps.Version.
|
||||||
|
func (ps *parseState) parseVersion(idx int, item *ast.ObjectItem) {
|
||||||
|
if len(item.Keys) != 1 || ps.identString(item.Keys[0].Token) != "version" {
|
||||||
|
// not a valid `version` declaration
|
||||||
|
ps.addError(item.Val, "Toplevel declarations cannot be assignments")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if idx != 0 {
|
||||||
|
ps.addError(item.Val, "`version` must be the first declaration")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
version, ok := ps.literalToInt(item.Val)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if version < minVersion || version > maxVersion {
|
||||||
|
ps.addError(item.Val, "`version = %d` is not supported", version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ps.Version = int(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseIdentifier parses the double-quoted identifier (name) for a
|
||||||
|
// "workflow" or "action" block.
|
||||||
|
func (ps *parseState) parseIdentifier(key *ast.ObjectKey) string {
|
||||||
|
id := key.Token.Text
|
||||||
|
if len(id) < 3 || id[0] != '"' || id[len(id)-1] != '"' {
|
||||||
|
ps.addError(key, "Invalid format for identifier `%s'", id)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return id[1 : len(id)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseRequiredString parses a string value, setting its value into the
|
||||||
|
// out-parameter `value` and returning true if successful.
|
||||||
|
func (ps *parseState) parseRequiredString(value *string, val ast.Node, nodeType, name, id string) bool {
|
||||||
|
if *value != "" {
|
||||||
|
ps.addWarning(val, "`%s' redefined in %s `%s'", name, nodeType, id)
|
||||||
|
// continue, allowing the redefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
newVal, ok := ps.literalToString(val)
|
||||||
|
if !ok {
|
||||||
|
ps.addError(val, "Invalid format for `%s' in %s `%s', expected string", name, nodeType, id)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if newVal == "" {
|
||||||
|
ps.addError(val, "`%s' value in %s `%s' cannot be blank", name, nodeType, id)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
*value = newVal
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBlockPreamble parses the beginning of a "workflow" or "action"
|
||||||
|
// block.
|
||||||
|
func (ps *parseState) parseBlockPreamble(item *ast.ObjectItem, nodeType string) (string, *ast.ObjectType) {
|
||||||
|
id := ps.parseIdentifier(item.Keys[1])
|
||||||
|
if id == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
node := item.Val
|
||||||
|
obj, ok := node.(*ast.ObjectType)
|
||||||
|
if !ok {
|
||||||
|
ps.addError(node, "Each %s must have an { ... } block", nodeType)
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.checkAssignmentsOnly(obj.List, id)
|
||||||
|
|
||||||
|
return id, obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// actionifyItem converts an AST block to an Action object.
|
||||||
|
func (ps *parseState) actionifyItem(item *ast.ObjectItem) *model.Action {
|
||||||
|
id, obj := ps.parseBlockPreamble(item, "action")
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
action := &model.Action{
|
||||||
|
Identifier: id,
|
||||||
|
}
|
||||||
|
ps.posMap[action] = item
|
||||||
|
|
||||||
|
for _, item := range obj.List.Items {
|
||||||
|
ps.parseActionAttribute(ps.identString(item.Keys[0].Token), action, item.Val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return action
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseActionAttribute parses a single key-value pair from an "action"
|
||||||
|
// block. This function rejects any unknown keys and enforces formatting
|
||||||
|
// requirements on all values.
|
||||||
|
// It also has higher-than-normal cyclomatic complexity, so we ask the
|
||||||
|
// gocyclo linter to ignore it.
|
||||||
|
// nolint: gocyclo
|
||||||
|
func (ps *parseState) parseActionAttribute(name string, action *model.Action, val ast.Node) {
|
||||||
|
switch name {
|
||||||
|
case "uses":
|
||||||
|
ps.parseUses(action, val)
|
||||||
|
case "needs":
|
||||||
|
needs, ok := ps.literalToStringArray(val, true)
|
||||||
|
if ok {
|
||||||
|
action.Needs = needs
|
||||||
|
ps.posMap[&action.Needs] = val
|
||||||
|
}
|
||||||
|
case "runs":
|
||||||
|
ps.parseCommand(action, &action.Runs, name, val, false)
|
||||||
|
case "args":
|
||||||
|
ps.parseCommand(action, &action.Args, name, val, true)
|
||||||
|
case "env":
|
||||||
|
env := ps.literalToStringMap(val)
|
||||||
|
if env != nil {
|
||||||
|
action.Env = env
|
||||||
|
}
|
||||||
|
ps.posMap[&action.Env] = val
|
||||||
|
case "secrets":
|
||||||
|
secrets, ok := ps.literalToStringArray(val, false)
|
||||||
|
if ok {
|
||||||
|
action.Secrets = secrets
|
||||||
|
ps.posMap[&action.Secrets] = val
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ps.addWarning(val, "Unknown action attribute `%s'", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseUses sets the action.Uses value based on the contents of the AST
|
||||||
|
// node. This function enforces formatting requirements on the value.
|
||||||
|
func (ps *parseState) parseUses(action *model.Action, node ast.Node) {
|
||||||
|
if action.Uses != nil {
|
||||||
|
ps.addWarning(node, "`uses' redefined in action `%s'", action.Identifier)
|
||||||
|
// continue, allowing the redefinition
|
||||||
|
}
|
||||||
|
strVal, ok := ps.literalToString(node)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strVal == "" {
|
||||||
|
action.Uses = &model.UsesInvalid{}
|
||||||
|
ps.addError(node, "`uses' value in action `%s' cannot be blank", action.Identifier)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(strVal, "./") {
|
||||||
|
action.Uses = &model.UsesPath{Path: strings.TrimPrefix(strVal, "./")}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(strVal, "docker://") {
|
||||||
|
action.Uses = &model.UsesDockerImage{Image: strings.TrimPrefix(strVal, "docker://")}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tok := strings.Split(strVal, "@")
|
||||||
|
if len(tok) != 2 {
|
||||||
|
action.Uses = &model.UsesInvalid{Raw: strVal}
|
||||||
|
ps.addError(node, "The `uses' attribute must be a path, a Docker image, or owner/repo@ref")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ref := tok[1]
|
||||||
|
tok = strings.SplitN(tok[0], "/", 3)
|
||||||
|
if len(tok) < 2 {
|
||||||
|
action.Uses = &model.UsesInvalid{Raw: strVal}
|
||||||
|
ps.addError(node, "The `uses' attribute must be a path, a Docker image, or owner/repo@ref")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
usesRepo := &model.UsesRepository{Repository: tok[0] + "/" + tok[1], Ref: ref}
|
||||||
|
action.Uses = usesRepo
|
||||||
|
if len(tok) == 3 {
|
||||||
|
usesRepo.Path = tok[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseUses sets the action.Runs or action.Command value based on the
|
||||||
|
// contents of the AST node. This function enforces formatting
|
||||||
|
// requirements on the value.
|
||||||
|
func (ps *parseState) parseCommand(action *model.Action, dest *model.ActionCommand, name string, node ast.Node, allowBlank bool) {
|
||||||
|
if len(dest.Parsed) > 0 {
|
||||||
|
ps.addWarning(node, "`%s' redefined in action `%s'", name, action.Identifier)
|
||||||
|
// continue, allowing the redefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it a list?
|
||||||
|
if _, ok := node.(*ast.ListType); ok {
|
||||||
|
if parsed, ok := ps.literalToStringArray(node, false); ok {
|
||||||
|
dest.Parsed = parsed
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not, parse a whitespace-separated string into a list.
|
||||||
|
var raw string
|
||||||
|
var ok bool
|
||||||
|
if raw, ok = ps.literalToString(node); !ok {
|
||||||
|
ps.addError(node, "The `%s' attribute must be a string or a list", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if raw == "" && !allowBlank {
|
||||||
|
ps.addError(node, "`%s' value in action `%s' cannot be blank", name, action.Identifier)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dest.Raw = raw
|
||||||
|
dest.Parsed = strings.Fields(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func typename(val interface{}) string {
|
||||||
|
switch cast := val.(type) {
|
||||||
|
case *ast.ListType:
|
||||||
|
return "list"
|
||||||
|
case *ast.LiteralType:
|
||||||
|
return strings.ToLower(cast.Token.Type.String())
|
||||||
|
case *ast.ObjectType:
|
||||||
|
return "object"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%T", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// workflowifyItem converts an AST block to a Workflow object.
|
||||||
|
func (ps *parseState) workflowifyItem(item *ast.ObjectItem) *model.Workflow {
|
||||||
|
id, obj := ps.parseBlockPreamble(item, "workflow")
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
workflow := &model.Workflow{Identifier: id}
|
||||||
|
for _, item := range obj.List.Items {
|
||||||
|
name := ps.identString(item.Keys[0].Token)
|
||||||
|
|
||||||
|
switch name {
|
||||||
|
case "on":
|
||||||
|
ok = ps.parseRequiredString(&workflow.On, item.Val, "workflow", name, id)
|
||||||
|
if ok {
|
||||||
|
ps.posMap[&workflow.On] = item
|
||||||
|
}
|
||||||
|
case "resolves":
|
||||||
|
if workflow.Resolves != nil {
|
||||||
|
ps.addWarning(item.Val, "`resolves' redefined in workflow `%s'", id)
|
||||||
|
// continue, allowing the redefinition
|
||||||
|
}
|
||||||
|
workflow.Resolves, ok = ps.literalToStringArray(item.Val, true)
|
||||||
|
ps.posMap[&workflow.Resolves] = item
|
||||||
|
if !ok {
|
||||||
|
ps.addError(item.Val, "Invalid format for `resolves' in workflow `%s', expected list of strings", id)
|
||||||
|
// continue, allowing workflow with no `resolves`
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ps.addWarning(item.Val, "Unknown workflow attribute `%s'", name)
|
||||||
|
// continue, treat as no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.posMap[workflow] = item
|
||||||
|
return workflow
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAssignment(item *ast.ObjectItem) bool {
|
||||||
|
return len(item.Keys) == 1 && item.Assign.IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkAssignmentsOnly ensures that all elements in the object are "key =
|
||||||
|
// value" pairs.
|
||||||
|
func (ps *parseState) checkAssignmentsOnly(objectList *ast.ObjectList, actionID string) {
|
||||||
|
for _, item := range objectList.Items {
|
||||||
|
if !isAssignment(item) {
|
||||||
|
var desc string
|
||||||
|
if actionID == "" {
|
||||||
|
desc = "the object"
|
||||||
|
} else {
|
||||||
|
desc = fmt.Sprintf("action `%s'", actionID)
|
||||||
|
}
|
||||||
|
ps.addErrorFromObjectItem(item, "Each attribute of %s must be an assignment", desc)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
child, ok := item.Val.(*ast.ObjectType)
|
||||||
|
if ok {
|
||||||
|
ps.checkAssignmentsOnly(child.List, actionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *parseState) addWarning(node ast.Node, format string, a ...interface{}) {
|
||||||
|
if ps.suppressSeverity < WARNING {
|
||||||
|
ps.Errors = append(ps.Errors, newWarning(posFromNode(node), format, a...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *parseState) addError(node ast.Node, format string, a ...interface{}) {
|
||||||
|
if ps.suppressSeverity < ERROR {
|
||||||
|
ps.Errors = append(ps.Errors, newError(posFromNode(node), format, a...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *parseState) addErrorFromToken(t token.Token, format string, a ...interface{}) {
|
||||||
|
if ps.suppressSeverity < ERROR {
|
||||||
|
ps.Errors = append(ps.Errors, newError(posFromToken(t), format, a...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *parseState) addErrorFromObjectItem(objectItem *ast.ObjectItem, format string, a ...interface{}) {
|
||||||
|
if ps.suppressSeverity < ERROR {
|
||||||
|
ps.Errors = append(ps.Errors, newError(posFromObjectItem(objectItem), format, a...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *parseState) addFatal(node ast.Node, format string, a ...interface{}) {
|
||||||
|
if ps.suppressSeverity < FATAL {
|
||||||
|
ps.Errors = append(ps.Errors, newFatal(posFromNode(node), format, a...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// posFromNode returns an ErrorPos (file, line, and column) from an AST
|
||||||
|
// node, so we can report specific locations for each parse error.
|
||||||
|
func posFromNode(node ast.Node) ErrorPos {
|
||||||
|
var pos *token.Pos
|
||||||
|
switch cast := node.(type) {
|
||||||
|
case *ast.ObjectList:
|
||||||
|
if len(cast.Items) > 0 {
|
||||||
|
if len(cast.Items[0].Keys) > 0 {
|
||||||
|
pos = &cast.Items[0].Keys[0].Token.Pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ast.ObjectItem:
|
||||||
|
return posFromNode(cast.Val)
|
||||||
|
case *ast.ObjectType:
|
||||||
|
pos = &cast.Lbrace
|
||||||
|
case *ast.LiteralType:
|
||||||
|
pos = &cast.Token.Pos
|
||||||
|
case *ast.ListType:
|
||||||
|
pos = &cast.Lbrack
|
||||||
|
case *ast.ObjectKey:
|
||||||
|
pos = &cast.Token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
if pos == nil {
|
||||||
|
return ErrorPos{}
|
||||||
|
}
|
||||||
|
return ErrorPos{File: pos.Filename, Line: pos.Line, Column: pos.Column}
|
||||||
|
}
|
||||||
|
|
||||||
|
// posFromObjectItem returns an ErrorPos from an ObjectItem. This is for
|
||||||
|
// cases where posFromNode(item) would fail because the item has no Val
|
||||||
|
// set.
|
||||||
|
func posFromObjectItem(item *ast.ObjectItem) ErrorPos {
|
||||||
|
if len(item.Keys) > 0 {
|
||||||
|
return posFromNode(item.Keys[0])
|
||||||
|
}
|
||||||
|
return ErrorPos{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// posFromToken returns an ErrorPos from a Token. We can't use
|
||||||
|
// posFromNode here because Tokens aren't Nodes.
|
||||||
|
func posFromToken(token token.Token) ErrorPos {
|
||||||
|
return ErrorPos{File: token.Pos.Filename, Line: token.Pos.Line, Column: token.Pos.Column}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
module "github.com/soniakeys/bits"
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
*.dot
|
||||||
|
anecdote/anecdote
|
|
@ -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
|
|
@ -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.
|
|
@ -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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module "github.com/soniakeys/graph"
|
||||||
|
|
||||||
|
require "github.com/soniakeys/bits" v1.0.0
|
|
@ -0,0 +1,767 @@
|
||||||
|
// Copyright 2014 Sonia Keys
|
||||||
|
// License MIT: http://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/soniakeys/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// graph.go contains type definitions for all graph types and components.
|
||||||
|
// Also, go generate directives for source transformations.
|
||||||
|
//
|
||||||
|
// For readability, the types are defined in a dependency order:
|
||||||
|
//
|
||||||
|
// NI
|
||||||
|
// AdjacencyList
|
||||||
|
// Directed
|
||||||
|
// Undirected
|
||||||
|
// Bipartite
|
||||||
|
// Subgraph
|
||||||
|
// DirectedSubgraph
|
||||||
|
// UndirectedSubgraph
|
||||||
|
// LI
|
||||||
|
// Half
|
||||||
|
// fromHalf
|
||||||
|
// LabeledAdjacencyList
|
||||||
|
// LabeledDirected
|
||||||
|
// LabeledUndirected
|
||||||
|
// LabeledBipartite
|
||||||
|
// LabeledSubgraph
|
||||||
|
// LabeledDirectedSubgraph
|
||||||
|
// LabeledUndirectedSubgraph
|
||||||
|
// Edge
|
||||||
|
// LabeledEdge
|
||||||
|
// LabeledPath
|
||||||
|
// WeightFunc
|
||||||
|
// WeightedEdgeList
|
||||||
|
// TraverseOption
|
||||||
|
|
||||||
|
//go:generate cp adj_cg.go adj_RO.go
|
||||||
|
//go:generate gofmt -r "LabeledAdjacencyList -> AdjacencyList" -w adj_RO.go
|
||||||
|
//go:generate gofmt -r "n.To -> n" -w adj_RO.go
|
||||||
|
//go:generate gofmt -r "Half -> NI" -w adj_RO.go
|
||||||
|
//go:generate gofmt -r "LabeledSubgraph -> Subgraph" -w adj_RO.go
|
||||||
|
|
||||||
|
//go:generate cp dir_cg.go dir_RO.go
|
||||||
|
//go:generate gofmt -r "LabeledDirected -> Directed" -w dir_RO.go
|
||||||
|
//go:generate gofmt -r "LabeledDirectedSubgraph -> DirectedSubgraph" -w dir_RO.go
|
||||||
|
//go:generate gofmt -r "LabeledAdjacencyList -> AdjacencyList" -w dir_RO.go
|
||||||
|
//go:generate gofmt -r "labEulerian -> eulerian" -w dir_RO.go
|
||||||
|
//go:generate gofmt -r "newLabEulerian -> newEulerian" -w dir_RO.go
|
||||||
|
//go:generate gofmt -r "Half{n, -1} -> n" -w dir_RO.go
|
||||||
|
//go:generate gofmt -r "n.To -> n" -w dir_RO.go
|
||||||
|
//go:generate gofmt -r "Half -> NI" -w dir_RO.go
|
||||||
|
|
||||||
|
//go:generate cp undir_cg.go undir_RO.go
|
||||||
|
//go:generate gofmt -r "LabeledUndirected -> Undirected" -w undir_RO.go
|
||||||
|
//go:generate gofmt -r "LabeledBipartite -> Bipartite" -w undir_RO.go
|
||||||
|
//go:generate gofmt -r "LabeledUndirectedSubgraph -> UndirectedSubgraph" -w undir_RO.go
|
||||||
|
//go:generate gofmt -r "LabeledAdjacencyList -> AdjacencyList" -w undir_RO.go
|
||||||
|
//go:generate gofmt -r "newLabEulerian -> newEulerian" -w undir_RO.go
|
||||||
|
//go:generate gofmt -r "Half{n, -1} -> n" -w undir_RO.go
|
||||||
|
//go:generate gofmt -r "n.To -> n" -w undir_RO.go
|
||||||
|
//go:generate gofmt -r "Half -> NI" -w undir_RO.go
|
||||||
|
|
||||||
|
// An AdjacencyList represents a graph as a list of neighbors for each node.
|
||||||
|
// The "node ID" of a node is simply it's slice index in the AdjacencyList.
|
||||||
|
// For an AdjacencyList g, g[n] represents arcs going from node n to nodes
|
||||||
|
// g[n].
|
||||||
|
//
|
||||||
|
// Adjacency lists are inherently directed but can be used to represent
|
||||||
|
// directed or undirected graphs. See types Directed and Undirected.
|
||||||
|
type AdjacencyList [][]NI
|
||||||
|
|
||||||
|
// Directed represents a directed graph.
|
||||||
|
//
|
||||||
|
// Directed methods generally rely on the graph being directed, specifically
|
||||||
|
// that arcs do not have reciprocals.
|
||||||
|
type Directed struct {
|
||||||
|
AdjacencyList // embedded to include AdjacencyList methods
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undirected represents an undirected graph.
|
||||||
|
//
|
||||||
|
// In an undirected graph, for each arc between distinct nodes there is also
|
||||||
|
// a reciprocal arc, an arc in the opposite direction. Loops do not have
|
||||||
|
// reciprocals.
|
||||||
|
//
|
||||||
|
// Undirected methods generally rely on the graph being undirected,
|
||||||
|
// specifically that every arc between distinct nodes has a reciprocal.
|
||||||
|
type Undirected struct {
|
||||||
|
AdjacencyList // embedded to include AdjacencyList methods
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bipartite represents a bipartite graph.
|
||||||
|
//
|
||||||
|
// In a bipartite graph, nodes are partitioned into two sets, or
|
||||||
|
// "colors," such that every edge in the graph goes from one set to the
|
||||||
|
// other.
|
||||||
|
//
|
||||||
|
// Member Color represents the partition with a bitmap of length the same
|
||||||
|
// as the number of nodes in the graph. For convenience N0 stores the number
|
||||||
|
// of zero bits in Color.
|
||||||
|
//
|
||||||
|
// To construct a Bipartite object, if you can easily or efficiently use
|
||||||
|
// available information to construct the Color member, then you should do
|
||||||
|
// this and construct a Bipartite object with a Go struct literal.
|
||||||
|
//
|
||||||
|
// If partition information is not readily available, see the constructor
|
||||||
|
// Undirected.Bipartite.
|
||||||
|
//
|
||||||
|
// Alternatively, in some cases where the graph may have multiple connected
|
||||||
|
// components, the lower level Undirected.BipartiteComponent can be used to
|
||||||
|
// control color assignment by component.
|
||||||
|
type Bipartite struct {
|
||||||
|
Undirected
|
||||||
|
Color bits.Bits
|
||||||
|
N0 int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subgraph represents a subgraph mapped to a supergraph.
|
||||||
|
//
|
||||||
|
// The subgraph is the embedded AdjacencyList and so the Subgraph type inherits
|
||||||
|
// all methods of Adjacency list.
|
||||||
|
//
|
||||||
|
// The embedded subgraph mapped relative to a specific supergraph, member
|
||||||
|
// Super. A subgraph may have fewer nodes than its supergraph.
|
||||||
|
// Each node of the subgraph must map to a distinct node of the supergraph.
|
||||||
|
//
|
||||||
|
// The mapping giving the supergraph node for a given subgraph node is
|
||||||
|
// represented by member SuperNI, a slice parallel to the the subgraph.
|
||||||
|
//
|
||||||
|
// The mapping in the other direction, giving a subgraph NI for a given
|
||||||
|
// supergraph NI, is represented with map SubNI.
|
||||||
|
//
|
||||||
|
// Multiple Subgraphs can be created relative to a single supergraph.
|
||||||
|
// The Subgraph type represents a mapping to only a single supergraph however.
|
||||||
|
//
|
||||||
|
// See graph methods InduceList and InduceBits for construction of
|
||||||
|
// node-induced subgraphs.
|
||||||
|
//
|
||||||
|
// Alternatively an empty subgraph can be constructed with InduceList(nil).
|
||||||
|
// Arbitrary subgraphs can then be built up with methods AddNode and AddArc.
|
||||||
|
type Subgraph struct {
|
||||||
|
AdjacencyList // the subgraph
|
||||||
|
Super *AdjacencyList // the supergraph
|
||||||
|
SubNI map[NI]NI // subgraph NIs, indexed by supergraph NIs
|
||||||
|
SuperNI []NI // supergraph NIs indexed by subgraph NIs
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirectedSubgraph represents a subgraph mapped to a supergraph.
|
||||||
|
//
|
||||||
|
// See additional doc at Subgraph type.
|
||||||
|
type DirectedSubgraph struct {
|
||||||
|
Directed
|
||||||
|
Super *Directed
|
||||||
|
SubNI map[NI]NI
|
||||||
|
SuperNI []NI
|
||||||
|
}
|
||||||
|
|
||||||
|
// UndirectedSubgraph represents a subgraph mapped to a supergraph.
|
||||||
|
//
|
||||||
|
// See additional doc at Subgraph type.
|
||||||
|
type UndirectedSubgraph struct {
|
||||||
|
Undirected
|
||||||
|
Super *Undirected
|
||||||
|
SubNI map[NI]NI
|
||||||
|
SuperNI []NI
|
||||||
|
}
|
||||||
|
|
||||||
|
// LI is a label integer, used for associating labels with arcs.
|
||||||
|
type LI int32
|
||||||
|
|
||||||
|
// Half is a half arc, representing a labeled arc and the "neighbor" node
|
||||||
|
// that the arc leads to.
|
||||||
|
//
|
||||||
|
// Halfs can be composed to form a labeled adjacency list.
|
||||||
|
type Half struct {
|
||||||
|
To NI // node ID, usable as a slice index
|
||||||
|
Label LI // half-arc ID for application data, often a weight
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromHalf is a half arc, representing a labeled arc and the "neighbor" node
|
||||||
|
// that the arc originates from.
|
||||||
|
//
|
||||||
|
// This used internally in a couple of places. It used to be exported but is
|
||||||
|
// not currently needed anwhere in the API.
|
||||||
|
type fromHalf struct {
|
||||||
|
From NI
|
||||||
|
Label LI
|
||||||
|
}
|
||||||
|
|
||||||
|
// A LabeledAdjacencyList represents a graph as a list of neighbors for each
|
||||||
|
// node, connected by labeled arcs.
|
||||||
|
//
|
||||||
|
// Arc labels are not necessarily unique arc IDs. Different arcs can have
|
||||||
|
// the same label.
|
||||||
|
//
|
||||||
|
// Arc labels are commonly used to assocate a weight with an arc. Arc labels
|
||||||
|
// are general purpose however and can be used to associate arbitrary
|
||||||
|
// information with an arc.
|
||||||
|
//
|
||||||
|
// Methods implementing weighted graph algorithms will commonly take a
|
||||||
|
// weight function that turns a label int into a float64 weight.
|
||||||
|
//
|
||||||
|
// If only a small amount of information -- such as an integer weight or
|
||||||
|
// a single printable character -- needs to be associated, it can sometimes
|
||||||
|
// be possible to encode the information directly into the label int. For
|
||||||
|
// more generality, some lookup scheme will be needed.
|
||||||
|
//
|
||||||
|
// In an undirected labeled graph, reciprocal arcs must have identical labels.
|
||||||
|
// Note this does not preclude parallel arcs with different labels.
|
||||||
|
type LabeledAdjacencyList [][]Half
|
||||||
|
|
||||||
|
// LabeledDirected represents a directed labeled graph.
|
||||||
|
//
|
||||||
|
// This is the labeled version of Directed. See types LabeledAdjacencyList
|
||||||
|
// and Directed.
|
||||||
|
type LabeledDirected struct {
|
||||||
|
LabeledAdjacencyList // embedded to include LabeledAdjacencyList methods
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabeledUndirected represents an undirected labeled graph.
|
||||||
|
//
|
||||||
|
// This is the labeled version of Undirected. See types LabeledAdjacencyList
|
||||||
|
// and Undirected.
|
||||||
|
type LabeledUndirected struct {
|
||||||
|
LabeledAdjacencyList // embedded to include LabeledAdjacencyList methods
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabeledBipartite represents a bipartite graph.
|
||||||
|
//
|
||||||
|
// In a bipartite graph, nodes are partitioned into two sets, or
|
||||||
|
// "colors," such that every edge in the graph goes from one set to the
|
||||||
|
// other.
|
||||||
|
//
|
||||||
|
// Member Color represents the partition with a bitmap of length the same
|
||||||
|
// as the number of nodes in the graph. For convenience N0 stores the number
|
||||||
|
// of zero bits in Color.
|
||||||
|
//
|
||||||
|
// To construct a LabeledBipartite object, if you can easily or efficiently use
|
||||||
|
// available information to construct the Color member, then you should do
|
||||||
|
// this and construct a LabeledBipartite object with a Go struct literal.
|
||||||
|
//
|
||||||
|
// If partition information is not readily available, see the constructor
|
||||||
|
// Undirected.LabeledBipartite.
|
||||||
|
//
|
||||||
|
// Alternatively, in some cases where the graph may have multiple connected
|
||||||
|
// components, the lower level LabeledUndirected.BipartiteComponent can be used
|
||||||
|
// to control color assignment by component.
|
||||||
|
type LabeledBipartite struct {
|
||||||
|
LabeledUndirected
|
||||||
|
Color bits.Bits
|
||||||
|
N0 int
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabeledSubgraph represents a subgraph mapped to a supergraph.
|
||||||
|
//
|
||||||
|
// See additional doc at Subgraph type.
|
||||||
|
type LabeledSubgraph struct {
|
||||||
|
LabeledAdjacencyList
|
||||||
|
Super *LabeledAdjacencyList
|
||||||
|
SubNI map[NI]NI
|
||||||
|
SuperNI []NI
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabeledDirectedSubgraph represents a subgraph mapped to a supergraph.
|
||||||
|
//
|
||||||
|
// See additional doc at Subgraph type.
|
||||||
|
type LabeledDirectedSubgraph struct {
|
||||||
|
LabeledDirected
|
||||||
|
Super *LabeledDirected
|
||||||
|
SubNI map[NI]NI
|
||||||
|
SuperNI []NI
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabeledUndirectedSubgraph represents a subgraph mapped to a supergraph.
|
||||||
|
//
|
||||||
|
// See additional doc at Subgraph type.
|
||||||
|
type LabeledUndirectedSubgraph struct {
|
||||||
|
LabeledUndirected
|
||||||
|
Super *LabeledUndirected
|
||||||
|
SubNI map[NI]NI
|
||||||
|
SuperNI []NI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge is an undirected edge between nodes N1 and N2.
|
||||||
|
type Edge struct{ N1, N2 NI }
|
||||||
|
|
||||||
|
// LabeledEdge is an undirected edge with an associated label.
|
||||||
|
type LabeledEdge struct {
|
||||||
|
Edge
|
||||||
|
LI
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabeledPath is a start node and a path of half arcs leading from start.
|
||||||
|
type LabeledPath struct {
|
||||||
|
Start NI
|
||||||
|
Path []Half
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance returns total path distance given WeightFunc w.
|
||||||
|
func (p LabeledPath) Distance(w WeightFunc) float64 {
|
||||||
|
d := 0.
|
||||||
|
for _, h := range p.Path {
|
||||||
|
d += w(h.Label)
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// WeightFunc returns a weight for a given label.
|
||||||
|
//
|
||||||
|
// WeightFunc is a parameter type for various search functions. The intent
|
||||||
|
// is to return a weight corresponding to an arc label. The name "weight"
|
||||||
|
// is an abstract term. An arc "weight" will typically have some application
|
||||||
|
// specific meaning other than physical weight.
|
||||||
|
type WeightFunc func(label LI) (weight float64)
|
||||||
|
|
||||||
|
// WeightedEdgeList is a graph representation.
|
||||||
|
//
|
||||||
|
// It is a labeled edge list, with an associated weight function to return
|
||||||
|
// a weight given an edge label.
|
||||||
|
//
|
||||||
|
// Also associated is the order, or number of nodes of the graph.
|
||||||
|
// All nodes occurring in the edge list must be strictly less than Order.
|
||||||
|
//
|
||||||
|
// WeigtedEdgeList sorts by weight, obtained by calling the weight function.
|
||||||
|
// If weight computation is expensive, consider supplying a cached or
|
||||||
|
// memoized version.
|
||||||
|
type WeightedEdgeList struct {
|
||||||
|
Order int
|
||||||
|
WeightFunc
|
||||||
|
Edges []LabeledEdge
|
||||||
|
}
|
||||||
|
|
||||||
|
// DistanceMatrix constructs a distance matrix corresponding to the weighted
|
||||||
|
// edges of l.
|
||||||
|
//
|
||||||
|
// An edge n1, n2 with WeightFunc return w is represented by both
|
||||||
|
// d[n1][n2] == w and d[n2][n1] = w. In case of parallel edges, 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 edge between n1 and
|
||||||
|
// distinct n2, +Inf is stored for d[n1][n2] and d[n2][n1].
|
||||||
|
//
|
||||||
|
// The returned DistanceMatrix is suitable for DistanceMatrix.FloydWarshall.
|
||||||
|
func (l WeightedEdgeList) DistanceMatrix() (d DistanceMatrix) {
|
||||||
|
d = newDM(l.Order)
|
||||||
|
for _, e := range l.Edges {
|
||||||
|
n1 := e.Edge.N1
|
||||||
|
n2 := e.Edge.N2
|
||||||
|
wt := l.WeightFunc(e.LI)
|
||||||
|
// < to pick min of parallel arcs (also nicely ignores NaN)
|
||||||
|
if wt < d[n1][n2] {
|
||||||
|
d[n1][n2] = wt
|
||||||
|
d[n2][n1] = wt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// A DistanceMatrix is a square matrix representing some distance between
|
||||||
|
// nodes of a graph. If the graph is directected, d[from][to] represents
|
||||||
|
// some distance from node 'from' to node 'to'. Depending on context, the
|
||||||
|
// distance may be an arc weight or path distance. A value of +Inf typically
|
||||||
|
// means no arc or no path between the nodes.
|
||||||
|
type DistanceMatrix [][]float64
|
||||||
|
|
||||||
|
// little helper function, makes a blank distance matrix for FloydWarshall.
|
||||||
|
// could be exported?
|
||||||
|
func newDM(n int) DistanceMatrix {
|
||||||
|
inf := math.Inf(1)
|
||||||
|
d := make(DistanceMatrix, n)
|
||||||
|
for i := range d {
|
||||||
|
di := make([]float64, n)
|
||||||
|
for j := range di {
|
||||||
|
di[j] = inf
|
||||||
|
}
|
||||||
|
di[i] = 0
|
||||||
|
d[i] = di
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// FloydWarshall finds all pairs shortest distances for a weighted graph
|
||||||
|
// without negative cycles.
|
||||||
|
//
|
||||||
|
// It operates on a distance matrix representing arcs of a graph and
|
||||||
|
// destructively replaces arc weights with shortest path distances.
|
||||||
|
//
|
||||||
|
// In receiver d, d[fr][to] will be the shortest distance from node
|
||||||
|
// 'fr' to node 'to'. An element value of +Inf means no path exists.
|
||||||
|
// Any diagonal element < 0 indicates a negative cycle exists.
|
||||||
|
//
|
||||||
|
// See DistanceMatrix constructor methods of LabeledAdjacencyList and
|
||||||
|
// WeightedEdgeList for suitable inputs.
|
||||||
|
func (d DistanceMatrix) FloydWarshall() {
|
||||||
|
for k, dk := range d {
|
||||||
|
for _, di := range d {
|
||||||
|
dik := di[k]
|
||||||
|
for j := range d {
|
||||||
|
if d2 := dik + dk[j]; d2 < di[j] {
|
||||||
|
di[j] = d2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathMatrix is a return type for FloydWarshallPaths.
|
||||||
|
//
|
||||||
|
// It encodes all pairs shortest paths.
|
||||||
|
type PathMatrix [][]NI
|
||||||
|
|
||||||
|
// Path returns a shortest path from node start to end.
|
||||||
|
//
|
||||||
|
// Argument p is truncated, appended to, and returned as the result.
|
||||||
|
// Thus the underlying allocation is reused if possible.
|
||||||
|
// If there is no path from start to end, p is returned truncated to
|
||||||
|
// zero length.
|
||||||
|
//
|
||||||
|
// If receiver m is not a valid populated PathMatrix as returned by
|
||||||
|
// FloydWarshallPaths, behavior is undefined and a panic is likely.
|
||||||
|
func (m PathMatrix) Path(start, end NI, p []NI) []NI {
|
||||||
|
p = p[:0]
|
||||||
|
for {
|
||||||
|
p = append(p, start)
|
||||||
|
if start == end {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
start = m[start][end]
|
||||||
|
if start < 0 {
|
||||||
|
return p[:0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FloydWarshallPaths finds all pairs shortest paths for a weighted graph
|
||||||
|
// without negative cycles.
|
||||||
|
//
|
||||||
|
// It operates on a distance matrix representing arcs of a graph and
|
||||||
|
// destructively replaces arc weights with shortest path distances.
|
||||||
|
//
|
||||||
|
// In receiver d, d[fr][to] will be the shortest distance from node
|
||||||
|
// 'fr' to node 'to'. An element value of +Inf means no path exists.
|
||||||
|
// Any diagonal element < 0 indicates a negative cycle exists.
|
||||||
|
//
|
||||||
|
// The return value encodes the paths. See PathMatrix.Path.
|
||||||
|
//
|
||||||
|
// See DistanceMatrix constructor methods of LabeledAdjacencyList and
|
||||||
|
// WeightedEdgeList for suitable inputs.
|
||||||
|
//
|
||||||
|
// See also similar method FloydWarshallFromLists which has a richer
|
||||||
|
// return value.
|
||||||
|
func (d DistanceMatrix) FloydWarshallPaths() PathMatrix {
|
||||||
|
m := make(PathMatrix, len(d))
|
||||||
|
inf := math.Inf(1)
|
||||||
|
for i, di := range d {
|
||||||
|
mi := make([]NI, len(d))
|
||||||
|
for j, dij := range di {
|
||||||
|
if dij == inf {
|
||||||
|
mi[j] = -1
|
||||||
|
} else {
|
||||||
|
mi[j] = NI(j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m[i] = mi
|
||||||
|
}
|
||||||
|
for k, dk := range d {
|
||||||
|
for i, di := range d {
|
||||||
|
mi := m[i]
|
||||||
|
dik := di[k]
|
||||||
|
for j := range d {
|
||||||
|
if d2 := dik + dk[j]; d2 < di[j] {
|
||||||
|
di[j] = d2
|
||||||
|
mi[j] = mi[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// FloydWarshallFromLists finds all pairs shortest paths for a weighted
|
||||||
|
// graph without negative cycles.
|
||||||
|
//
|
||||||
|
// It operates on a distance matrix representing arcs of a graph and
|
||||||
|
// destructively replaces arc weights with shortest path distances.
|
||||||
|
//
|
||||||
|
// In receiver d, d[fr][to] will be the shortest distance from node
|
||||||
|
// 'fr' to node 'to'. An element value of +Inf means no path exists.
|
||||||
|
// Any diagonal element < 0 indicates a negative cycle exists.
|
||||||
|
//
|
||||||
|
// The return value encodes the paths. The FromLists are fully populated
|
||||||
|
// with Leaves and Len values. See for example FromList.PathTo for
|
||||||
|
// extracting paths. Note though that for i'th FromList of the return
|
||||||
|
// value, PathTo(j) will return the path from j's root, which will not
|
||||||
|
// be i in the case that there is no path from i to j. You must check
|
||||||
|
// the first node of the path to see if it is i. If not, there is no
|
||||||
|
// path from i to j. See example.
|
||||||
|
//
|
||||||
|
// See DistanceMatrix constructor methods of LabeledAdjacencyList and
|
||||||
|
// WeightedEdgeList for suitable inputs.
|
||||||
|
//
|
||||||
|
// See also similar method FloydWarshallPaths, which has a lighter
|
||||||
|
// weight return value.
|
||||||
|
func (d DistanceMatrix) FloydWarshallFromLists() []FromList {
|
||||||
|
l := make([]FromList, len(d))
|
||||||
|
inf := math.Inf(1)
|
||||||
|
for i, di := range d {
|
||||||
|
li := NewFromList(len(d))
|
||||||
|
p := li.Paths
|
||||||
|
for j, dij := range di {
|
||||||
|
if i == j || dij == inf {
|
||||||
|
p[j] = PathEnd{From: -1}
|
||||||
|
} else {
|
||||||
|
p[j] = PathEnd{From: NI(i)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l[i] = li
|
||||||
|
}
|
||||||
|
for k, dk := range d {
|
||||||
|
pk := l[k].Paths
|
||||||
|
for i, di := range d {
|
||||||
|
dik := di[k]
|
||||||
|
pi := l[i].Paths
|
||||||
|
for j := range d {
|
||||||
|
if d2 := dik + dk[j]; d2 < di[j] {
|
||||||
|
di[j] = d2
|
||||||
|
pi[j] = pk[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, li := range l {
|
||||||
|
li.RecalcLeaves()
|
||||||
|
li.RecalcLen()
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddEdge adds an edge to a subgraph.
|
||||||
|
//
|
||||||
|
// For argument e, e.N1 and e.N2 must be NIs in supergraph s.Super. As with
|
||||||
|
// AddNode, AddEdge panics if e.N1 and e.N2 are not valid node indexes of
|
||||||
|
// s.Super.
|
||||||
|
//
|
||||||
|
// Edge e must exist in s.Super. Further, the number of
|
||||||
|
// parallel edges in the subgraph cannot exceed the number of corresponding
|
||||||
|
// parallel edges in the supergraph. That is, each edge already added to the
|
||||||
|
// subgraph counts against the edges available in the supergraph. If a matching
|
||||||
|
// edge is not available, AddEdge returns an error.
|
||||||
|
//
|
||||||
|
// If a matching edge is available, subgraph nodes are added as needed, the
|
||||||
|
// subgraph edge is added, and the method returns nil.
|
||||||
|
func (s *UndirectedSubgraph) AddEdge(n1, n2 NI) error {
|
||||||
|
// verify supergraph NIs first, but without adding subgraph nodes just yet.
|
||||||
|
if int(n1) < 0 || int(n1) >= s.Super.Order() {
|
||||||
|
panic(fmt.Sprint("AddEdge: NI ", n1, " not in supergraph"))
|
||||||
|
}
|
||||||
|
if int(n2) < 0 || int(n2) >= s.Super.Order() {
|
||||||
|
panic(fmt.Sprint("AddEdge: NI ", n2, " not in supergraph"))
|
||||||
|
}
|
||||||
|
// count existing matching edges in subgraph
|
||||||
|
n := 0
|
||||||
|
a := s.Undirected.AdjacencyList
|
||||||
|
if b1, ok := s.SubNI[n1]; ok {
|
||||||
|
if b2, ok := s.SubNI[n2]; ok {
|
||||||
|
// both NIs already exist in subgraph, need to count edges
|
||||||
|
for _, t := range a[b1] {
|
||||||
|
if t == b2 {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b1 != b2 {
|
||||||
|
// verify reciprocal arcs exist
|
||||||
|
r := 0
|
||||||
|
for _, t := range a[b2] {
|
||||||
|
if t == b1 {
|
||||||
|
r++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r < n {
|
||||||
|
n = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// verify matching edges are available in supergraph
|
||||||
|
m := 0
|
||||||
|
for _, t := range (*s.Super).AdjacencyList[n1] {
|
||||||
|
if t == n2 {
|
||||||
|
if m == n {
|
||||||
|
goto r // arc match after all existing arcs matched
|
||||||
|
}
|
||||||
|
m++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("edge not available in supergraph")
|
||||||
|
r:
|
||||||
|
if n1 != n2 {
|
||||||
|
// verify reciprocal arcs
|
||||||
|
m = 0
|
||||||
|
for _, t := range (*s.Super).AdjacencyList[n2] {
|
||||||
|
if t == n1 {
|
||||||
|
if m == n {
|
||||||
|
goto good
|
||||||
|
}
|
||||||
|
m++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("edge not available in supergraph")
|
||||||
|
}
|
||||||
|
good:
|
||||||
|
// matched enough edges. nodes can finally
|
||||||
|
// be added as needed and then the edge can be added.
|
||||||
|
b1 := s.AddNode(n1)
|
||||||
|
b2 := s.AddNode(n2)
|
||||||
|
s.Undirected.AddEdge(b1, b2)
|
||||||
|
return nil // success
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddEdge adds an edge to a subgraph.
|
||||||
|
//
|
||||||
|
// For argument e, e.N1 and e.N2 must be NIs in supergraph s.Super. As with
|
||||||
|
// AddNode, AddEdge panics if e.N1 and e.N2 are not valid node indexes of
|
||||||
|
// s.Super.
|
||||||
|
//
|
||||||
|
// Edge e must exist in s.Super with label l. Further, the number of
|
||||||
|
// parallel edges in the subgraph cannot exceed the number of corresponding
|
||||||
|
// parallel edges in the supergraph. That is, each edge already added to the
|
||||||
|
// subgraph counts against the edges available in the supergraph. If a matching
|
||||||
|
// edge is not available, AddEdge returns an error.
|
||||||
|
//
|
||||||
|
// If a matching edge is available, subgraph nodes are added as needed, the
|
||||||
|
// subgraph edge is added, and the method returns nil.
|
||||||
|
func (s *LabeledUndirectedSubgraph) AddEdge(e Edge, l LI) error {
|
||||||
|
// verify supergraph NIs first, but without adding subgraph nodes just yet.
|
||||||
|
if int(e.N1) < 0 || int(e.N1) >= s.Super.Order() {
|
||||||
|
panic(fmt.Sprint("AddEdge: NI ", e.N1, " not in supergraph"))
|
||||||
|
}
|
||||||
|
if int(e.N2) < 0 || int(e.N2) >= s.Super.Order() {
|
||||||
|
panic(fmt.Sprint("AddEdge: NI ", e.N2, " not in supergraph"))
|
||||||
|
}
|
||||||
|
// count existing matching edges in subgraph
|
||||||
|
n := 0
|
||||||
|
a := s.LabeledUndirected.LabeledAdjacencyList
|
||||||
|
if b1, ok := s.SubNI[e.N1]; ok {
|
||||||
|
if b2, ok := s.SubNI[e.N2]; ok {
|
||||||
|
// both NIs already exist in subgraph, need to count edges
|
||||||
|
h := Half{b2, l}
|
||||||
|
for _, t := range a[b1] {
|
||||||
|
if t == h {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b1 != b2 {
|
||||||
|
// verify reciprocal arcs exist
|
||||||
|
r := 0
|
||||||
|
h.To = b1
|
||||||
|
for _, t := range a[b2] {
|
||||||
|
if t == h {
|
||||||
|
r++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r < n {
|
||||||
|
n = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// verify matching edges are available in supergraph
|
||||||
|
m := 0
|
||||||
|
h := Half{e.N2, l}
|
||||||
|
for _, t := range (*s.Super).LabeledAdjacencyList[e.N1] {
|
||||||
|
if t == h {
|
||||||
|
if m == n {
|
||||||
|
goto r // arc match after all existing arcs matched
|
||||||
|
}
|
||||||
|
m++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("edge not available in supergraph")
|
||||||
|
r:
|
||||||
|
if e.N1 != e.N2 {
|
||||||
|
// verify reciprocal arcs
|
||||||
|
m = 0
|
||||||
|
h.To = e.N1
|
||||||
|
for _, t := range (*s.Super).LabeledAdjacencyList[e.N2] {
|
||||||
|
if t == h {
|
||||||
|
if m == n {
|
||||||
|
goto good
|
||||||
|
}
|
||||||
|
m++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("edge not available in supergraph")
|
||||||
|
}
|
||||||
|
good:
|
||||||
|
// matched enough edges. nodes can finally
|
||||||
|
// be added as needed and then the edge can be added.
|
||||||
|
n1 := s.AddNode(e.N1)
|
||||||
|
n2 := s.AddNode(e.N2)
|
||||||
|
s.LabeledUndirected.AddEdge(Edge{n1, n2}, l)
|
||||||
|
return nil // success
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility function called from all of the InduceList methods.
|
||||||
|
func mapList(l []NI) (sub map[NI]NI, sup []NI) {
|
||||||
|
sub = map[NI]NI{}
|
||||||
|
// one pass to collect unique NIs
|
||||||
|
for _, p := range l {
|
||||||
|
sub[NI(p)] = -1
|
||||||
|
}
|
||||||
|
if len(sub) == len(l) { // NIs in l are unique
|
||||||
|
sup = append([]NI{}, l...) // just copy them
|
||||||
|
for b, p := range l {
|
||||||
|
sub[p] = NI(b) // and fill in map
|
||||||
|
}
|
||||||
|
} else { // NIs in l not unique
|
||||||
|
sup = make([]NI, 0, len(sub))
|
||||||
|
for _, p := range l { // preserve ordering of first occurrences in l
|
||||||
|
if sub[p] < 0 {
|
||||||
|
sub[p] = NI(len(sup))
|
||||||
|
sup = append(sup, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility function called from all of the InduceBits methods.
|
||||||
|
func mapBits(t bits.Bits) (sub map[NI]NI, sup []NI) {
|
||||||
|
sup = make([]NI, 0, t.OnesCount())
|
||||||
|
sub = make(map[NI]NI, cap(sup))
|
||||||
|
t.IterateOnes(func(n int) bool {
|
||||||
|
sub[NI(n)] = NI(len(sup))
|
||||||
|
sup = append(sup, NI(n))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrderMap formats maps for testable examples.
|
||||||
|
//
|
||||||
|
// OrderMap provides simple, no-frills formatting of maps in sorted order,
|
||||||
|
// convenient in some cases for output of testable examples.
|
||||||
|
func OrderMap(m interface{}) string {
|
||||||
|
// in particular exclude slices, which template would happily accept but
|
||||||
|
// which would probably represent a coding mistake
|
||||||
|
if reflect.TypeOf(m).Kind() != reflect.Map {
|
||||||
|
panic("not a map")
|
||||||
|
}
|
||||||
|
t := template.Must(template.New("").Parse(
|
||||||
|
`map[{{range $k, $v := .}}{{$k}}:{{$v}} {{end}}]`))
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := t.Execute(&b, m); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
|
@ -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.
|
|
@ -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]
|
||||||
|
}
|
|
@ -0,0 +1,708 @@
|
||||||
|
// Copyright 2016 Sonia Keys
|
||||||
|
// License MIT: https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/soniakeys/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChungLu constructs a random simple undirected graph.
|
||||||
|
//
|
||||||
|
// The Chung Lu model is similar to a "configuration model" where each
|
||||||
|
// node has a specified degree. In the Chung Lu model the degree specified
|
||||||
|
// for each node is taken as an expected degree, not an exact degree.
|
||||||
|
//
|
||||||
|
// Argument w is "weight," the expected degree for each node.
|
||||||
|
// The values of w must be given in decreasing order.
|
||||||
|
//
|
||||||
|
// The constructed graph will have node 0 with expected degree w[0] and so on
|
||||||
|
// so degree will decrease with node number. To randomize degree across
|
||||||
|
// node numbers, consider using the Permute method with a rand.Perm.
|
||||||
|
//
|
||||||
|
// Also returned is the actual size m of constructed graph g.
|
||||||
|
//
|
||||||
|
// If Rand r is nil, the rand package default shared source is used.
|
||||||
|
func ChungLu(w []float64, rr *rand.Rand) (g Undirected, m int) {
|
||||||
|
// Ref: "Efficient Generation of Networks with Given Expected Degrees"
|
||||||
|
// Joel C. Miller and Aric Hagberg
|
||||||
|
// accessed at http://aric.hagberg.org/papers/miller-2011-efficient.pdf
|
||||||
|
rf := rand.Float64
|
||||||
|
if rr != nil {
|
||||||
|
rf = rr.Float64
|
||||||
|
}
|
||||||
|
a := make(AdjacencyList, len(w))
|
||||||
|
S := 0.
|
||||||
|
for i := len(w) - 1; i >= 0; i-- {
|
||||||
|
S += w[i]
|
||||||
|
}
|
||||||
|
for u := 0; u < len(w)-1; u++ {
|
||||||
|
v := u + 1
|
||||||
|
p := w[u] * w[v] / S
|
||||||
|
if p > 1 {
|
||||||
|
p = 1
|
||||||
|
}
|
||||||
|
for v < len(w) && p > 0 {
|
||||||
|
if p != 1 {
|
||||||
|
v += int(math.Log(rf()) / math.Log(1-p))
|
||||||
|
}
|
||||||
|
if v < len(w) {
|
||||||
|
q := w[u] * w[v] / S
|
||||||
|
if q > 1 {
|
||||||
|
q = 1
|
||||||
|
}
|
||||||
|
if rf() < q/p {
|
||||||
|
a[u] = append(a[u], NI(v))
|
||||||
|
a[v] = append(a[v], NI(u))
|
||||||
|
m++
|
||||||
|
}
|
||||||
|
p = q
|
||||||
|
v++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Undirected{a}, m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Euclidean generates a random simple graph on the Euclidean plane.
|
||||||
|
//
|
||||||
|
// Nodes are associated with coordinates uniformly distributed on a unit
|
||||||
|
// square. Arcs are added between random nodes with a bias toward connecting
|
||||||
|
// nearer nodes.
|
||||||
|
//
|
||||||
|
// Unfortunately the function has a few "knobs".
|
||||||
|
// The returned graph will have order nNodes and arc size nArcs. The affinity
|
||||||
|
// argument controls the bias toward connecting nearer nodes. The function
|
||||||
|
// selects random pairs of nodes as a candidate arc then rejects the candidate
|
||||||
|
// if the nodes fail an affinity test. Also parallel arcs are rejected.
|
||||||
|
// As more affine or denser graphs are requested, rejections increase,
|
||||||
|
// increasing run time. The patience argument controls the number of arc
|
||||||
|
// rejections allowed before the function gives up and returns an error.
|
||||||
|
// Note that higher affinity will require more patience and that some
|
||||||
|
// combinations of nNodes and nArcs cannot be achieved with any amount of
|
||||||
|
// patience given that the returned graph must be simple.
|
||||||
|
//
|
||||||
|
// If Rand r is nil, the rand package default shared source is used.
|
||||||
|
//
|
||||||
|
// Returned is a directed simple graph and associated positions indexed by
|
||||||
|
// node number. In the arc list for each node, to-nodes are in random
|
||||||
|
// order.
|
||||||
|
//
|
||||||
|
// See also LabeledEuclidean.
|
||||||
|
func Euclidean(nNodes, nArcs int, affinity float64, patience int, rr *rand.Rand) (g Directed, pos []struct{ X, Y float64 }, err error) {
|
||||||
|
a := make(AdjacencyList, nNodes) // graph
|
||||||
|
ri, rf, re := rand.Intn, rand.Float64, rand.ExpFloat64
|
||||||
|
if rr != nil {
|
||||||
|
ri, rf, re = rr.Intn, rr.Float64, rr.ExpFloat64
|
||||||
|
}
|
||||||
|
// generate random positions
|
||||||
|
pos = make([]struct{ X, Y float64 }, nNodes)
|
||||||
|
for i := range pos {
|
||||||
|
pos[i].X = rf()
|
||||||
|
pos[i].Y = rf()
|
||||||
|
}
|
||||||
|
// arcs
|
||||||
|
var tooFar, dup int
|
||||||
|
arc:
|
||||||
|
for i := 0; i < nArcs; {
|
||||||
|
if tooFar == nArcs*patience {
|
||||||
|
err = errors.New("affinity not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if dup == nArcs*patience {
|
||||||
|
err = errors.New("overcrowding")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n1 := NI(ri(nNodes))
|
||||||
|
var n2 NI
|
||||||
|
for {
|
||||||
|
n2 = NI(ri(nNodes))
|
||||||
|
if n2 != n1 { // no graph loops
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c1 := &pos[n1]
|
||||||
|
c2 := &pos[n2]
|
||||||
|
dist := math.Hypot(c2.X-c1.X, c2.Y-c1.Y)
|
||||||
|
if dist*affinity > re() { // favor near nodes
|
||||||
|
tooFar++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, nb := range a[n1] {
|
||||||
|
if nb == n2 { // no parallel arcs
|
||||||
|
dup++
|
||||||
|
continue arc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a[n1] = append(a[n1], n2)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
g = Directed{a}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabeledEuclidean generates a random simple graph on the Euclidean plane.
|
||||||
|
//
|
||||||
|
// Arc label values in the returned graph g are indexes into the return value
|
||||||
|
// wt. Wt is the Euclidean distance between the from and to nodes of the arc.
|
||||||
|
//
|
||||||
|
// Otherwise the function arguments and return values are the same as for
|
||||||
|
// function Euclidean. See Euclidean.
|
||||||
|
func LabeledEuclidean(nNodes, nArcs int, affinity float64, patience int, rr *rand.Rand) (g LabeledDirected, pos []struct{ X, Y float64 }, wt []float64, err error) {
|
||||||
|
a := make(LabeledAdjacencyList, nNodes) // graph
|
||||||
|
wt = make([]float64, nArcs) // arc weights
|
||||||
|
ri, rf, re := rand.Intn, rand.Float64, rand.ExpFloat64
|
||||||
|
if rr != nil {
|
||||||
|
ri, rf, re = rr.Intn, rr.Float64, rr.ExpFloat64
|
||||||
|
}
|
||||||
|
// generate random positions
|
||||||
|
pos = make([]struct{ X, Y float64 }, nNodes)
|
||||||
|
for i := range pos {
|
||||||
|
pos[i].X = rf()
|
||||||
|
pos[i].Y = rf()
|
||||||
|
}
|
||||||
|
// arcs
|
||||||
|
var tooFar, dup int
|
||||||
|
arc:
|
||||||
|
for i := 0; i < nArcs; {
|
||||||
|
if tooFar == nArcs*patience {
|
||||||
|
err = errors.New("affinity not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if dup == nArcs*patience {
|
||||||
|
err = errors.New("overcrowding")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n1 := NI(ri(nNodes))
|
||||||
|
var n2 NI
|
||||||
|
for {
|
||||||
|
n2 = NI(ri(nNodes))
|
||||||
|
if n2 != n1 { // no graph loops
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c1 := &pos[n1]
|
||||||
|
c2 := &pos[n2]
|
||||||
|
dist := math.Hypot(c2.X-c1.X, c2.Y-c1.Y)
|
||||||
|
if dist*affinity > re() { // favor near nodes
|
||||||
|
tooFar++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, nb := range a[n1] {
|
||||||
|
if nb.To == n2 { // no parallel arcs
|
||||||
|
dup++
|
||||||
|
continue arc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wt[i] = dist
|
||||||
|
a[n1] = append(a[n1], Half{n2, LI(i)})
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
g = LabeledDirected{a}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Geometric generates a random geometric graph (RGG) on the Euclidean plane.
|
||||||
|
//
|
||||||
|
// An RGG is an undirected simple graph. Nodes are associated with coordinates
|
||||||
|
// uniformly distributed on a unit square. Edges are added between all nodes
|
||||||
|
// falling within a specified distance or radius of each other.
|
||||||
|
//
|
||||||
|
// The resulting number of edges is somewhat random but asymptotically
|
||||||
|
// approaches m = πr²n²/2. The method accumulates and returns the actual
|
||||||
|
// number of edges constructed. In the arc list for each node, to-nodes are
|
||||||
|
// ordered. Consider using ShuffleArcLists if random order is important.
|
||||||
|
//
|
||||||
|
// If Rand r is nil, the rand package default shared source is used.
|
||||||
|
//
|
||||||
|
// See also LabeledGeometric.
|
||||||
|
func Geometric(nNodes int, radius float64, rr *rand.Rand) (g Undirected, pos []struct{ X, Y float64 }, m int) {
|
||||||
|
// Expected degree is approximately nπr².
|
||||||
|
a := make(AdjacencyList, nNodes)
|
||||||
|
rf := rand.Float64
|
||||||
|
if rr != nil {
|
||||||
|
rf = rr.Float64
|
||||||
|
}
|
||||||
|
pos = make([]struct{ X, Y float64 }, nNodes)
|
||||||
|
for i := range pos {
|
||||||
|
pos[i].X = rf()
|
||||||
|
pos[i].Y = rf()
|
||||||
|
}
|
||||||
|
for u, up := range pos {
|
||||||
|
for v := u + 1; v < len(pos); v++ {
|
||||||
|
vp := pos[v]
|
||||||
|
dx := math.Abs(up.X - vp.X)
|
||||||
|
if dx >= radius {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dy := math.Abs(up.Y - vp.Y)
|
||||||
|
if dy >= radius {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if math.Hypot(dx, dy) < radius {
|
||||||
|
a[u] = append(a[u], NI(v))
|
||||||
|
a[v] = append(a[v], NI(u))
|
||||||
|
m++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g = Undirected{a}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabeledGeometric generates a random geometric graph (RGG) on the Euclidean
|
||||||
|
// plane.
|
||||||
|
//
|
||||||
|
// Edge label values in the returned graph g are indexes into the return value
|
||||||
|
// wt. Wt is the Euclidean distance between nodes of the edge. The graph
|
||||||
|
// size m is len(wt).
|
||||||
|
//
|
||||||
|
// See Geometric for additional description.
|
||||||
|
func LabeledGeometric(nNodes int, radius float64, rr *rand.Rand) (g LabeledUndirected, pos []struct{ X, Y float64 }, wt []float64) {
|
||||||
|
a := make(LabeledAdjacencyList, nNodes)
|
||||||
|
rf := rand.Float64
|
||||||
|
if rr != nil {
|
||||||
|
rf = rr.Float64
|
||||||
|
}
|
||||||
|
pos = make([]struct{ X, Y float64 }, nNodes)
|
||||||
|
for i := range pos {
|
||||||
|
pos[i].X = rf()
|
||||||
|
pos[i].Y = rf()
|
||||||
|
}
|
||||||
|
for u, up := range pos {
|
||||||
|
for v := u + 1; v < len(pos); v++ {
|
||||||
|
vp := pos[v]
|
||||||
|
if w := math.Hypot(up.X-vp.X, up.Y-vp.Y); w < radius {
|
||||||
|
a[u] = append(a[u], Half{NI(v), LI(len(wt))})
|
||||||
|
a[v] = append(a[v], Half{NI(u), LI(len(wt))})
|
||||||
|
wt = append(wt, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g = LabeledUndirected{a}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GnmUndirected constructs a random simple undirected graph.
|
||||||
|
//
|
||||||
|
// Construction is by the Erdős–Rényi model where the specified number of
|
||||||
|
// distinct edges is selected from all possible edges with equal probability.
|
||||||
|
//
|
||||||
|
// Argument n is number of nodes, m is number of edges and must be <= n(n-1)/2.
|
||||||
|
//
|
||||||
|
// If Rand r is nil, the rand package default shared source is used.
|
||||||
|
//
|
||||||
|
// In the generated arc list for each node, to-nodes are ordered.
|
||||||
|
// Consider using ShuffleArcLists if random order is important.
|
||||||
|
//
|
||||||
|
// See also Gnm3Undirected, a method producing a statistically equivalent
|
||||||
|
// result, but by an algorithm with somewhat different performance properties.
|
||||||
|
// Performance of the two methods is expected to be similar in most cases but
|
||||||
|
// it may be worth trying both with your data to see if one has a clear
|
||||||
|
// advantage.
|
||||||
|
func GnmUndirected(n, m int, rr *rand.Rand) Undirected {
|
||||||
|
// based on Alg. 2 from "Efficient Generation of Large Random Networks",
|
||||||
|
// Vladimir Batagelj and Ulrik Brandes.
|
||||||
|
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||||
|
ri := rand.Intn
|
||||||
|
if rr != nil {
|
||||||
|
ri = rr.Intn
|
||||||
|
}
|
||||||
|
re := n * (n - 1) / 2
|
||||||
|
ml := m
|
||||||
|
if m*2 > re {
|
||||||
|
ml = re - m
|
||||||
|
}
|
||||||
|
e := map[int]struct{}{}
|
||||||
|
for len(e) < ml {
|
||||||
|
e[ri(re)] = struct{}{}
|
||||||
|
}
|
||||||
|
a := make(AdjacencyList, n)
|
||||||
|
if m*2 > re {
|
||||||
|
i := 0
|
||||||
|
for v := 1; v < n; v++ {
|
||||||
|
for w := 0; w < v; w++ {
|
||||||
|
if _, ok := e[i]; !ok {
|
||||||
|
a[v] = append(a[v], NI(w))
|
||||||
|
a[w] = append(a[w], NI(v))
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := range e {
|
||||||
|
v := 1 + int(math.Sqrt(.25+float64(2*i))-.5)
|
||||||
|
w := i - (v * (v - 1) / 2)
|
||||||
|
a[v] = append(a[v], NI(w))
|
||||||
|
a[w] = append(a[w], NI(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Undirected{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GnmDirected constructs a random simple directed graph.
|
||||||
|
//
|
||||||
|
// Construction is by the Erdős–Rényi model where the specified number of
|
||||||
|
// distinct arcs is selected from all possible arcs with equal probability.
|
||||||
|
//
|
||||||
|
// Argument n is number of nodes, ma is number of arcs and must be <= n(n-1).
|
||||||
|
//
|
||||||
|
// If Rand r is nil, the rand package default shared source is used.
|
||||||
|
//
|
||||||
|
// In the generated arc list for each node, to-nodes are ordered.
|
||||||
|
// Consider using ShuffleArcLists if random order is important.
|
||||||
|
//
|
||||||
|
// See also Gnm3Directed, a method producing a statistically equivalent
|
||||||
|
// result, but by
|
||||||
|
// an algorithm with somewhat different performance properties. Performance
|
||||||
|
// of the two methods is expected to be similar in most cases but it may be
|
||||||
|
// worth trying both with your data to see if one has a clear advantage.
|
||||||
|
func GnmDirected(n, ma int, rr *rand.Rand) Directed {
|
||||||
|
// based on Alg. 2 from "Efficient Generation of Large Random Networks",
|
||||||
|
// Vladimir Batagelj and Ulrik Brandes.
|
||||||
|
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||||
|
ri := rand.Intn
|
||||||
|
if rr != nil {
|
||||||
|
ri = rr.Intn
|
||||||
|
}
|
||||||
|
re := n * (n - 1)
|
||||||
|
ml := ma
|
||||||
|
if ma*2 > re {
|
||||||
|
ml = re - ma
|
||||||
|
}
|
||||||
|
e := map[int]struct{}{}
|
||||||
|
for len(e) < ml {
|
||||||
|
e[ri(re)] = struct{}{}
|
||||||
|
}
|
||||||
|
a := make(AdjacencyList, n)
|
||||||
|
if ma*2 > re {
|
||||||
|
i := 0
|
||||||
|
for v := 0; v < n; v++ {
|
||||||
|
for w := 0; w < n; w++ {
|
||||||
|
if w == v {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := e[i]; !ok {
|
||||||
|
a[v] = append(a[v], NI(w))
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := range e {
|
||||||
|
v := i / (n - 1)
|
||||||
|
w := i % (n - 1)
|
||||||
|
if w >= v {
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
a[v] = append(a[v], NI(w))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Directed{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gnm3Undirected constructs a random simple undirected graph.
|
||||||
|
//
|
||||||
|
// Construction is by the Erdős–Rényi model where the specified number of
|
||||||
|
// distinct edges is selected from all possible edges with equal probability.
|
||||||
|
//
|
||||||
|
// Argument n is number of nodes, m is number of edges and must be <= n(n-1)/2.
|
||||||
|
//
|
||||||
|
// If Rand r is nil, the rand package default shared source is used.
|
||||||
|
//
|
||||||
|
// In the generated arc list for each node, to-nodes are ordered.
|
||||||
|
// Consider using ShuffleArcLists if random order is important.
|
||||||
|
//
|
||||||
|
// See also GnmUndirected, a method producing a statistically equivalent
|
||||||
|
// result, but by an algorithm with somewhat different performance properties.
|
||||||
|
// Performance of the two methods is expected to be similar in most cases but
|
||||||
|
// it may be worth trying both with your data to see if one has a clear
|
||||||
|
// advantage.
|
||||||
|
func Gnm3Undirected(n, m int, rr *rand.Rand) Undirected {
|
||||||
|
// based on Alg. 3 from "Efficient Generation of Large Random Networks",
|
||||||
|
// Vladimir Batagelj and Ulrik Brandes.
|
||||||
|
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||||
|
//
|
||||||
|
// I like this algorithm for its elegance. Pitty it tends to run a
|
||||||
|
// a little slower than the retry algorithm of Gnm.
|
||||||
|
ri := rand.Intn
|
||||||
|
if rr != nil {
|
||||||
|
ri = rr.Intn
|
||||||
|
}
|
||||||
|
a := make(AdjacencyList, n)
|
||||||
|
re := n * (n - 1) / 2
|
||||||
|
rm := map[int]int{}
|
||||||
|
for i := 0; i < m; i++ {
|
||||||
|
er := i + ri(re-i)
|
||||||
|
eNew := er
|
||||||
|
if rp, ok := rm[er]; ok {
|
||||||
|
eNew = rp
|
||||||
|
}
|
||||||
|
if rp, ok := rm[i]; !ok {
|
||||||
|
rm[er] = i
|
||||||
|
} else {
|
||||||
|
rm[er] = rp
|
||||||
|
}
|
||||||
|
v := 1 + int(math.Sqrt(.25+float64(2*eNew))-.5)
|
||||||
|
w := eNew - (v * (v - 1) / 2)
|
||||||
|
a[v] = append(a[v], NI(w))
|
||||||
|
a[w] = append(a[w], NI(v))
|
||||||
|
}
|
||||||
|
return Undirected{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gnm3Directed constructs a random simple directed graph.
|
||||||
|
//
|
||||||
|
// Construction is by the Erdős–Rényi model where the specified number of
|
||||||
|
// distinct arcs is selected from all possible arcs with equal probability.
|
||||||
|
//
|
||||||
|
// Argument n is number of nodes, ma is number of arcs and must be <= n(n-1).
|
||||||
|
//
|
||||||
|
// If Rand r is nil, the rand package default shared source is used.
|
||||||
|
//
|
||||||
|
// In the generated arc list for each node, to-nodes are ordered.
|
||||||
|
// Consider using ShuffleArcLists if random order is important.
|
||||||
|
//
|
||||||
|
// See also GnmDirected, a method producing a statistically equivalent result,
|
||||||
|
// but by an algorithm with somewhat different performance properties.
|
||||||
|
// Performance of the two methods is expected to be similar in most cases
|
||||||
|
// but it may be worth trying both with your data to see if one has a clear
|
||||||
|
// advantage.
|
||||||
|
func Gnm3Directed(n, ma int, rr *rand.Rand) Directed {
|
||||||
|
// based on Alg. 3 from "Efficient Generation of Large Random Networks",
|
||||||
|
// Vladimir Batagelj and Ulrik Brandes.
|
||||||
|
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||||
|
ri := rand.Intn
|
||||||
|
if rr != nil {
|
||||||
|
ri = rr.Intn
|
||||||
|
}
|
||||||
|
a := make(AdjacencyList, n)
|
||||||
|
re := n * (n - 1)
|
||||||
|
rm := map[int]int{}
|
||||||
|
for i := 0; i < ma; i++ {
|
||||||
|
er := i + ri(re-i)
|
||||||
|
eNew := er
|
||||||
|
if rp, ok := rm[er]; ok {
|
||||||
|
eNew = rp
|
||||||
|
}
|
||||||
|
if rp, ok := rm[i]; !ok {
|
||||||
|
rm[er] = i
|
||||||
|
} else {
|
||||||
|
rm[er] = rp
|
||||||
|
}
|
||||||
|
v := eNew / (n - 1)
|
||||||
|
w := eNew % (n - 1)
|
||||||
|
if w >= v {
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
a[v] = append(a[v], NI(w))
|
||||||
|
}
|
||||||
|
return Directed{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GnpUndirected constructs a random simple undirected graph.
|
||||||
|
//
|
||||||
|
// Construction is by the Gilbert model, an Erdős–Rényi like model where
|
||||||
|
// distinct edges are independently selected from all possible edges with
|
||||||
|
// the specified probability.
|
||||||
|
//
|
||||||
|
// Argument n is number of nodes, p is probability for selecting an edge.
|
||||||
|
//
|
||||||
|
// If Rand r is nil, the rand package default shared source is used.
|
||||||
|
//
|
||||||
|
// In the generated arc list for each node, to-nodes are ordered.
|
||||||
|
// Consider using ShuffleArcLists if random order is important.
|
||||||
|
//
|
||||||
|
// Also returned is the actual size m of constructed graph g.
|
||||||
|
func GnpUndirected(n int, p float64, rr *rand.Rand) (g Undirected, m int) {
|
||||||
|
a := make(AdjacencyList, n)
|
||||||
|
if n < 2 {
|
||||||
|
return Undirected{a}, 0
|
||||||
|
}
|
||||||
|
rf := rand.Float64
|
||||||
|
if rr != nil {
|
||||||
|
rf = rr.Float64
|
||||||
|
}
|
||||||
|
// based on Alg. 1 from "Efficient Generation of Large Random Networks",
|
||||||
|
// Vladimir Batagelj and Ulrik Brandes.
|
||||||
|
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||||
|
var v, w NI = 1, -1
|
||||||
|
g:
|
||||||
|
for c := 1 / math.Log(1-p); ; {
|
||||||
|
w += 1 + NI(c*math.Log(1-rf()))
|
||||||
|
for {
|
||||||
|
if w < v {
|
||||||
|
a[v] = append(a[v], w)
|
||||||
|
a[w] = append(a[w], v)
|
||||||
|
m++
|
||||||
|
continue g
|
||||||
|
}
|
||||||
|
w -= v
|
||||||
|
v++
|
||||||
|
if v == NI(n) {
|
||||||
|
break g
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Undirected{a}, m
|
||||||
|
}
|
||||||
|
|
||||||
|
// GnpDirected constructs a random simple directed graph.
|
||||||
|
//
|
||||||
|
// Construction is by the Gilbert model, an Erdős–Rényi like model where
|
||||||
|
// distinct arcs are independently selected from all possible arcs with
|
||||||
|
// the specified probability.
|
||||||
|
//
|
||||||
|
// Argument n is number of nodes, p is probability for selecting an arc.
|
||||||
|
//
|
||||||
|
// If Rand r is nil, the rand package default shared source is used.
|
||||||
|
//
|
||||||
|
// In the generated arc list for each node, to-nodes are ordered.
|
||||||
|
// Consider using ShuffleArcLists if random order is important.
|
||||||
|
//
|
||||||
|
// Also returned is the actual arc size m of constructed graph g.
|
||||||
|
func GnpDirected(n int, p float64, rr *rand.Rand) (g Directed, ma int) {
|
||||||
|
a := make(AdjacencyList, n)
|
||||||
|
if n < 2 {
|
||||||
|
return Directed{a}, 0
|
||||||
|
}
|
||||||
|
rf := rand.Float64
|
||||||
|
if rr != nil {
|
||||||
|
rf = rr.Float64
|
||||||
|
}
|
||||||
|
// based on Alg. 1 from "Efficient Generation of Large Random Networks",
|
||||||
|
// Vladimir Batagelj and Ulrik Brandes.
|
||||||
|
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||||
|
var v, w NI = 0, -1
|
||||||
|
g:
|
||||||
|
for c := 1 / math.Log(1-p); ; {
|
||||||
|
w += 1 + NI(c*math.Log(1-rf()))
|
||||||
|
for ; ; w -= NI(n) {
|
||||||
|
if w == v {
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
if w < NI(n) {
|
||||||
|
a[v] = append(a[v], w)
|
||||||
|
ma++
|
||||||
|
continue g
|
||||||
|
}
|
||||||
|
v++
|
||||||
|
if v == NI(n) {
|
||||||
|
break g
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Directed{a}, ma
|
||||||
|
}
|
||||||
|
|
||||||
|
// KroneckerDirected generates a Kronecker-like random directed graph.
|
||||||
|
//
|
||||||
|
// The returned graph g is simple and has no isolated nodes but is not
|
||||||
|
// necessarily fully connected. The number of of nodes will be <= 2^scale,
|
||||||
|
// and will be near 2^scale for typical values of arcFactor, >= 2.
|
||||||
|
// ArcFactor * 2^scale arcs are generated, although loops and duplicate arcs
|
||||||
|
// are rejected. In the arc list for each node, to-nodes are in random
|
||||||
|
// order.
|
||||||
|
//
|
||||||
|
// If Rand r is nil, the rand package default shared source is used.
|
||||||
|
//
|
||||||
|
// Return value ma is the number of arcs retained in the result graph.
|
||||||
|
func KroneckerDirected(scale uint, arcFactor float64, rr *rand.Rand) (g Directed, ma int) {
|
||||||
|
a, m := kronecker(scale, arcFactor, true, rr)
|
||||||
|
return Directed{a}, m
|
||||||
|
}
|
||||||
|
|
||||||
|
// KroneckerUndirected generates a Kronecker-like random undirected graph.
|
||||||
|
//
|
||||||
|
// The returned graph g is simple and has no isolated nodes but is not
|
||||||
|
// necessarily fully connected. The number of of nodes will be <= 2^scale,
|
||||||
|
// and will be near 2^scale for typical values of edgeFactor, >= 2.
|
||||||
|
// EdgeFactor * 2^scale edges are generated, although loops and duplicate edges
|
||||||
|
// are rejected. In the arc list for each node, to-nodes are in random
|
||||||
|
// order.
|
||||||
|
//
|
||||||
|
// If Rand r is nil, the rand package default shared source is used.
|
||||||
|
//
|
||||||
|
// Return value m is the true number of edges--not arcs--retained in the result
|
||||||
|
// graph.
|
||||||
|
func KroneckerUndirected(scale uint, edgeFactor float64, rr *rand.Rand) (g Undirected, m int) {
|
||||||
|
al, s := kronecker(scale, edgeFactor, false, rr)
|
||||||
|
return Undirected{al}, s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Styled after the Graph500 example code. Not well tested currently.
|
||||||
|
// Graph500 example generates undirected only. No idea if the directed variant
|
||||||
|
// here is meaningful or not.
|
||||||
|
//
|
||||||
|
// note mma returns arc size ma for dir=true, but returns size m for dir=false
|
||||||
|
func kronecker(scale uint, edgeFactor float64, dir bool, rr *rand.Rand) (g AdjacencyList, mma int) {
|
||||||
|
rf, ri, rp := rand.Float64, rand.Intn, rand.Perm
|
||||||
|
if rr != nil {
|
||||||
|
rf, ri, rp = rr.Float64, rr.Intn, rr.Perm
|
||||||
|
}
|
||||||
|
N := 1 << scale // node extent
|
||||||
|
M := int(edgeFactor*float64(N) + .5) // number of arcs/edges to generate
|
||||||
|
a, b, c := 0.57, 0.19, 0.19 // initiator probabilities
|
||||||
|
ab := a + b
|
||||||
|
cNorm := c / (1 - ab)
|
||||||
|
aNorm := a / ab
|
||||||
|
ij := make([][2]NI, M)
|
||||||
|
bm := bits.New(N)
|
||||||
|
var nNodes int
|
||||||
|
for k := range ij {
|
||||||
|
var i, j int
|
||||||
|
for b := 1; b < N; b <<= 1 {
|
||||||
|
if rf() > ab {
|
||||||
|
i |= b
|
||||||
|
if rf() > cNorm {
|
||||||
|
j |= b
|
||||||
|
}
|
||||||
|
} else if rf() > aNorm {
|
||||||
|
j |= b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bm.Bit(i) == 0 {
|
||||||
|
bm.SetBit(i, 1)
|
||||||
|
nNodes++
|
||||||
|
}
|
||||||
|
if bm.Bit(j) == 0 {
|
||||||
|
bm.SetBit(j, 1)
|
||||||
|
nNodes++
|
||||||
|
}
|
||||||
|
r := ri(k + 1) // shuffle edges as they are generated
|
||||||
|
ij[k] = ij[r]
|
||||||
|
ij[r] = [2]NI{NI(i), NI(j)}
|
||||||
|
}
|
||||||
|
p := rp(nNodes) // mapping to shuffle IDs of non-isolated nodes
|
||||||
|
px := 0
|
||||||
|
rn := make([]NI, N)
|
||||||
|
for i := range rn {
|
||||||
|
if bm.Bit(i) == 1 {
|
||||||
|
rn[i] = NI(p[px]) // fill lookup table
|
||||||
|
px++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g = make(AdjacencyList, nNodes)
|
||||||
|
ij:
|
||||||
|
for _, e := range ij {
|
||||||
|
if e[0] == e[1] {
|
||||||
|
continue // skip loops
|
||||||
|
}
|
||||||
|
ri, rj := rn[e[0]], rn[e[1]]
|
||||||
|
for _, nb := range g[ri] {
|
||||||
|
if nb == rj {
|
||||||
|
continue ij // skip parallel edges
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g[ri] = append(g[ri], rj)
|
||||||
|
mma++
|
||||||
|
if !dir {
|
||||||
|
g[rj] = append(g[rj], ri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -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.
|
|
@ -0,0 +1,761 @@
|
||||||
|
// Copyright 2013 Sonia Keys
|
||||||
|
// License MIT: http://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/heap"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/soniakeys/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rNode holds data for a "reached" node
|
||||||
|
type rNode struct {
|
||||||
|
nx NI
|
||||||
|
state int8 // state constants defined below
|
||||||
|
f float64 // "g+h", path dist + heuristic estimate
|
||||||
|
fx int // heap.Fix index
|
||||||
|
}
|
||||||
|
|
||||||
|
// for rNode.state
|
||||||
|
const (
|
||||||
|
unreached = 0
|
||||||
|
reached = 1
|
||||||
|
open = 1
|
||||||
|
closed = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type openHeap []*rNode
|
||||||
|
|
||||||
|
// A Heuristic is defined on a specific end node. The function
|
||||||
|
// returns an estimate of the path distance from node argument
|
||||||
|
// "from" to the end node. Two subclasses of heuristics are "admissible"
|
||||||
|
// and "monotonic."
|
||||||
|
//
|
||||||
|
// Admissible means the value returned is guaranteed to be less than or
|
||||||
|
// equal to the actual shortest path distance from the node to end.
|
||||||
|
//
|
||||||
|
// An admissible estimate may further be monotonic.
|
||||||
|
// Monotonic means that for any neighboring nodes A and B with half arc aB
|
||||||
|
// leading from A to B, and for heuristic h defined on some end node, then
|
||||||
|
// h(A) <= aB.ArcWeight + h(B).
|
||||||
|
//
|
||||||
|
// See AStarA for additional notes on implementing heuristic functions for
|
||||||
|
// AStar search methods.
|
||||||
|
type Heuristic func(from NI) float64
|
||||||
|
|
||||||
|
// Admissible returns true if heuristic h is admissible on graph g relative to
|
||||||
|
// the given end node.
|
||||||
|
//
|
||||||
|
// If h is inadmissible, the string result describes a counter example.
|
||||||
|
func (h Heuristic) Admissible(g LabeledAdjacencyList, w WeightFunc, end NI) (bool, string) {
|
||||||
|
// invert graph
|
||||||
|
inv := make(LabeledAdjacencyList, len(g))
|
||||||
|
for from, nbs := range g {
|
||||||
|
for _, nb := range nbs {
|
||||||
|
inv[nb.To] = append(inv[nb.To],
|
||||||
|
Half{To: NI(from), Label: nb.Label})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// run dijkstra
|
||||||
|
// Dijkstra.AllPaths takes a start node but after inverting the graph
|
||||||
|
// argument end now represents the start node of the inverted graph.
|
||||||
|
f, _, dist, _ := inv.Dijkstra(end, -1, w)
|
||||||
|
// compare h to found shortest paths
|
||||||
|
for n := range inv {
|
||||||
|
if f.Paths[n].Len == 0 {
|
||||||
|
continue // no path, any heuristic estimate is fine.
|
||||||
|
}
|
||||||
|
if !(h(NI(n)) <= dist[n]) {
|
||||||
|
return false, fmt.Sprintf("h(%d) = %g, "+
|
||||||
|
"required to be <= found shortest path (%g)",
|
||||||
|
n, h(NI(n)), dist[n])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monotonic returns true if heuristic h is monotonic on weighted graph g.
|
||||||
|
//
|
||||||
|
// If h is non-monotonic, the string result describes a counter example.
|
||||||
|
func (h Heuristic) Monotonic(g LabeledAdjacencyList, w WeightFunc) (bool, string) {
|
||||||
|
// precompute
|
||||||
|
hv := make([]float64, len(g))
|
||||||
|
for n := range g {
|
||||||
|
hv[n] = h(NI(n))
|
||||||
|
}
|
||||||
|
// iterate over all edges
|
||||||
|
for from, nbs := range g {
|
||||||
|
for _, nb := range nbs {
|
||||||
|
arcWeight := w(nb.Label)
|
||||||
|
if !(hv[from] <= arcWeight+hv[nb.To]) {
|
||||||
|
return false, fmt.Sprintf("h(%d) = %g, "+
|
||||||
|
"required to be <= arc weight + h(%d) (= %g + %g = %g)",
|
||||||
|
from, hv[from],
|
||||||
|
nb.To, arcWeight, hv[nb.To], arcWeight+hv[nb.To])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// AStarA finds a path between two nodes.
|
||||||
|
//
|
||||||
|
// AStarA implements both algorithm A and algorithm A*. The difference in the
|
||||||
|
// two algorithms is strictly in the heuristic estimate returned by argument h.
|
||||||
|
// If h is an "admissible" heuristic estimate, then the algorithm is termed A*,
|
||||||
|
// otherwise it is algorithm A.
|
||||||
|
//
|
||||||
|
// Like Dijkstra's algorithm, AStarA with an admissible heuristic finds the
|
||||||
|
// shortest path between start and end. AStarA generally runs faster than
|
||||||
|
// Dijkstra though, by using the heuristic distance estimate.
|
||||||
|
//
|
||||||
|
// AStarA with an inadmissible heuristic becomes algorithm A. Algorithm A
|
||||||
|
// will find a path, but it is not guaranteed to be the shortest path.
|
||||||
|
// The heuristic still guides the search however, so a nearly admissible
|
||||||
|
// heuristic is likely to find a very good path, if not the best. Quality
|
||||||
|
// of the path returned degrades gracefully with the quality of the heuristic.
|
||||||
|
//
|
||||||
|
// The heuristic function h should ideally be fairly inexpensive. AStarA
|
||||||
|
// may call it more than once for the same node, especially as graph density
|
||||||
|
// increases. In some cases it may be worth the effort to memoize or
|
||||||
|
// precompute values.
|
||||||
|
//
|
||||||
|
// Argument g is the graph to be searched, with arc weights returned by w.
|
||||||
|
// As usual for AStar, arc weights must be non-negative.
|
||||||
|
// Graphs may be directed or undirected.
|
||||||
|
//
|
||||||
|
// If AStarA finds a path it returns a FromList encoding the path, the arc
|
||||||
|
// labels for path nodes, the total path distance, and ok = true.
|
||||||
|
// Otherwise it returns ok = false.
|
||||||
|
func (g LabeledAdjacencyList) AStarA(w WeightFunc, start, end NI, h Heuristic) (f FromList, labels []LI, dist float64, ok bool) {
|
||||||
|
// NOTE: AStarM is largely duplicate code.
|
||||||
|
|
||||||
|
f = NewFromList(len(g))
|
||||||
|
labels = make([]LI, len(g))
|
||||||
|
d := make([]float64, len(g))
|
||||||
|
r := make([]rNode, len(g))
|
||||||
|
for i := range r {
|
||||||
|
r[i].nx = NI(i)
|
||||||
|
}
|
||||||
|
// start node is reached initially
|
||||||
|
cr := &r[start]
|
||||||
|
cr.state = reached
|
||||||
|
cr.f = h(start) // total path estimate is estimate from start
|
||||||
|
rp := f.Paths
|
||||||
|
rp[start] = PathEnd{Len: 1, From: -1} // path length at start is 1 node
|
||||||
|
// oh is a heap of nodes "open" for exploration. nodes go on the heap
|
||||||
|
// when they get an initial or new "g" path distance, and therefore a
|
||||||
|
// new "f" which serves as priority for exploration.
|
||||||
|
oh := openHeap{cr}
|
||||||
|
for len(oh) > 0 {
|
||||||
|
bestPath := heap.Pop(&oh).(*rNode)
|
||||||
|
bestNode := bestPath.nx
|
||||||
|
if bestNode == end {
|
||||||
|
return f, labels, d[end], true
|
||||||
|
}
|
||||||
|
bp := &rp[bestNode]
|
||||||
|
nextLen := bp.Len + 1
|
||||||
|
for _, nb := range g[bestNode] {
|
||||||
|
alt := &r[nb.To]
|
||||||
|
ap := &rp[alt.nx]
|
||||||
|
// "g" path distance from start
|
||||||
|
g := d[bestNode] + w(nb.Label)
|
||||||
|
if alt.state == reached {
|
||||||
|
if g > d[nb.To] {
|
||||||
|
// candidate path to nb is longer than some alternate path
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if g == d[nb.To] && nextLen >= ap.Len {
|
||||||
|
// candidate path has identical length of some alternate
|
||||||
|
// path but it takes no fewer hops.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// cool, we found a better way to get to this node.
|
||||||
|
// record new path data for this node and
|
||||||
|
// update alt with new data and make sure it's on the heap.
|
||||||
|
*ap = PathEnd{From: bestNode, Len: nextLen}
|
||||||
|
labels[nb.To] = nb.Label
|
||||||
|
d[nb.To] = g
|
||||||
|
alt.f = g + h(nb.To)
|
||||||
|
if alt.fx < 0 {
|
||||||
|
heap.Push(&oh, alt)
|
||||||
|
} else {
|
||||||
|
heap.Fix(&oh, alt.fx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// bestNode being reached for the first time.
|
||||||
|
*ap = PathEnd{From: bestNode, Len: nextLen}
|
||||||
|
labels[nb.To] = nb.Label
|
||||||
|
d[nb.To] = g
|
||||||
|
alt.f = g + h(nb.To)
|
||||||
|
alt.state = reached
|
||||||
|
heap.Push(&oh, alt) // and it's now open for exploration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return // no path
|
||||||
|
}
|
||||||
|
|
||||||
|
// AStarAPath finds a shortest path using the AStarA algorithm.
|
||||||
|
//
|
||||||
|
// This is a convenience method with a simpler result than the AStarA method.
|
||||||
|
// See documentation on the AStarA method.
|
||||||
|
//
|
||||||
|
// If a path is found, the non-nil node path is returned with the total path
|
||||||
|
// distance. Otherwise the returned path will be nil.
|
||||||
|
func (g LabeledAdjacencyList) AStarAPath(start, end NI, h Heuristic, w WeightFunc) (LabeledPath, float64) {
|
||||||
|
f, labels, d, _ := g.AStarA(w, start, end, h)
|
||||||
|
return f.PathToLabeled(end, labels, nil), d
|
||||||
|
}
|
||||||
|
|
||||||
|
// AStarM is AStarA optimized for monotonic heuristic estimates.
|
||||||
|
//
|
||||||
|
// Note that this function requires a monotonic heuristic. Results will
|
||||||
|
// not be meaningful if argument h is non-monotonic.
|
||||||
|
//
|
||||||
|
// See AStarA for general usage. See Heuristic for notes on monotonicity.
|
||||||
|
func (g LabeledAdjacencyList) AStarM(w WeightFunc, start, end NI, h Heuristic) (f FromList, labels []LI, dist float64, ok bool) {
|
||||||
|
// NOTE: AStarM is largely code duplicated from AStarA.
|
||||||
|
// Differences are noted in comments in this method.
|
||||||
|
|
||||||
|
f = NewFromList(len(g))
|
||||||
|
labels = make([]LI, len(g))
|
||||||
|
d := make([]float64, len(g))
|
||||||
|
r := make([]rNode, len(g))
|
||||||
|
for i := range r {
|
||||||
|
r[i].nx = NI(i)
|
||||||
|
}
|
||||||
|
cr := &r[start]
|
||||||
|
|
||||||
|
// difference from AStarA:
|
||||||
|
// instead of a bit to mark a reached node, there are two states,
|
||||||
|
// open and closed. open marks nodes "open" for exploration.
|
||||||
|
// nodes are marked open as they are reached, then marked
|
||||||
|
// closed as they are found to be on the best path.
|
||||||
|
cr.state = open
|
||||||
|
|
||||||
|
cr.f = h(start)
|
||||||
|
rp := f.Paths
|
||||||
|
rp[start] = PathEnd{Len: 1, From: -1}
|
||||||
|
oh := openHeap{cr}
|
||||||
|
for len(oh) > 0 {
|
||||||
|
bestPath := heap.Pop(&oh).(*rNode)
|
||||||
|
bestNode := bestPath.nx
|
||||||
|
if bestNode == end {
|
||||||
|
return f, labels, d[end], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// difference from AStarA:
|
||||||
|
// move nodes to closed list as they are found to be best so far.
|
||||||
|
bestPath.state = closed
|
||||||
|
|
||||||
|
bp := &rp[bestNode]
|
||||||
|
nextLen := bp.Len + 1
|
||||||
|
for _, nb := range g[bestNode] {
|
||||||
|
alt := &r[nb.To]
|
||||||
|
|
||||||
|
// difference from AStarA:
|
||||||
|
// Monotonicity means that f cannot be improved.
|
||||||
|
if alt.state == closed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ap := &rp[alt.nx]
|
||||||
|
g := d[bestNode] + w(nb.Label)
|
||||||
|
|
||||||
|
// difference from AStarA:
|
||||||
|
// test for open state, not just reached
|
||||||
|
if alt.state == open {
|
||||||
|
|
||||||
|
if g > d[nb.To] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if g == d[nb.To] && nextLen >= ap.Len {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
*ap = PathEnd{From: bestNode, Len: nextLen}
|
||||||
|
labels[nb.To] = nb.Label
|
||||||
|
d[nb.To] = g
|
||||||
|
alt.f = g + h(nb.To)
|
||||||
|
|
||||||
|
// difference from AStarA:
|
||||||
|
// we know alt was on the heap because we found it marked open
|
||||||
|
heap.Fix(&oh, alt.fx)
|
||||||
|
} else {
|
||||||
|
*ap = PathEnd{From: bestNode, Len: nextLen}
|
||||||
|
labels[nb.To] = nb.Label
|
||||||
|
d[nb.To] = g
|
||||||
|
alt.f = g + h(nb.To)
|
||||||
|
|
||||||
|
// difference from AStarA:
|
||||||
|
// nodes are opened when first reached
|
||||||
|
alt.state = open
|
||||||
|
heap.Push(&oh, alt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AStarMPath finds a shortest path using the AStarM algorithm.
|
||||||
|
//
|
||||||
|
// This is a convenience method with a simpler result than the AStarM method.
|
||||||
|
// See documentation on the AStarM and AStarA methods.
|
||||||
|
//
|
||||||
|
// If a path is found, the non-nil node path is returned with the total path
|
||||||
|
// distance. Otherwise the returned path will be nil.
|
||||||
|
func (g LabeledAdjacencyList) AStarMPath(start, end NI, h Heuristic, w WeightFunc) (LabeledPath, float64) {
|
||||||
|
f, labels, d, _ := g.AStarM(w, start, end, h)
|
||||||
|
return f.PathToLabeled(end, labels, nil), d
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement container/heap
|
||||||
|
func (h openHeap) Len() int { return len(h) }
|
||||||
|
func (h openHeap) Less(i, j int) bool { return h[i].f < h[j].f }
|
||||||
|
func (h openHeap) Swap(i, j int) {
|
||||||
|
h[i], h[j] = h[j], h[i]
|
||||||
|
h[i].fx = i
|
||||||
|
h[j].fx = j
|
||||||
|
}
|
||||||
|
func (p *openHeap) Push(x interface{}) {
|
||||||
|
h := *p
|
||||||
|
fx := len(h)
|
||||||
|
h = append(h, x.(*rNode))
|
||||||
|
h[fx].fx = fx
|
||||||
|
*p = h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *openHeap) Pop() interface{} {
|
||||||
|
h := *p
|
||||||
|
last := len(h) - 1
|
||||||
|
*p = h[:last]
|
||||||
|
h[last].fx = -1
|
||||||
|
return h[last]
|
||||||
|
}
|
||||||
|
|
||||||
|
// BellmanFord finds shortest paths from a start node in a weighted directed
|
||||||
|
// graph using the Bellman-Ford-Moore algorithm.
|
||||||
|
//
|
||||||
|
// WeightFunc w must translate arc labels to arc weights.
|
||||||
|
// Negative arc weights are allowed but not negative cycles.
|
||||||
|
// Loops and parallel arcs are allowed.
|
||||||
|
//
|
||||||
|
// If the algorithm completes without encountering a negative cycle the method
|
||||||
|
// returns shortest paths encoded in a FromList, labels and path distances
|
||||||
|
// indexed by node, and return value end = -1.
|
||||||
|
//
|
||||||
|
// If it encounters a negative cycle reachable from start it returns end >= 0.
|
||||||
|
// In this case the cycle can be obtained by calling f.BellmanFordCycle(end).
|
||||||
|
//
|
||||||
|
// Negative cycles are only detected when reachable from start. A negative
|
||||||
|
// cycle not reachable from start will not prevent the algorithm from finding
|
||||||
|
// shortest paths from start.
|
||||||
|
//
|
||||||
|
// See also NegativeCycle to find a cycle anywhere in the graph, see
|
||||||
|
// NegativeCycles for enumerating all negative cycles, and see
|
||||||
|
// HasNegativeCycle for lighter-weight negative cycle detection,
|
||||||
|
func (g LabeledDirected) BellmanFord(w WeightFunc, start NI) (f FromList, labels []LI, dist []float64, end NI) {
|
||||||
|
a := g.LabeledAdjacencyList
|
||||||
|
f = NewFromList(len(a))
|
||||||
|
labels = make([]LI, len(a))
|
||||||
|
dist = make([]float64, len(a))
|
||||||
|
inf := math.Inf(1)
|
||||||
|
for i := range dist {
|
||||||
|
dist[i] = inf
|
||||||
|
}
|
||||||
|
rp := f.Paths
|
||||||
|
rp[start] = PathEnd{Len: 1, From: -1}
|
||||||
|
dist[start] = 0
|
||||||
|
for _ = range a[1:] {
|
||||||
|
imp := false
|
||||||
|
for from, nbs := range a {
|
||||||
|
fp := &rp[from]
|
||||||
|
d1 := dist[from]
|
||||||
|
for _, nb := range nbs {
|
||||||
|
d2 := d1 + w(nb.Label)
|
||||||
|
to := &rp[nb.To]
|
||||||
|
// TODO improve to break ties
|
||||||
|
if fp.Len > 0 && d2 < dist[nb.To] {
|
||||||
|
*to = PathEnd{From: NI(from), Len: fp.Len + 1}
|
||||||
|
labels[nb.To] = nb.Label
|
||||||
|
dist[nb.To] = d2
|
||||||
|
imp = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !imp {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for from, nbs := range a {
|
||||||
|
d1 := dist[from]
|
||||||
|
for _, nb := range nbs {
|
||||||
|
if d1+w(nb.Label) < dist[nb.To] {
|
||||||
|
// return nb as end of a path with negative cycle at root
|
||||||
|
return f, labels, dist, NI(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f, labels, dist, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// BellmanFordCycle decodes a negative cycle detected by BellmanFord.
|
||||||
|
//
|
||||||
|
// Receiver f and argument end must be results returned from BellmanFord.
|
||||||
|
func (f FromList) BellmanFordCycle(end NI) (c []NI) {
|
||||||
|
p := f.Paths
|
||||||
|
b := bits.New(len(p))
|
||||||
|
for b.Bit(int(end)) == 0 {
|
||||||
|
b.SetBit(int(end), 1)
|
||||||
|
end = p[end].From
|
||||||
|
}
|
||||||
|
for b.Bit(int(end)) == 1 {
|
||||||
|
c = append(c, end)
|
||||||
|
b.SetBit(int(end), 0)
|
||||||
|
end = p[end].From
|
||||||
|
}
|
||||||
|
for i, j := 0, len(c)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
c[i], c[j] = c[j], c[i]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasNegativeCycle returns true if the graph contains any negative cycle.
|
||||||
|
//
|
||||||
|
// HasNegativeCycle uses a Bellman-Ford-like algorithm, but finds negative
|
||||||
|
// cycles anywhere in the graph. Also path information is not computed,
|
||||||
|
// reducing memory use somewhat compared to BellmanFord.
|
||||||
|
//
|
||||||
|
// See also NegativeCycle to obtain the cycle, see NegativeCycles for
|
||||||
|
// enumerating all negative cycles, and see BellmanFord for single source
|
||||||
|
// shortest path searches with negative cycle detection.
|
||||||
|
func (g LabeledDirected) HasNegativeCycle(w WeightFunc) bool {
|
||||||
|
a := g.LabeledAdjacencyList
|
||||||
|
dist := make([]float64, len(a))
|
||||||
|
for _ = range a[1:] {
|
||||||
|
imp := false
|
||||||
|
for from, nbs := range a {
|
||||||
|
d1 := dist[from]
|
||||||
|
for _, nb := range nbs {
|
||||||
|
d2 := d1 + w(nb.Label)
|
||||||
|
if d2 < dist[nb.To] {
|
||||||
|
dist[nb.To] = d2
|
||||||
|
imp = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !imp {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for from, nbs := range a {
|
||||||
|
d1 := dist[from]
|
||||||
|
for _, nb := range nbs {
|
||||||
|
if d1+w(nb.Label) < dist[nb.To] {
|
||||||
|
return true // negative cycle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NegativeCycle finds a negative cycle if one exists.
|
||||||
|
//
|
||||||
|
// NegativeCycle uses a Bellman-Ford-like algorithm, but finds negative
|
||||||
|
// cycles anywhere in the graph. If a negative cycle exists, one will be
|
||||||
|
// returned. The result is nil if no negative cycle exists.
|
||||||
|
//
|
||||||
|
// See also NegativeCycles for enumerating all negative cycles, see
|
||||||
|
// HasNegativeCycle for lighter-weight cycle detection, and see
|
||||||
|
// BellmanFord for single source shortest paths, also with negative cycle
|
||||||
|
// detection.
|
||||||
|
func (g LabeledDirected) NegativeCycle(w WeightFunc) (c []Half) {
|
||||||
|
a := g.LabeledAdjacencyList
|
||||||
|
f := NewFromList(len(a))
|
||||||
|
p := f.Paths
|
||||||
|
for n := range p {
|
||||||
|
p[n] = PathEnd{From: -1, Len: 1}
|
||||||
|
}
|
||||||
|
labels := make([]LI, len(a))
|
||||||
|
dist := make([]float64, len(a))
|
||||||
|
for _ = range a {
|
||||||
|
imp := false
|
||||||
|
for from, nbs := range a {
|
||||||
|
fp := &p[from]
|
||||||
|
d1 := dist[from]
|
||||||
|
for _, nb := range nbs {
|
||||||
|
d2 := d1 + w(nb.Label)
|
||||||
|
to := &p[nb.To]
|
||||||
|
if fp.Len > 0 && d2 < dist[nb.To] {
|
||||||
|
*to = PathEnd{From: NI(from), Len: fp.Len + 1}
|
||||||
|
labels[nb.To] = nb.Label
|
||||||
|
dist[nb.To] = d2
|
||||||
|
imp = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !imp {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vis := bits.New(len(a))
|
||||||
|
a:
|
||||||
|
for n := range a {
|
||||||
|
end := n
|
||||||
|
b := bits.New(len(a))
|
||||||
|
for b.Bit(end) == 0 {
|
||||||
|
if vis.Bit(end) == 1 {
|
||||||
|
continue a
|
||||||
|
}
|
||||||
|
vis.SetBit(end, 1)
|
||||||
|
b.SetBit(end, 1)
|
||||||
|
end = int(p[end].From)
|
||||||
|
if end < 0 {
|
||||||
|
continue a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for b.Bit(end) == 1 {
|
||||||
|
c = append(c, Half{NI(end), labels[end]})
|
||||||
|
b.SetBit(end, 0)
|
||||||
|
end = int(p[end].From)
|
||||||
|
}
|
||||||
|
for i, j := 0, len(c)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
c[i], c[j] = c[j], c[i]
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil // no negative cycle
|
||||||
|
}
|
||||||
|
|
||||||
|
// DAGMinDistPath finds a single shortest path.
|
||||||
|
//
|
||||||
|
// Shortest means minimum sum of arc weights.
|
||||||
|
//
|
||||||
|
// Returned is the path and distance as returned by FromList.PathTo.
|
||||||
|
//
|
||||||
|
// This is a convenience method. See DAGOptimalPaths for more options.
|
||||||
|
func (g LabeledDirected) DAGMinDistPath(start, end NI, w WeightFunc) (LabeledPath, float64, error) {
|
||||||
|
return g.dagPath(start, end, w, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DAGMaxDistPath finds a single longest path.
|
||||||
|
//
|
||||||
|
// Longest means maximum sum of arc weights.
|
||||||
|
//
|
||||||
|
// Returned is the path and distance as returned by FromList.PathTo.
|
||||||
|
//
|
||||||
|
// This is a convenience method. See DAGOptimalPaths for more options.
|
||||||
|
func (g LabeledDirected) DAGMaxDistPath(start, end NI, w WeightFunc) (LabeledPath, float64, error) {
|
||||||
|
return g.dagPath(start, end, w, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g LabeledDirected) dagPath(start, end NI, w WeightFunc, longest bool) (LabeledPath, float64, error) {
|
||||||
|
o, _ := g.Topological()
|
||||||
|
if o == nil {
|
||||||
|
return LabeledPath{}, 0, fmt.Errorf("not a DAG")
|
||||||
|
}
|
||||||
|
f, labels, dist, _ := g.DAGOptimalPaths(start, end, o, w, longest)
|
||||||
|
if f.Paths[end].Len == 0 {
|
||||||
|
return LabeledPath{}, 0, fmt.Errorf("no path from %d to %d", start, end)
|
||||||
|
}
|
||||||
|
return f.PathToLabeled(end, labels, nil), dist[end], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DAGOptimalPaths finds either longest or shortest distance paths in a
|
||||||
|
// directed acyclic graph.
|
||||||
|
//
|
||||||
|
// Path distance is the sum of arc weights on the path.
|
||||||
|
// Negative arc weights are allowed.
|
||||||
|
// Where multiple paths exist with the same distance, the path length
|
||||||
|
// (number of nodes) is used as a tie breaker.
|
||||||
|
//
|
||||||
|
// Receiver g must be a directed acyclic graph. Argument o must be either nil
|
||||||
|
// or a topological ordering of g. If nil, a topologcal ordering is
|
||||||
|
// computed internally. If longest is true, an optimal path is a longest
|
||||||
|
// distance path. Otherwise it is a shortest distance path.
|
||||||
|
//
|
||||||
|
// Argument start is the start node for paths, end is the end node. If end
|
||||||
|
// is a valid node number, the method returns as soon as the optimal path
|
||||||
|
// to end is found. If end is -1, all optimal paths from start are found.
|
||||||
|
//
|
||||||
|
// Paths and path distances are encoded in the returned FromList, labels,
|
||||||
|
// and dist slices. The number of nodes reached is returned as nReached.
|
||||||
|
func (g LabeledDirected) DAGOptimalPaths(start, end NI, ordering []NI, w WeightFunc, longest bool) (f FromList, labels []LI, dist []float64, nReached int) {
|
||||||
|
a := g.LabeledAdjacencyList
|
||||||
|
f = NewFromList(len(a))
|
||||||
|
f.Leaves = bits.New(len(a))
|
||||||
|
labels = make([]LI, len(a))
|
||||||
|
dist = make([]float64, len(a))
|
||||||
|
if ordering == nil {
|
||||||
|
ordering, _ = g.Topological()
|
||||||
|
}
|
||||||
|
// search ordering for start
|
||||||
|
o := 0
|
||||||
|
for ordering[o] != start {
|
||||||
|
o++
|
||||||
|
}
|
||||||
|
var fBetter func(cand, ext float64) bool
|
||||||
|
var iBetter func(cand, ext int) bool
|
||||||
|
if longest {
|
||||||
|
fBetter = func(cand, ext float64) bool { return cand > ext }
|
||||||
|
iBetter = func(cand, ext int) bool { return cand > ext }
|
||||||
|
} else {
|
||||||
|
fBetter = func(cand, ext float64) bool { return cand < ext }
|
||||||
|
iBetter = func(cand, ext int) bool { return cand < ext }
|
||||||
|
}
|
||||||
|
p := f.Paths
|
||||||
|
p[start] = PathEnd{From: -1, Len: 1}
|
||||||
|
f.MaxLen = 1
|
||||||
|
leaves := &f.Leaves
|
||||||
|
leaves.SetBit(int(start), 1)
|
||||||
|
nReached = 1
|
||||||
|
for n := start; n != end; n = ordering[o] {
|
||||||
|
if p[n].Len > 0 && len(a[n]) > 0 {
|
||||||
|
nDist := dist[n]
|
||||||
|
candLen := p[n].Len + 1 // len for any candidate arc followed from n
|
||||||
|
for _, to := range a[n] {
|
||||||
|
leaves.SetBit(int(to.To), 1)
|
||||||
|
candDist := nDist + w(to.Label)
|
||||||
|
switch {
|
||||||
|
case p[to.To].Len == 0: // first path to node to.To
|
||||||
|
nReached++
|
||||||
|
case fBetter(candDist, dist[to.To]): // better distance
|
||||||
|
case candDist == dist[to.To] && iBetter(candLen, p[to.To].Len): // same distance but better path length
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dist[to.To] = candDist
|
||||||
|
p[to.To] = PathEnd{From: n, Len: candLen}
|
||||||
|
labels[to.To] = to.Label
|
||||||
|
if candLen > f.MaxLen {
|
||||||
|
f.MaxLen = candLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
leaves.SetBit(int(n), 0)
|
||||||
|
}
|
||||||
|
o++
|
||||||
|
if o == len(ordering) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dijkstra finds shortest paths by Dijkstra's algorithm.
|
||||||
|
//
|
||||||
|
// Shortest means shortest distance where distance is the
|
||||||
|
// sum of arc weights. Where multiple paths exist with the same distance,
|
||||||
|
// a path with the minimum number of nodes is returned.
|
||||||
|
//
|
||||||
|
// As usual for Dijkstra's algorithm, arc weights must be non-negative.
|
||||||
|
// Graphs may be directed or undirected. Loops and parallel arcs are
|
||||||
|
// allowed.
|
||||||
|
//
|
||||||
|
// Paths and path distances are encoded in the returned FromList and dist
|
||||||
|
// slice. Returned labels are the labels of arcs followed to each node.
|
||||||
|
// The number of nodes reached is returned as nReached.
|
||||||
|
func (g LabeledAdjacencyList) Dijkstra(start, end NI, w WeightFunc) (f FromList, labels []LI, dist []float64, nReached int) {
|
||||||
|
r := make([]tentResult, len(g))
|
||||||
|
for i := range r {
|
||||||
|
r[i].nx = NI(i)
|
||||||
|
}
|
||||||
|
f = NewFromList(len(g))
|
||||||
|
labels = make([]LI, len(g))
|
||||||
|
dist = make([]float64, len(g))
|
||||||
|
current := start
|
||||||
|
rp := f.Paths
|
||||||
|
rp[current] = PathEnd{Len: 1, From: -1} // path length at start is 1 node
|
||||||
|
cr := &r[current]
|
||||||
|
cr.dist = 0 // distance at start is 0.
|
||||||
|
cr.done = true // mark start done. it skips the heap.
|
||||||
|
nDone := 1 // accumulated for a return value
|
||||||
|
var t tent
|
||||||
|
for current != end {
|
||||||
|
nextLen := rp[current].Len + 1
|
||||||
|
for _, nb := range g[current] {
|
||||||
|
// d.arcVis++
|
||||||
|
hr := &r[nb.To]
|
||||||
|
if hr.done {
|
||||||
|
continue // skip nodes already done
|
||||||
|
}
|
||||||
|
dist := cr.dist + w(nb.Label)
|
||||||
|
vl := rp[nb.To].Len
|
||||||
|
visited := vl > 0
|
||||||
|
if visited {
|
||||||
|
if dist > hr.dist {
|
||||||
|
continue // distance is worse
|
||||||
|
}
|
||||||
|
// tie breaker is a nice touch and doesn't seem to
|
||||||
|
// impact performance much.
|
||||||
|
if dist == hr.dist && nextLen >= vl {
|
||||||
|
continue // distance same, but number of nodes is no better
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the path through current to this node is shortest so far.
|
||||||
|
// record new path data for this node and update tentative set.
|
||||||
|
hr.dist = dist
|
||||||
|
rp[nb.To].Len = nextLen
|
||||||
|
rp[nb.To].From = current
|
||||||
|
labels[nb.To] = nb.Label
|
||||||
|
if visited {
|
||||||
|
heap.Fix(&t, hr.fx)
|
||||||
|
} else {
|
||||||
|
heap.Push(&t, hr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//d.ndVis++
|
||||||
|
if len(t) == 0 {
|
||||||
|
// no more reachable nodes. AllPaths normal return
|
||||||
|
return f, labels, dist, nDone
|
||||||
|
}
|
||||||
|
// new current is node with smallest tentative distance
|
||||||
|
cr = heap.Pop(&t).(*tentResult)
|
||||||
|
cr.done = true
|
||||||
|
nDone++
|
||||||
|
current = cr.nx
|
||||||
|
dist[current] = cr.dist // store final distance
|
||||||
|
}
|
||||||
|
// normal return for single shortest path search
|
||||||
|
return f, labels, dist, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// DijkstraPath finds a single shortest path.
|
||||||
|
//
|
||||||
|
// Returned is the path as returned by FromList.LabeledPathTo and the total
|
||||||
|
// path distance.
|
||||||
|
func (g LabeledAdjacencyList) DijkstraPath(start, end NI, w WeightFunc) (LabeledPath, float64) {
|
||||||
|
f, labels, dist, _ := g.Dijkstra(start, end, w)
|
||||||
|
return f.PathToLabeled(end, labels, nil), dist[end]
|
||||||
|
}
|
||||||
|
|
||||||
|
// tent implements container/heap
|
||||||
|
func (t tent) Len() int { return len(t) }
|
||||||
|
func (t tent) Less(i, j int) bool { return t[i].dist < t[j].dist }
|
||||||
|
func (t tent) Swap(i, j int) {
|
||||||
|
t[i], t[j] = t[j], t[i]
|
||||||
|
t[i].fx = i
|
||||||
|
t[j].fx = j
|
||||||
|
}
|
||||||
|
func (s *tent) Push(x interface{}) {
|
||||||
|
nd := x.(*tentResult)
|
||||||
|
nd.fx = len(*s)
|
||||||
|
*s = append(*s, nd)
|
||||||
|
}
|
||||||
|
func (s *tent) Pop() interface{} {
|
||||||
|
t := *s
|
||||||
|
last := len(t) - 1
|
||||||
|
*s = t[:last]
|
||||||
|
return t[last]
|
||||||
|
}
|
||||||
|
|
||||||
|
type tentResult struct {
|
||||||
|
dist float64 // tentative distance, sum of arc weights
|
||||||
|
nx NI // slice index, "node id"
|
||||||
|
fx int // heap.Fix index
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type tent []*tentResult
|
|
@ -0,0 +1,817 @@
|
||||||
|
// Copyright 2014 Sonia Keys
|
||||||
|
// License MIT: http://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
package graph
|
||||||
|
|
||||||
|
// undir.go has methods specific to undirected graphs, Undirected and
|
||||||
|
// LabeledUndirected.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/soniakeys/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddEdge adds an edge to a graph.
|
||||||
|
//
|
||||||
|
// It can be useful for constructing undirected graphs.
|
||||||
|
//
|
||||||
|
// When n1 and n2 are distinct, it adds the arc n1->n2 and the reciprocal
|
||||||
|
// n2->n1. When n1 and n2 are the same, it adds a single arc loop.
|
||||||
|
//
|
||||||
|
// The pointer receiver allows the method to expand the graph as needed
|
||||||
|
// to include the values n1 and n2. If n1 or n2 happen to be greater than
|
||||||
|
// len(*p) the method does not panic, but simply expands the graph.
|
||||||
|
//
|
||||||
|
// If you know or can compute the final graph order however, consider
|
||||||
|
// preallocating to avoid any overhead of expanding the graph.
|
||||||
|
// See second example, "More".
|
||||||
|
func (p *Undirected) AddEdge(n1, n2 NI) {
|
||||||
|
// Similar code in LabeledAdjacencyList.AddEdge.
|
||||||
|
|
||||||
|
// determine max of the two end points
|
||||||
|
max := n1
|
||||||
|
if n2 > max {
|
||||||
|
max = n2
|
||||||
|
}
|
||||||
|
// expand graph if needed, to include both
|
||||||
|
g := p.AdjacencyList
|
||||||
|
if int(max) >= len(g) {
|
||||||
|
p.AdjacencyList = make(AdjacencyList, max+1)
|
||||||
|
copy(p.AdjacencyList, g)
|
||||||
|
g = p.AdjacencyList
|
||||||
|
}
|
||||||
|
// create one half-arc,
|
||||||
|
g[n1] = append(g[n1], n2)
|
||||||
|
// and except for loops, create the reciprocal
|
||||||
|
if n1 != n2 {
|
||||||
|
g[n2] = append(g[n2], n1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveEdge removes a single edge between nodes n1 and n2.
|
||||||
|
//
|
||||||
|
// It removes reciprocal arcs in the case of distinct n1 and n2 or removes
|
||||||
|
// a single arc loop in the case of n1 == n2.
|
||||||
|
//
|
||||||
|
// Returns true if the specified edge is found and successfully removed,
|
||||||
|
// false if the edge does not exist.
|
||||||
|
func (g Undirected) RemoveEdge(n1, n2 NI) (ok bool) {
|
||||||
|
ok, x1, x2 := g.HasEdge(n1, n2)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a := g.AdjacencyList
|
||||||
|
to := a[n1]
|
||||||
|
last := len(to) - 1
|
||||||
|
to[x1] = to[last]
|
||||||
|
a[n1] = to[:last]
|
||||||
|
if n1 == n2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
to = a[n2]
|
||||||
|
last = len(to) - 1
|
||||||
|
to[x2] = to[last]
|
||||||
|
a[n2] = to[:last]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArcDensity returns density for a simple directed graph.
|
||||||
|
//
|
||||||
|
// Parameter n is order, or number of nodes of a simple directed graph.
|
||||||
|
// Parameter a is the arc size, or number of directed arcs.
|
||||||
|
//
|
||||||
|
// Returned density is the fraction `a` over the total possible number of arcs
|
||||||
|
// or a / (n * (n-1)).
|
||||||
|
//
|
||||||
|
// See also Density for density of a simple undirected graph.
|
||||||
|
//
|
||||||
|
// See also the corresponding methods AdjacencyList.ArcDensity and
|
||||||
|
// LabeledAdjacencyList.ArcDensity.
|
||||||
|
func ArcDensity(n, a int) float64 {
|
||||||
|
return float64(a) / (float64(n) * float64(n-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Density returns density for a simple undirected graph.
|
||||||
|
//
|
||||||
|
// Parameter n is order, or number of nodes of a simple undirected graph.
|
||||||
|
// Parameter m is the size, or number of undirected edges.
|
||||||
|
//
|
||||||
|
// Returned density is the fraction m over the total possible number of edges
|
||||||
|
// or m / ((n * (n-1))/2).
|
||||||
|
//
|
||||||
|
// See also ArcDensity for simple directed graphs.
|
||||||
|
//
|
||||||
|
// See also the corresponding methods AdjacencyList.Density and
|
||||||
|
// LabeledAdjacencyList.Density.
|
||||||
|
func Density(n, m int) float64 {
|
||||||
|
return float64(m) * 2 / (float64(n) * float64(n-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// An EdgeVisitor is an argument to some traversal methods.
|
||||||
|
//
|
||||||
|
// Traversal methods call the visitor function for each edge visited.
|
||||||
|
// Argument e is the edge being visited.
|
||||||
|
type EdgeVisitor func(e Edge)
|
||||||
|
|
||||||
|
// Edges iterates over the edges of an undirected graph.
|
||||||
|
//
|
||||||
|
// Edge visitor v is called for each edge of the graph. That is, it is called
|
||||||
|
// once for each reciprocal arc pair and once for each loop.
|
||||||
|
//
|
||||||
|
// See also LabeledUndirected.Edges for a labeled version.
|
||||||
|
// See also Undirected.SimpleEdges for a version that emits only the simple
|
||||||
|
// subgraph.
|
||||||
|
func (g Undirected) Edges(v EdgeVisitor) {
|
||||||
|
a := g.AdjacencyList
|
||||||
|
unpaired := make(AdjacencyList, len(a))
|
||||||
|
for fr, to := range a {
|
||||||
|
arc: // for each arc in a
|
||||||
|
for _, to := range to {
|
||||||
|
if to == NI(fr) {
|
||||||
|
v(Edge{NI(fr), to}) // output loop
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// search unpaired arcs
|
||||||
|
ut := unpaired[to]
|
||||||
|
for i, u := range ut {
|
||||||
|
if u == NI(fr) { // found reciprocal
|
||||||
|
v(Edge{u, to}) // output edge
|
||||||
|
last := len(ut) - 1
|
||||||
|
ut[i] = ut[last]
|
||||||
|
unpaired[to] = ut[:last]
|
||||||
|
continue arc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// reciprocal not found
|
||||||
|
unpaired[fr] = append(unpaired[fr], to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// undefined behavior is that unpaired arcs are silently ignored.
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromList builds a forest with a tree spanning each connected component.
|
||||||
|
//
|
||||||
|
// For each component a root is chosen and spanning is done with the method
|
||||||
|
// Undirected.SpanTree, and so is breadth-first. Returned is a FromList with
|
||||||
|
// all spanned trees, a list of roots chosen, and a bool indicating if the
|
||||||
|
// receiver graph g was found to be a simple graph connected as a forest.
|
||||||
|
// Any cycles, loops, or parallel edges in any component will cause
|
||||||
|
// simpleForest to be false, but FromList f will still be populated with
|
||||||
|
// a valid and complete spanning forest.
|
||||||
|
func (g Undirected) FromList() (f FromList, roots []NI, simpleForest bool) {
|
||||||
|
p := make([]PathEnd, g.Order())
|
||||||
|
for i := range p {
|
||||||
|
p[i].From = -1
|
||||||
|
}
|
||||||
|
f.Paths = p
|
||||||
|
simpleForest = true
|
||||||
|
ts := 0
|
||||||
|
for n := range g.AdjacencyList {
|
||||||
|
if p[n].From >= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
roots = append(roots, NI(n))
|
||||||
|
ns, st := g.SpanTree(NI(n), &f)
|
||||||
|
if !st {
|
||||||
|
simpleForest = false
|
||||||
|
}
|
||||||
|
ts += ns
|
||||||
|
if ts == len(p) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasEdge returns true if g has any edge between nodes n1 and n2.
|
||||||
|
//
|
||||||
|
// Also returned are indexes x1 and x2 such that g[n1][x1] == n2
|
||||||
|
// and g[n2][x2] == n1. If no edge between n1 and n2 is present HasArc
|
||||||
|
// returns `has` == false.
|
||||||
|
//
|
||||||
|
// See also HasArc. If you are interested only in the boolean result and
|
||||||
|
// g is a well formed (passes IsUndirected) then HasArc is an adequate test.
|
||||||
|
func (g Undirected) HasEdge(n1, n2 NI) (has bool, x1, x2 int) {
|
||||||
|
if has, x1 = g.HasArc(n1, n2); !has {
|
||||||
|
return has, x1, x1
|
||||||
|
}
|
||||||
|
has, x2 = g.HasArc(n2, n1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleEdges iterates over the edges of the simple subgraph of an undirected
|
||||||
|
// graph.
|
||||||
|
//
|
||||||
|
// Edge visitor v is called for each pair of distinct nodes that is connected
|
||||||
|
// with an edge. That is, loops are ignored and parallel edges are reduced to
|
||||||
|
// a single edge.
|
||||||
|
//
|
||||||
|
// See also Undirected.Edges for a version that emits all edges.
|
||||||
|
func (g Undirected) SimpleEdges(v EdgeVisitor) {
|
||||||
|
for fr, to := range g.AdjacencyList {
|
||||||
|
e := bits.New(len(g.AdjacencyList))
|
||||||
|
for _, to := range to {
|
||||||
|
if to > NI(fr) && e.Bit(int(to)) == 0 {
|
||||||
|
e.SetBit(int(to), 1)
|
||||||
|
v(Edge{NI(fr), to})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// undefined behavior is that unpaired arcs may or may not be emitted.
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpanTree builds a tree spanning a connected component.
|
||||||
|
//
|
||||||
|
// The component is spanned by breadth-first search from the given root.
|
||||||
|
// The resulting spanning tree in stored a FromList.
|
||||||
|
//
|
||||||
|
// If FromList.Paths is not the same length as g, it is allocated and
|
||||||
|
// initialized. This allows a zero value FromList to be passed as f.
|
||||||
|
// If FromList.Paths is the same length as g, it is used as is and is not
|
||||||
|
// reinitialized. This allows multiple trees to be spanned in the same
|
||||||
|
// FromList with successive calls.
|
||||||
|
//
|
||||||
|
// For nodes spanned, the Path member of the returned FromList is populated
|
||||||
|
// with both From and Len values. The MaxLen member will be updated but
|
||||||
|
// not Leaves.
|
||||||
|
//
|
||||||
|
// Returned is the number of nodes spanned, which will be the number of nodes
|
||||||
|
// in the component, and a bool indicating if the component was found to be a
|
||||||
|
// simply connected unrooted tree in the receiver graph g. Any cycles, loops,
|
||||||
|
// or parallel edges in the component will cause simpleTree to be false, but
|
||||||
|
// FromList f will still be populated with a valid and complete spanning tree.
|
||||||
|
func (g Undirected) SpanTree(root NI, f *FromList) (nSpanned int, simpleTree bool) {
|
||||||
|
a := g.AdjacencyList
|
||||||
|
p := f.Paths
|
||||||
|
if len(p) != len(a) {
|
||||||
|
p = make([]PathEnd, len(a))
|
||||||
|
for i := range p {
|
||||||
|
p[i].From = -1
|
||||||
|
}
|
||||||
|
f.Paths = p
|
||||||
|
}
|
||||||
|
simpleTree = true
|
||||||
|
p[root] = PathEnd{From: -1, Len: 1}
|
||||||
|
type arc struct {
|
||||||
|
from NI
|
||||||
|
half NI
|
||||||
|
}
|
||||||
|
var next []arc
|
||||||
|
frontier := []arc{{-1, root}}
|
||||||
|
for len(frontier) > 0 {
|
||||||
|
for _, fa := range frontier { // fa frontier arc
|
||||||
|
nSpanned++
|
||||||
|
l := p[fa.half].Len + 1
|
||||||
|
for _, to := range a[fa.half] {
|
||||||
|
if to == fa.from {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if p[to].Len > 0 {
|
||||||
|
simpleTree = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p[to] = PathEnd{From: fa.half, Len: l}
|
||||||
|
if l > f.MaxLen {
|
||||||
|
f.MaxLen = l
|
||||||
|
}
|
||||||
|
next = append(next, arc{fa.half, to})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frontier, next = next, frontier[:0]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TarjanBiconnectedComponents decomposes a graph into maximal biconnected
|
||||||
|
// components, components for which if any node were removed the component
|
||||||
|
// would remain connected.
|
||||||
|
//
|
||||||
|
// The receiver g must be a simple graph. The method calls the emit argument
|
||||||
|
// for each component identified, as long as emit returns true. If emit
|
||||||
|
// returns false, TarjanBiconnectedComponents returns immediately.
|
||||||
|
//
|
||||||
|
// See also the eqivalent labeled TarjanBiconnectedComponents.
|
||||||
|
func (g Undirected) TarjanBiconnectedComponents(emit func([]Edge) bool) {
|
||||||
|
// Implemented closely to pseudocode in "Depth-first search and linear
|
||||||
|
// graph algorithms", Robert Tarjan, SIAM J. Comput. Vol. 1, No. 2,
|
||||||
|
// June 1972.
|
||||||
|
//
|
||||||
|
// Note Tarjan's "adjacency structure" is graph.AdjacencyList,
|
||||||
|
// His "adjacency list" is an element of a graph.AdjacencyList, also
|
||||||
|
// termed a "to-list", "neighbor list", or "child list."
|
||||||
|
a := g.AdjacencyList
|
||||||
|
number := make([]int, len(a))
|
||||||
|
lowpt := make([]int, len(a))
|
||||||
|
var stack []Edge
|
||||||
|
var i int
|
||||||
|
var biconnect func(NI, NI) bool
|
||||||
|
biconnect = func(v, u NI) bool {
|
||||||
|
i++
|
||||||
|
number[v] = i
|
||||||
|
lowpt[v] = i
|
||||||
|
for _, w := range a[v] {
|
||||||
|
if number[w] == 0 {
|
||||||
|
stack = append(stack, Edge{v, w})
|
||||||
|
if !biconnect(w, v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lowpt[w] < lowpt[v] {
|
||||||
|
lowpt[v] = lowpt[w]
|
||||||
|
}
|
||||||
|
if lowpt[w] >= number[v] {
|
||||||
|
var bcc []Edge
|
||||||
|
top := len(stack) - 1
|
||||||
|
for number[stack[top].N1] >= number[w] {
|
||||||
|
bcc = append(bcc, stack[top])
|
||||||
|
stack = stack[:top]
|
||||||
|
top--
|
||||||
|
}
|
||||||
|
bcc = append(bcc, stack[top])
|
||||||
|
stack = stack[:top]
|
||||||
|
top--
|
||||||
|
if !emit(bcc) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if number[w] < number[v] && w != u {
|
||||||
|
stack = append(stack, Edge{v, w})
|
||||||
|
if number[w] < lowpt[v] {
|
||||||
|
lowpt[v] = number[w]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for w := range a {
|
||||||
|
if number[w] == 0 && !biconnect(NI(w), -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g Undirected) BlockCut(block func([]Edge) bool, cut func(NI) bool, isolated func(NI) bool) {
|
||||||
|
a := g.AdjacencyList
|
||||||
|
number := make([]int, len(a))
|
||||||
|
lowpt := make([]int, len(a))
|
||||||
|
var stack []Edge
|
||||||
|
var i, rc int
|
||||||
|
var biconnect func(NI, NI) bool
|
||||||
|
biconnect = func(v, u NI) bool {
|
||||||
|
i++
|
||||||
|
number[v] = i
|
||||||
|
lowpt[v] = i
|
||||||
|
for _, w := range a[v] {
|
||||||
|
if number[w] == 0 {
|
||||||
|
if u < 0 {
|
||||||
|
rc++
|
||||||
|
}
|
||||||
|
stack = append(stack, Edge{v, w})
|
||||||
|
if !biconnect(w, v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lowpt[w] < lowpt[v] {
|
||||||
|
lowpt[v] = lowpt[w]
|
||||||
|
}
|
||||||
|
if lowpt[w] >= number[v] {
|
||||||
|
if u >= 0 && !cut(v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var bcc []Edge
|
||||||
|
top := len(stack) - 1
|
||||||
|
for number[stack[top].N1] >= number[w] {
|
||||||
|
bcc = append(bcc, stack[top])
|
||||||
|
stack = stack[:top]
|
||||||
|
top--
|
||||||
|
}
|
||||||
|
bcc = append(bcc, stack[top])
|
||||||
|
stack = stack[:top]
|
||||||
|
top--
|
||||||
|
if !block(bcc) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if number[w] < number[v] && w != u {
|
||||||
|
stack = append(stack, Edge{v, w})
|
||||||
|
if number[w] < lowpt[v] {
|
||||||
|
lowpt[v] = number[w]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if u < 0 && rc > 1 {
|
||||||
|
return cut(v)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for w := range a {
|
||||||
|
if number[w] > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(a[w]) == 0 {
|
||||||
|
if !isolated(NI(w)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rc = 0
|
||||||
|
if !biconnect(NI(w), -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddEdge adds an edge to a labeled graph.
|
||||||
|
//
|
||||||
|
// It can be useful for constructing undirected graphs.
|
||||||
|
//
|
||||||
|
// When n1 and n2 are distinct, it adds the arc n1->n2 and the reciprocal
|
||||||
|
// n2->n1. When n1 and n2 are the same, it adds a single arc loop.
|
||||||
|
//
|
||||||
|
// If the edge already exists in *p, a parallel edge is added.
|
||||||
|
//
|
||||||
|
// The pointer receiver allows the method to expand the graph as needed
|
||||||
|
// to include the values n1 and n2. If n1 or n2 happen to be greater than
|
||||||
|
// len(*p) the method does not panic, but simply expands the graph.
|
||||||
|
func (p *LabeledUndirected) AddEdge(e Edge, l LI) {
|
||||||
|
// Similar code in AdjacencyList.AddEdge.
|
||||||
|
|
||||||
|
// determine max of the two end points
|
||||||
|
max := e.N1
|
||||||
|
if e.N2 > max {
|
||||||
|
max = e.N2
|
||||||
|
}
|
||||||
|
// expand graph if needed, to include both
|
||||||
|
g := p.LabeledAdjacencyList
|
||||||
|
if max >= NI(len(g)) {
|
||||||
|
p.LabeledAdjacencyList = make(LabeledAdjacencyList, max+1)
|
||||||
|
copy(p.LabeledAdjacencyList, g)
|
||||||
|
g = p.LabeledAdjacencyList
|
||||||
|
}
|
||||||
|
// create one half-arc,
|
||||||
|
g[e.N1] = append(g[e.N1], Half{To: e.N2, Label: l})
|
||||||
|
// and except for loops, create the reciprocal
|
||||||
|
if e.N1 != e.N2 {
|
||||||
|
g[e.N2] = append(g[e.N2], Half{To: e.N1, Label: l})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A LabeledEdgeVisitor is an argument to some traversal methods.
|
||||||
|
//
|
||||||
|
// Traversal methods call the visitor function for each edge visited.
|
||||||
|
// Argument e is the edge being visited.
|
||||||
|
type LabeledEdgeVisitor func(e LabeledEdge)
|
||||||
|
|
||||||
|
// Edges iterates over the edges of a labeled undirected graph.
|
||||||
|
//
|
||||||
|
// Edge visitor v is called for each edge of the graph. That is, it is called
|
||||||
|
// once for each reciprocal arc pair and once for each loop.
|
||||||
|
//
|
||||||
|
// See also Undirected.Edges for an unlabeled version.
|
||||||
|
// See also the more simplistic LabeledAdjacencyList.ArcsAsEdges.
|
||||||
|
func (g LabeledUndirected) Edges(v LabeledEdgeVisitor) {
|
||||||
|
// similar code in LabeledAdjacencyList.InUndirected
|
||||||
|
a := g.LabeledAdjacencyList
|
||||||
|
unpaired := make(LabeledAdjacencyList, len(a))
|
||||||
|
for fr, to := range a {
|
||||||
|
arc: // for each arc in a
|
||||||
|
for _, to := range to {
|
||||||
|
if to.To == NI(fr) {
|
||||||
|
v(LabeledEdge{Edge{NI(fr), to.To}, to.Label}) // output loop
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// search unpaired arcs
|
||||||
|
ut := unpaired[to.To]
|
||||||
|
for i, u := range ut {
|
||||||
|
if u.To == NI(fr) && u.Label == to.Label { // found reciprocal
|
||||||
|
v(LabeledEdge{Edge{NI(fr), to.To}, to.Label}) // output edge
|
||||||
|
last := len(ut) - 1
|
||||||
|
ut[i] = ut[last]
|
||||||
|
unpaired[to.To] = ut[:last]
|
||||||
|
continue arc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// reciprocal not found
|
||||||
|
unpaired[fr] = append(unpaired[fr], to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromList builds a forest with a tree spanning each connected component in g.
|
||||||
|
//
|
||||||
|
// A root is chosen and spanning is done with the LabeledUndirected.SpanTree
|
||||||
|
// method, and so is breadth-first. Returned is a FromList with all spanned
|
||||||
|
// trees, labels corresponding to arcs in f,
|
||||||
|
// a list of roots chosen, and a bool indicating if the receiver graph g was
|
||||||
|
// found to be a simple graph connected as a forest. Any cycles, loops, or
|
||||||
|
// parallel edges in any component will cause simpleForest to be false, but
|
||||||
|
// FromList f will still be populated with a valid and complete spanning forest.
|
||||||
|
|
||||||
|
// FromList builds a forest with a tree spanning each connected component.
|
||||||
|
//
|
||||||
|
// For each component a root is chosen and spanning is done with the method
|
||||||
|
// Undirected.SpanTree, and so is breadth-first. Returned is a FromList with
|
||||||
|
// all spanned trees, labels corresponding to arcs in f, a list of roots
|
||||||
|
// chosen, and a bool indicating if the receiver graph g was found to be a
|
||||||
|
// simple graph connected as a forest. Any cycles, loops, or parallel edges
|
||||||
|
// in any component will cause simpleForest to be false, but FromList f will
|
||||||
|
// still be populated with a valid and complete spanning forest.
|
||||||
|
func (g LabeledUndirected) FromList() (f FromList, labels []LI, roots []NI, simpleForest bool) {
|
||||||
|
p := make([]PathEnd, g.Order())
|
||||||
|
for i := range p {
|
||||||
|
p[i].From = -1
|
||||||
|
}
|
||||||
|
f.Paths = p
|
||||||
|
labels = make([]LI, len(p))
|
||||||
|
simpleForest = true
|
||||||
|
ts := 0
|
||||||
|
for n := range g.LabeledAdjacencyList {
|
||||||
|
if p[n].From >= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
roots = append(roots, NI(n))
|
||||||
|
ns, st := g.SpanTree(NI(n), &f, labels)
|
||||||
|
if !st {
|
||||||
|
simpleForest = false
|
||||||
|
}
|
||||||
|
ts += ns
|
||||||
|
if ts == len(p) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpanTree builds a tree spanning a connected component.
|
||||||
|
//
|
||||||
|
// The component is spanned by breadth-first search from the given root.
|
||||||
|
// The resulting spanning tree in stored a FromList, and arc labels optionally
|
||||||
|
// stored in a slice.
|
||||||
|
//
|
||||||
|
// If FromList.Paths is not the same length as g, it is allocated and
|
||||||
|
// initialized. This allows a zero value FromList to be passed as f.
|
||||||
|
// If FromList.Paths is the same length as g, it is used as is and is not
|
||||||
|
// reinitialized. This allows multiple trees to be spanned in the same
|
||||||
|
// FromList with successive calls.
|
||||||
|
//
|
||||||
|
// For nodes spanned, the Path member of returned FromList f is populated
|
||||||
|
// populated with both From and Len values. The MaxLen member will be
|
||||||
|
// updated but not Leaves.
|
||||||
|
//
|
||||||
|
// The labels slice will be populated only if it is same length as g.
|
||||||
|
// Nil can be passed for example if labels are not needed.
|
||||||
|
//
|
||||||
|
// Returned is the number of nodes spanned, which will be the number of nodes
|
||||||
|
// in the component, and a bool indicating if the component was found to be a
|
||||||
|
// simply connected unrooted tree in the receiver graph g. Any cycles, loops,
|
||||||
|
// or parallel edges in the component will cause simpleTree to be false, but
|
||||||
|
// FromList f will still be populated with a valid and complete spanning tree.
|
||||||
|
func (g LabeledUndirected) SpanTree(root NI, f *FromList, labels []LI) (nSpanned int, simple bool) {
|
||||||
|
a := g.LabeledAdjacencyList
|
||||||
|
p := f.Paths
|
||||||
|
if len(p) != len(a) {
|
||||||
|
p = make([]PathEnd, len(a))
|
||||||
|
for i := range p {
|
||||||
|
p[i].From = -1
|
||||||
|
}
|
||||||
|
f.Paths = p
|
||||||
|
}
|
||||||
|
simple = true
|
||||||
|
p[root].Len = 1
|
||||||
|
type arc struct {
|
||||||
|
from NI
|
||||||
|
half Half
|
||||||
|
}
|
||||||
|
var next []arc
|
||||||
|
frontier := []arc{{-1, Half{root, -1}}}
|
||||||
|
for len(frontier) > 0 {
|
||||||
|
for _, fa := range frontier { // fa frontier arc
|
||||||
|
nSpanned++
|
||||||
|
l := p[fa.half.To].Len + 1
|
||||||
|
for _, to := range a[fa.half.To] {
|
||||||
|
if to.To == fa.from && to.Label == fa.half.Label {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if p[to.To].Len > 0 {
|
||||||
|
simple = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p[to.To] = PathEnd{From: fa.half.To, Len: l}
|
||||||
|
if len(labels) == len(p) {
|
||||||
|
labels[to.To] = to.Label
|
||||||
|
}
|
||||||
|
if l > f.MaxLen {
|
||||||
|
f.MaxLen = l
|
||||||
|
}
|
||||||
|
next = append(next, arc{fa.half.To, to})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frontier, next = next, frontier[:0]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasEdge returns true if g has any edge between nodes n1 and n2.
|
||||||
|
//
|
||||||
|
// Also returned are indexes x1 and x2 such that g[n1][x1] == Half{n2, l}
|
||||||
|
// and g[n2][x2] == {n1, l} for some label l. If no edge between n1 and n2
|
||||||
|
// exists, HasArc returns `has` == false.
|
||||||
|
//
|
||||||
|
// See also HasArc. If you are only interested in the boolean result then
|
||||||
|
// HasArc is an adequate test.
|
||||||
|
func (g LabeledUndirected) HasEdge(n1, n2 NI) (has bool, x1, x2 int) {
|
||||||
|
if has, x1 = g.HasArc(n1, n2); !has {
|
||||||
|
return has, x1, x1
|
||||||
|
}
|
||||||
|
has, x2 = g.HasArcLabel(n2, n1, g.LabeledAdjacencyList[n1][x1].Label)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasEdgeLabel returns true if g has any edge between nodes n1 and n2 with
|
||||||
|
// label l.
|
||||||
|
//
|
||||||
|
// Also returned are indexes x1 and x2 such that g[n1][x1] == Half{n2, l}
|
||||||
|
// and g[n2][x2] == Half{n1, l}. If no edge between n1 and n2 with label l
|
||||||
|
// is present HasArc returns `has` == false.
|
||||||
|
func (g LabeledUndirected) HasEdgeLabel(n1, n2 NI, l LI) (has bool, x1, x2 int) {
|
||||||
|
if has, x1 = g.HasArcLabel(n1, n2, l); !has {
|
||||||
|
return has, x1, x1
|
||||||
|
}
|
||||||
|
has, x2 = g.HasArcLabel(n2, n1, l)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveEdge removes a single edge between nodes n1 and n2.
|
||||||
|
//
|
||||||
|
// It removes reciprocal arcs in the case of distinct n1 and n2 or removes
|
||||||
|
// a single arc loop in the case of n1 == n2.
|
||||||
|
//
|
||||||
|
// If the specified edge is found and successfully removed, RemoveEdge returns
|
||||||
|
// true and the label of the edge removed. If no edge exists between n1 and n2,
|
||||||
|
// RemoveEdge returns false, 0.
|
||||||
|
func (g LabeledUndirected) RemoveEdge(n1, n2 NI) (ok bool, label LI) {
|
||||||
|
ok, x1, x2 := g.HasEdge(n1, n2)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a := g.LabeledAdjacencyList
|
||||||
|
to := a[n1]
|
||||||
|
label = to[x1].Label // return value
|
||||||
|
last := len(to) - 1
|
||||||
|
to[x1] = to[last]
|
||||||
|
a[n1] = to[:last]
|
||||||
|
if n1 == n2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
to = a[n2]
|
||||||
|
last = len(to) - 1
|
||||||
|
to[x2] = to[last]
|
||||||
|
a[n2] = to[:last]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveEdgeLabel removes a single edge between nodes n1 and n2 with label l.
|
||||||
|
//
|
||||||
|
// It removes reciprocal arcs in the case of distinct n1 and n2 or removes
|
||||||
|
// a single arc loop in the case of n1 == n2.
|
||||||
|
//
|
||||||
|
// Returns true if the specified edge is found and successfully removed,
|
||||||
|
// false if the edge does not exist.
|
||||||
|
func (g LabeledUndirected) RemoveEdgeLabel(n1, n2 NI, l LI) (ok bool) {
|
||||||
|
ok, x1, x2 := g.HasEdgeLabel(n1, n2, l)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a := g.LabeledAdjacencyList
|
||||||
|
to := a[n1]
|
||||||
|
last := len(to) - 1
|
||||||
|
to[x1] = to[last]
|
||||||
|
a[n1] = to[:last]
|
||||||
|
if n1 == n2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
to = a[n2]
|
||||||
|
last = len(to) - 1
|
||||||
|
to[x2] = to[last]
|
||||||
|
a[n2] = to[:last]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TarjanBiconnectedComponents decomposes a graph into maximal biconnected
|
||||||
|
// components, components for which if any node were removed the component
|
||||||
|
// would remain connected.
|
||||||
|
//
|
||||||
|
// The receiver g must be a simple graph. The method calls the emit argument
|
||||||
|
// for each component identified, as long as emit returns true. If emit
|
||||||
|
// returns false, TarjanBiconnectedComponents returns immediately.
|
||||||
|
//
|
||||||
|
// See also the eqivalent unlabeled TarjanBiconnectedComponents.
|
||||||
|
func (g LabeledUndirected) TarjanBiconnectedComponents(emit func([]LabeledEdge) bool) {
|
||||||
|
// Code nearly identical to unlabled version.
|
||||||
|
number := make([]int, g.Order())
|
||||||
|
lowpt := make([]int, g.Order())
|
||||||
|
var stack []LabeledEdge
|
||||||
|
var i int
|
||||||
|
var biconnect func(NI, NI) bool
|
||||||
|
biconnect = func(v, u NI) bool {
|
||||||
|
i++
|
||||||
|
number[v] = i
|
||||||
|
lowpt[v] = i
|
||||||
|
for _, w := range g.LabeledAdjacencyList[v] {
|
||||||
|
if number[w.To] == 0 {
|
||||||
|
stack = append(stack, LabeledEdge{Edge{v, w.To}, w.Label})
|
||||||
|
if !biconnect(w.To, v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lowpt[w.To] < lowpt[v] {
|
||||||
|
lowpt[v] = lowpt[w.To]
|
||||||
|
}
|
||||||
|
if lowpt[w.To] >= number[v] {
|
||||||
|
var bcc []LabeledEdge
|
||||||
|
top := len(stack) - 1
|
||||||
|
for number[stack[top].N1] >= number[w.To] {
|
||||||
|
bcc = append(bcc, stack[top])
|
||||||
|
stack = stack[:top]
|
||||||
|
top--
|
||||||
|
}
|
||||||
|
bcc = append(bcc, stack[top])
|
||||||
|
stack = stack[:top]
|
||||||
|
top--
|
||||||
|
if !emit(bcc) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if number[w.To] < number[v] && w.To != u {
|
||||||
|
stack = append(stack, LabeledEdge{Edge{v, w.To}, w.Label})
|
||||||
|
if number[w.To] < lowpt[v] {
|
||||||
|
lowpt[v] = number[w.To]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for w := range g.LabeledAdjacencyList {
|
||||||
|
if number[w] == 0 && !biconnect(NI(w), -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eulerian) pushUndir() error {
|
||||||
|
for u := e.top(); ; {
|
||||||
|
e.uv.SetBit(int(u), 0)
|
||||||
|
arcs := e.g[u]
|
||||||
|
if len(arcs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w := arcs[0]
|
||||||
|
e.s++
|
||||||
|
e.p[e.s] = w
|
||||||
|
e.g[u] = arcs[1:] // consume arc
|
||||||
|
// difference from directed counterpart in dir.go:
|
||||||
|
// as long as it's not a loop, consume reciprocal arc as well
|
||||||
|
if w != u {
|
||||||
|
a2 := e.g[w]
|
||||||
|
for x, rx := range a2 {
|
||||||
|
if rx == u { // here it is
|
||||||
|
last := len(a2) - 1
|
||||||
|
a2[x] = a2[last] // someone else gets the seat
|
||||||
|
e.g[w] = a2[:last] // and it's gone.
|
||||||
|
goto l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("graph not undirected. %d -> %d reciprocal not found", u, w)
|
||||||
|
}
|
||||||
|
l:
|
||||||
|
u = w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *labEulerian) pushUndir() error {
|
||||||
|
for u := e.top(); ; {
|
||||||
|
e.uv.SetBit(int(u.To), 0)
|
||||||
|
arcs := e.g[u.To]
|
||||||
|
if len(arcs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w := arcs[0]
|
||||||
|
e.s++
|
||||||
|
e.p[e.s] = w
|
||||||
|
e.g[u.To] = arcs[1:] // consume arc
|
||||||
|
// difference from directed counterpart in dir.go:
|
||||||
|
// as long as it's not a loop, consume reciprocal arc as well
|
||||||
|
if w.To != u.To {
|
||||||
|
a2 := e.g[w.To]
|
||||||
|
for x, rx := range a2 {
|
||||||
|
if rx.To == u.To && rx.Label == w.Label { // here it is
|
||||||
|
last := len(a2) - 1
|
||||||
|
a2[x] = a2[last] // someone else can have the seat
|
||||||
|
e.g[w.To] = a2[:last] // and it's gone.
|
||||||
|
goto l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("graph not undirected. %d -> %v reciprocal not found", u.To, w)
|
||||||
|
}
|
||||||
|
l:
|
||||||
|
u = w
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,8 @@
|
||||||
# github.com/Microsoft/go-winio v0.4.11
|
# github.com/Microsoft/go-winio v0.4.11
|
||||||
github.com/Microsoft/go-winio
|
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 v0.0.0-20181203112020-004b46473808
|
||||||
github.com/containerd/continuity/pathdriver
|
github.com/containerd/continuity/pathdriver
|
||||||
# github.com/davecgh/go-spew v1.1.1
|
# 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 v1.0.0
|
||||||
github.com/hashicorp/hcl
|
github.com/hashicorp/hcl
|
||||||
github.com/hashicorp/hcl/hcl/ast
|
github.com/hashicorp/hcl/hcl/ast
|
||||||
github.com/hashicorp/hcl/hcl/token
|
|
||||||
github.com/hashicorp/hcl/hcl/parser
|
github.com/hashicorp/hcl/hcl/parser
|
||||||
|
github.com/hashicorp/hcl/hcl/token
|
||||||
github.com/hashicorp/hcl/json/parser
|
github.com/hashicorp/hcl/json/parser
|
||||||
github.com/hashicorp/hcl/hcl/strconv
|
|
||||||
github.com/hashicorp/hcl/hcl/scanner
|
github.com/hashicorp/hcl/hcl/scanner
|
||||||
|
github.com/hashicorp/hcl/hcl/strconv
|
||||||
github.com/hashicorp/hcl/json/scanner
|
github.com/hashicorp/hcl/json/scanner
|
||||||
github.com/hashicorp/hcl/json/token
|
github.com/hashicorp/hcl/json/token
|
||||||
# github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
|
# 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/sergi/go-diff/diffmatchpatch
|
||||||
# github.com/sirupsen/logrus v1.3.0
|
# github.com/sirupsen/logrus v1.3.0
|
||||||
github.com/sirupsen/logrus
|
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 v0.0.3
|
||||||
github.com/spf13/cobra
|
github.com/spf13/cobra
|
||||||
# github.com/spf13/pflag v1.0.3
|
# github.com/spf13/pflag v1.0.3
|
||||||
|
|
Loading…
Reference in New Issue