replace parser with actions/workflow-parser

This commit is contained in:
Casey Lee 2019-01-30 23:14:18 -08:00
parent 72fbefcedc
commit 5d0a8d26ae
No known key found for this signature in database
GPG Key ID: 1899120ECD0A1784
43 changed files with 12749 additions and 634 deletions

View File

@ -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
} }

64
actions/graph.go Normal file
View File

@ -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
}

View File

@ -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]
}
}
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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"

View File

@ -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)
}
}
}

View File

@ -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
View File

@ -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
View File

@ -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=

21
vendor/github.com/actions/workflow-parser/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,73 @@
package model
// Configuration is a parsed main.workflow file
type Configuration struct {
Actions []*Action
Workflows []*Workflow
}
// Action represents a single "action" stanza in a .workflow file.
type Action struct {
Identifier string
Uses Uses
Runs, Args ActionCommand
Needs []string
Env map[string]string
Secrets []string
}
// ActionCommand represents the optional "runs" and "args" attributes.
// Each one takes one of two forms:
// - runs="entrypoint arg1 arg2 ..."
// - runs=[ "entrypoint", "arg1", "arg2", ... ]
// If the user uses the string form, "Raw" contains that value, and
// "Parsed" contains an array of the string value split at whitespace.
// If the user uses the array form, "Raw" is empty, and "Parsed" contains
// the array.
type ActionCommand struct {
Raw string
Parsed []string
}
// Workflow represents a single "workflow" stanza in a .workflow file.
type Workflow struct {
Identifier string
On string
Resolves []string
}
// GetAction looks up action by identifier.
//
// If the action is not found, nil is returned.
func (c *Configuration) GetAction(id string) *Action {
for _, action := range c.Actions {
if action.Identifier == id {
return action
}
}
return nil
}
// GetWorkflow looks up a workflow by identifier.
//
// If the workflow is not found, nil is returned.
func (c *Configuration) GetWorkflow(id string) *Workflow {
for _, workflow := range c.Workflows {
if workflow.Identifier == id {
return workflow
}
}
return nil
}
// GetWorkflows gets all Workflow structures that match a given type of event.
// e.g., GetWorkflows("push")
func (c *Configuration) GetWorkflows(eventType string) []*Workflow {
var ret []*Workflow
for _, workflow := range c.Workflows {
if IsMatchingEventType(workflow.On, eventType) {
ret = append(ret, workflow)
}
}
return ret
}

View File

@ -0,0 +1,55 @@
package model
import (
"strings"
)
// IsAllowedEventType returns true if the event type is supported.
func IsAllowedEventType(eventType string) bool {
_, ok := eventTypeWhitelist[strings.ToLower(eventType)]
return ok
}
// IsMatchingEventType checks to see if the "flowOn" string from a flow's on attribute matches the incoming webhook of type eventType.
func IsMatchingEventType(flowOn, eventType string) bool {
return strings.ToLower(flowOn) == strings.ToLower(eventType)
}
// https://developer.github.com/actions/creating-workflows/workflow-configuration-options/#events-supported-in-workflow-files
var eventTypeWhitelist = map[string]struct{}{
"check_run": {},
"check_suite": {},
"commit_comment": {},
"create": {},
"delete": {},
"deployment": {},
"deployment_status": {},
"fork": {},
"gollum": {},
"issue_comment": {},
"issues": {},
"label": {},
"member": {},
"milestone": {},
"page_build": {},
"project_card": {},
"project_column": {},
"project": {},
"public": {},
"pull_request_review_comment": {},
"pull_request_review": {},
"pull_request": {},
"push": {},
"release": {},
"repository_dispatch": {},
"status": {},
"watch": {},
}
func AddAllowedEventType(s string) {
eventTypeWhitelist[s] = struct{}{}
}
func RemoveAllowedEventType(s string) {
delete(eventTypeWhitelist, s)
}

View File

@ -0,0 +1,57 @@
package model
import (
"fmt"
)
type Uses interface {
fmt.Stringer
isUses()
}
// UsesDockerImage represents `uses = "docker://<image>"`
type UsesDockerImage struct {
Image string
}
// UsesRepository represents `uses = "<owner>/<repo>[/<path>]@<ref>"`
type UsesRepository struct {
Repository string
Path string
Ref string
}
// UsesPath represents `uses = "./<path>"`
type UsesPath struct {
Path string
}
// UsesInvalid represents any invalid `uses = "<raw>"` value
type UsesInvalid struct {
Raw string
}
func (u *UsesDockerImage) isUses() {}
func (u *UsesRepository) isUses() {}
func (u *UsesPath) isUses() {}
func (u *UsesInvalid) isUses() {}
func (u *UsesDockerImage) String() string {
return fmt.Sprintf("docker://%s", u.Image)
}
func (u *UsesRepository) String() string {
if u.Path == "" {
return fmt.Sprintf("%s@%s", u.Repository, u.Ref)
}
return fmt.Sprintf("%s/%s@%s", u.Repository, u.Path, u.Ref)
}
func (u *UsesPath) String() string {
return fmt.Sprintf("./%s", u.Path)
}
func (u *UsesInvalid) String() string {
return u.Raw
}

View File

@ -0,0 +1,129 @@
package parser
import (
"fmt"
"sort"
"strconv"
"strings"
"github.com/actions/workflow-parser/model"
)
type ParserError struct {
message string
Errors ErrorList
Actions []*model.Action
Workflows []*model.Workflow
}
func (p *ParserError) Error() string {
return p.message
}
// Error represents an error identified by the parser, either syntactic
// (HCL) or semantic (.workflow) in nature. There are fields for location
// (File, Line, Column), severity, and base error string. The `Error()`
// function on this type concatenates whatever bits of the location are
// available with the message. The severity is only used for filtering.
type Error struct {
message string
Pos ErrorPos
Severity Severity
}
// ErrorPos represents the location of an error in a user's workflow
// file(s).
type ErrorPos struct {
File string
Line int
Column int
}
// newFatal creates a new error at the FATAL level, indicating that the
// file is so broken it should not be displayed.
func newFatal(pos ErrorPos, format string, a ...interface{}) *Error {
return &Error{
message: fmt.Sprintf(format, a...),
Pos: pos,
Severity: FATAL,
}
}
// newError creates a new error at the ERROR level, indicating that the
// file can be displayed but cannot be run.
func newError(pos ErrorPos, format string, a ...interface{}) *Error {
return &Error{
message: fmt.Sprintf(format, a...),
Pos: pos,
Severity: ERROR,
}
}
// newWarning creates a new error at the WARNING level, indicating that
// the file might be runnable but might not execute as intended.
func newWarning(pos ErrorPos, format string, a ...interface{}) *Error {
return &Error{
message: fmt.Sprintf(format, a...),
Pos: pos,
Severity: WARNING,
}
}
func (e *Error) Error() string {
var sb strings.Builder
if e.Pos.Line != 0 {
sb.WriteString("Line ") // nolint: errcheck
sb.WriteString(strconv.Itoa(e.Pos.Line)) // nolint: errcheck
sb.WriteString(": ") // nolint: errcheck
}
if sb.Len() > 0 {
sb.WriteString(e.message) // nolint: errcheck
return sb.String()
}
return e.message
}
const (
_ = iota
// WARNING indicates a mistake that might affect correctness
WARNING
// ERROR indicates a mistake that prevents execution of any workflows in the file
ERROR
// FATAL indicates a mistake that prevents even drawing the file
FATAL
)
// Severity represents the level of an error encountered while parsing a
// workflow file. See the comments for WARNING, ERROR, and FATAL, above.
type Severity int
// FirstError searches a Configuration for the first error at or above a
// given severity level. Checking the return value against nil is a good
// way to see if the file has any errors at or above the given severity.
// A caller intending to execute the file might check for
// `errors.FirstError(parser.WARNING)`, while a caller intending to
// display the file might check for `errors.FirstError(parser.FATAL)`.
func (errors ErrorList) FirstError(severity Severity) error {
for _, e := range errors {
if e.Severity >= severity {
return e
}
}
return nil
}
type ErrorList []*Error
func (a ErrorList) Len() int { return len(a) }
func (a ErrorList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ErrorList) Less(i, j int) bool { return a[i].Pos.Line < a[j].Pos.Line }
// sortErrors sorts the errors reported by the parser. Do this after
// parsing is complete. The sort is stable, so order is preserved within
// a single line: left to right, syntax errors before validation errors.
func (errors ErrorList) sort() {
sort.Stable(errors)
}

View File

@ -0,0 +1,15 @@
package parser
type OptionFunc func(*parseState)
func WithSuppressWarnings() OptionFunc {
return func(ps *parseState) {
ps.suppressSeverity = WARNING
}
}
func WithSuppressErrors() OptionFunc {
return func(ps *parseState) {
ps.suppressSeverity = ERROR
}
}

View File

@ -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}
}

9
vendor/github.com/soniakeys/bits/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,9 @@
sudo: false
language: go
go: master
before_script:
- go vet
- go get github.com/client9/misspell/cmd/misspell
- misspell -error *
- go get github.com/soniakeys/vetc
- vetc

463
vendor/github.com/soniakeys/bits/bits.go generated vendored Normal file
View File

@ -0,0 +1,463 @@
// Copyright 2017 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
// Bits implements methods on a bit array type.
//
// The Bits type holds a fixed size array of bits, numbered consecutively
// from zero. Some set-like operations are possible, but the API is more
// array-like or register-like.
package bits
import (
"fmt"
mb "math/bits"
)
// Bits holds a fixed number of bits.
//
// Bit number 0 is stored in the LSB, or bit 0, of the word indexed at 0.
//
// When Num is not a multiple of 64, the last element of Bits will hold some
// bits beyond Num. These bits are undefined. They are not required to be
// zero but do not have any meaning. Bits methods are not required to leave
// them undisturbed.
type Bits struct {
Num int // number of bits
Bits []uint64
}
// New constructs a Bits value with the given number of bits.
//
// It panics if num is negative.
func New(num int) Bits {
if num < 0 {
panic("negative number of bits")
}
return Bits{num, make([]uint64, (num+63)>>6)}
}
// NewGivens constructs a Bits value with the given bits nums set to 1.
//
// The number of bits will be just enough to hold the largest bit value
// listed. That is, the number of bits will be the max bit number plus one.
//
// It panics if any bit number is negative.
func NewGivens(nums ...int) Bits {
max := -1
for _, p := range nums {
if p > max {
max = p
}
}
b := New(max + 1)
for _, p := range nums {
b.SetBit(p, 1)
}
return b
}
// AllOnes returns true if all Num bits are 1.
func (b Bits) AllOnes() bool {
last := len(b.Bits) - 1
for _, w := range b.Bits[:last] {
if w != ^uint64(0) {
return false
}
}
return ^b.Bits[last]<<uint(64*len(b.Bits)-b.Num) == 0
}
// AllZeros returns true if all Num bits are 0.
func (b Bits) AllZeros() bool {
last := len(b.Bits) - 1
for _, w := range b.Bits[:last] {
if w != 0 {
return false
}
}
return b.Bits[last]<<uint(64*len(b.Bits)-b.Num) == 0
}
// And sets z = x & y.
//
// It panics if x and y do not have the same Num.
func (z *Bits) And(x, y Bits) {
if x.Num != y.Num {
panic("arguments have different number of bits")
}
if z.Num != x.Num {
*z = New(x.Num)
}
for i, w := range y.Bits {
z.Bits[i] = x.Bits[i] & w
}
}
// AndNot sets z = x &^ y.
//
// It panics if x and y do not have the same Num.
func (z *Bits) AndNot(x, y Bits) {
if x.Num != y.Num {
panic("arguments have different number of bits")
}
if z.Num != x.Num {
*z = New(x.Num)
}
for i, w := range y.Bits {
z.Bits[i] = x.Bits[i] &^ w
}
}
// Bit returns the value of the n'th bit of receiver b.
func (b Bits) Bit(n int) int {
if n < 0 || n >= b.Num {
panic("bit number out of range")
}
return int(b.Bits[n>>6] >> uint(n&63) & 1)
}
// ClearAll sets all bits to 0.
func (b Bits) ClearAll() {
for i := range b.Bits {
b.Bits[i] = 0
}
}
// ClearBits sets the given bits to 0 in receiver b.
//
// Other bits of b are left unchanged.
//
// It panics if any bit number is out of range.
// That is, negative or >= the number of bits.
func (b Bits) ClearBits(nums ...int) {
for _, p := range nums {
b.SetBit(p, 0)
}
}
// Equal returns true if all Num bits of a and b are equal.
//
// It panics if a and b have different Num.
func (a Bits) Equal(b Bits) bool {
if a.Num != b.Num {
panic("receiver and argument have different number of bits")
}
if a.Num == 0 {
return true
}
last := len(a.Bits) - 1
for i, w := range a.Bits[:last] {
if w != b.Bits[i] {
return false
}
}
return (a.Bits[last]^b.Bits[last])<<uint(len(a.Bits)*64-a.Num) == 0
}
// IterateOnes calls visitor function v for each bit with a value of 1, in order
// from lowest bit to highest bit.
//
// Iteration continues to the highest bit as long as v returns true.
// It stops if v returns false.
//
// IterateOnes returns true normally. It returns false if v returns false.
//
// IterateOnes may not be sensitive to changes if bits are changed during
// iteration, by the vistor function for example.
// See OneFrom for an iteration method sensitive to changes during iteration.
func (b Bits) IterateOnes(v func(int) bool) bool {
for x, w := range b.Bits {
if w != 0 {
t := mb.TrailingZeros64(w)
i := t // index in w of next 1 bit
for {
n := x<<6 | i
if n >= b.Num {
return true
}
if !v(x<<6 | i) {
return false
}
w >>= uint(t + 1)
if w == 0 {
break
}
t = mb.TrailingZeros64(w)
i += 1 + t
}
}
}
return true
}
// IterateZeros calls visitor function v for each bit with a value of 0,
// in order from lowest bit to highest bit.
//
// Iteration continues to the highest bit as long as v returns true.
// It stops if v returns false.
//
// IterateZeros returns true normally. It returns false if v returns false.
//
// IterateZeros may not be sensitive to changes if bits are changed during
// iteration, by the vistor function for example.
// See ZeroFrom for an iteration method sensitive to changes during iteration.
func (b Bits) IterateZeros(v func(int) bool) bool {
for x, w := range b.Bits {
w = ^w
if w != 0 {
t := mb.TrailingZeros64(w)
i := t // index in w of next 1 bit
for {
n := x<<6 | i
if n >= b.Num {
return true
}
if !v(x<<6 | i) {
return false
}
w >>= uint(t + 1)
if w == 0 {
break
}
t = mb.TrailingZeros64(w)
i += 1 + t
}
}
}
return true
}
// Not sets receiver z to the complement of b.
func (z *Bits) Not(b Bits) {
if z.Num != b.Num {
*z = New(b.Num)
}
for i, w := range b.Bits {
z.Bits[i] = ^w
}
}
// OneFrom returns the number of the first 1 bit at or after (from) bit num.
//
// It returns -1 if there is no one bit at or after num.
//
// This provides one way to iterate over one bits.
// To iterate over the one bits, call OneFrom with n = 0 to get the the first
// one bit, then call with the result + 1 to get successive one bits.
// Unlike the Iterate method, this technique is stateless and so allows
// bits to be changed between successive calls.
//
// There is no panic for calling OneFrom with an argument >= b.Num.
// In this case OneFrom simply returns -1.
//
// See also Iterate.
func (b Bits) OneFrom(num int) int {
if num >= b.Num {
return -1
}
x := num >> 6
// test for 1 in this word at or after n
if wx := b.Bits[x] >> uint(num&63); wx != 0 {
num += mb.TrailingZeros64(wx)
if num >= b.Num {
return -1
}
return num
}
x++
for y, wy := range b.Bits[x:] {
if wy != 0 {
num = (x+y)<<6 | mb.TrailingZeros64(wy)
if num >= b.Num {
return -1
}
return num
}
}
return -1
}
// Or sets z = x | y.
//
// It panics if x and y do not have the same Num.
func (z *Bits) Or(x, y Bits) {
if x.Num != y.Num {
panic("arguments have different number of bits")
}
if z.Num != x.Num {
*z = New(x.Num)
}
for i, w := range y.Bits {
z.Bits[i] = x.Bits[i] | w
}
}
// OnesCount returns the number of 1 bits.
func (b Bits) OnesCount() (c int) {
if b.Num == 0 {
return 0
}
last := len(b.Bits) - 1
for _, w := range b.Bits[:last] {
c += mb.OnesCount64(w)
}
c += mb.OnesCount64(b.Bits[last] << uint(len(b.Bits)*64-b.Num))
return
}
// Set sets the bits of z to the bits of x.
func (z *Bits) Set(b Bits) {
if z.Num != b.Num {
*z = New(b.Num)
}
copy(z.Bits, b.Bits)
}
// SetAll sets z to have all 1 bits.
func (b Bits) SetAll() {
for i := range b.Bits {
b.Bits[i] = ^uint64(0)
}
}
// SetBit sets the n'th bit to x, where x is a 0 or 1.
//
// It panics if n is out of range
func (b Bits) SetBit(n, x int) {
if n < 0 || n >= b.Num {
panic("bit number out of range")
}
if x == 0 {
b.Bits[n>>6] &^= 1 << uint(n&63)
} else {
b.Bits[n>>6] |= 1 << uint(n&63)
}
}
// SetBits sets the given bits to 1 in receiver b.
//
// Other bits of b are left unchanged.
//
// It panics if any bit number is out of range, negative or >= the number
// of bits.
func (b Bits) SetBits(nums ...int) {
for _, p := range nums {
b.SetBit(p, 1)
}
}
// Single returns true if b has exactly one 1 bit.
func (b Bits) Single() bool {
// like OnesCount, but stop as soon as two are found
if b.Num == 0 {
return false
}
c := 0
last := len(b.Bits) - 1
for _, w := range b.Bits[:last] {
c += mb.OnesCount64(w)
if c > 1 {
return false
}
}
c += mb.OnesCount64(b.Bits[last] << uint(len(b.Bits)*64-b.Num))
return c == 1
}
// Slice returns a slice with the bit numbers of each 1 bit.
func (b Bits) Slice() (s []int) {
for x, w := range b.Bits {
if w == 0 {
continue
}
t := mb.TrailingZeros64(w)
i := t // index in w of next 1 bit
for {
n := x<<6 | i
if n >= b.Num {
break
}
s = append(s, n)
w >>= uint(t + 1)
if w == 0 {
break
}
t = mb.TrailingZeros64(w)
i += 1 + t
}
}
return
}
// String returns a readable representation.
//
// The returned string is big-endian, with the highest number bit first.
//
// If Num is 0, an empty string is returned.
func (b Bits) String() (s string) {
if b.Num == 0 {
return ""
}
last := len(b.Bits) - 1
for _, w := range b.Bits[:last] {
s = fmt.Sprintf("%064b", w) + s
}
lb := b.Num - 64*last
return fmt.Sprintf("%0*b", lb,
b.Bits[last]&(^uint64(0)>>uint(64-lb))) + s
}
// Xor sets z = x ^ y.
func (z *Bits) Xor(x, y Bits) {
if x.Num != y.Num {
panic("arguments have different number of bits")
}
if z.Num != x.Num {
*z = New(x.Num)
}
for i, w := range y.Bits {
z.Bits[i] = x.Bits[i] ^ w
}
}
// ZeroFrom returns the number of the first 0 bit at or after (from) bit num.
//
// It returns -1 if there is no zero bit at or after num.
//
// This provides one way to iterate over zero bits.
// To iterate over the zero bits, call ZeroFrom with n = 0 to get the the first
// zero bit, then call with the result + 1 to get successive zero bits.
// Unlike the IterateZeros method, this technique is stateless and so allows
// bits to be changed between successive calls.
//
// There is no panic for calling ZeroFrom with an argument >= b.Num.
// In this case ZeroFrom simply returns -1.
//
// See also IterateZeros.
func (b Bits) ZeroFrom(num int) int {
// code much like OneFrom except words are negated before testing
if num >= b.Num {
return -1
}
x := num >> 6
// negate word to test for 0 at or after n
if wx := ^b.Bits[x] >> uint(num&63); wx != 0 {
num += mb.TrailingZeros64(wx)
if num >= b.Num {
return -1
}
return num
}
x++
for y, wy := range b.Bits[x:] {
wy = ^wy
if wy != 0 {
num = (x+y)<<6 | mb.TrailingZeros64(wy)
if num >= b.Num {
return -1
}
return num
}
}
return -1
}

1
vendor/github.com/soniakeys/bits/go.mod generated vendored Normal file
View File

@ -0,0 +1 @@
module "github.com/soniakeys/bits"

38
vendor/github.com/soniakeys/bits/readme.adoc generated vendored Normal file
View File

@ -0,0 +1,38 @@
= Bits
Bits provides methods on a bit array type.
The Bits type holds a fixed size array of bits, numbered consecutively
from zero. Some set-like operations are possible, but the API is more
array-like or register-like.
image:https://godoc.org/github.com/soniakeys/bits?status.svg[link=https://godoc.org/github.com/soniakeys/bits] image:https://travis-ci.org/soniakeys/bits.svg[link=https://travis-ci.org/soniakeys/bits]
== Motivation and history
This package evolved from needs of my library of
https://github.com/soniakeys/graph[graph algorithms]. For graph algorithms
a common need is to store a single bit of information per node in a way that
is both fast and memory efficient. I began by using `big.Int` from the standard
library, then wrapped big.Int in a type. From time to time I considered
other publicly available bit array or bit set packages, such as Will
Fitzgerald's popular https://github.com/willf/bitset[bitset], but there were
always little reasons I preferred my own type and methods. My type that
wrapped `big.Int` met my needs until some simple benchmarks indicated it
might be causing performance problems. Some further experiments supported
this hypothesis so I ran further tests with a prototype bit array written
from scratch. Then satisfied that my custom bit array was solving the graph
performance problems, I decided to move it to a separate package with the
idea it might have more general utility. For the initial version of this
package I did the following:
- implemented a few tests to demonstrate fundamental correctness
- brought over most methods of my type that wrapped big.Int
- changed the index type from the graph-specific node index to a general `int`
- replaced some custom bit-twiddling with use of the new `math/bits` package
in the standard library
- renamed a few methods for clarity
- added a few methods for symmetry
- added a few new methods I had seen a need for in my graph library
- added doc, examples, tests, and more tests for 100% coverage
- added this readme

2
vendor/github.com/soniakeys/graph/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
*.dot
anecdote/anecdote

11
vendor/github.com/soniakeys/graph/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,11 @@
sudo: false
language: go
go:
- "1.9.x"
- master
before_script:
- go tool vet -composites=false -printf=false -shift=false .
- go get github.com/client9/misspell/cmd/misspell
- go get github.com/soniakeys/vetc
- misspell -error * */* */*/*
- vetc

406
vendor/github.com/soniakeys/graph/adj.go generated vendored Normal file
View File

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

417
vendor/github.com/soniakeys/graph/adj_RO.go generated vendored Normal file
View File

@ -0,0 +1,417 @@
// Copyright 2014 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
package graph
// adj_RO.go is code generated from adj_cg.go by directives in graph.go.
// Editing adj_cg.go is okay.
// DO NOT EDIT adj_RO.go. The RO is for Read Only.
import (
"errors"
"fmt"
"math/rand"
"github.com/soniakeys/bits"
)
// ArcDensity returns density for an simple directed graph.
//
// See also ArcDensity function.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) ArcDensity() float64 {
return ArcDensity(len(g), g.ArcSize())
}
// ArcSize returns the number of arcs in g.
//
// Note that for an undirected graph without loops, the number of undirected
// edges -- the traditional meaning of graph size -- will be ArcSize()/2.
// On the other hand, if g is an undirected graph that has or may have loops,
// g.ArcSize()/2 is not a meaningful quantity.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) ArcSize() int {
m := 0
for _, to := range g {
m += len(to)
}
return m
}
// BoundsOk validates that all arcs in g stay within the slice bounds of g.
//
// BoundsOk returns true when no arcs point outside the bounds of g.
// Otherwise it returns false and an example arc that points outside of g.
//
// Most methods of this package assume the BoundsOk condition and may
// panic when they encounter an arc pointing outside of the graph. This
// function can be used to validate a graph when the BoundsOk condition
// is unknown.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) BoundsOk() (ok bool, fr NI, to NI) {
for fr, to := range g {
for _, to := range to {
if to < 0 || to >= NI(len(g)) {
return false, NI(fr), to
}
}
}
return true, -1, to
}
// BreadthFirst traverses a directed or undirected graph in breadth
// first order.
//
// Traversal starts at node start and visits the nodes reachable from
// start. The function visit is called for each node visited. Nodes
// not reachable from start are not visited.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also alt.BreadthFirst, a variant with more options, and
// alt.BreadthFirst2, a direction optimizing variant.
func (g AdjacencyList) BreadthFirst(start NI, visit func(NI)) {
v := bits.New(len(g))
v.SetBit(int(start), 1)
visit(start)
var next []NI
for frontier := []NI{start}; len(frontier) > 0; {
for _, n := range frontier {
for _, nb := range g[n] {
if v.Bit(int(nb)) == 0 {
v.SetBit(int(nb), 1)
visit(nb)
next = append(next, nb)
}
}
}
frontier, next = next, frontier[:0]
}
}
// Copy makes a deep copy of g.
// Copy also computes the arc size ma, the number of arcs.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) Copy() (c AdjacencyList, ma int) {
c = make(AdjacencyList, len(g))
for n, to := range g {
c[n] = append([]NI{}, to...)
ma += len(to)
}
return
}
// DepthFirst traverses a directed or undirected graph in depth
// first order.
//
// Traversal starts at node start and visits the nodes reachable from
// start. The function visit is called for each node visited. Nodes
// not reachable from start are not visited.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also alt.DepthFirst, a variant with more options.
func (g AdjacencyList) DepthFirst(start NI, visit func(NI)) {
v := bits.New(len(g))
var f func(NI)
f = func(n NI) {
visit(n)
v.SetBit(int(n), 1)
for _, to := range g[n] {
if v.Bit(int(to)) == 0 {
f(to)
}
}
}
f(start)
}
// HasArc returns true if g has any arc from node `fr` to node `to`.
//
// Also returned is the index within the slice of arcs from node `fr`.
// If no arc from `fr` to `to` is present, HasArc returns false, -1.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also the method ParallelArcs, which finds all parallel arcs from
// `fr` to `to`.
func (g AdjacencyList) HasArc(fr, to NI) (bool, int) {
for x, h := range g[fr] {
if h == to {
return true, x
}
}
return false, -1
}
// AnyLoop identifies if a graph contains a loop, an arc that leads from a
// a node back to the same node.
//
// If g contains a loop, the method returns true and an example of a node
// with a loop. If there are no loops in g, the method returns false, -1.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) AnyLoop() (bool, NI) {
for fr, to := range g {
for _, to := range to {
if NI(fr) == to {
return true, to
}
}
}
return false, -1
}
// AddNode maps a node in a supergraph to a subgraph node.
//
// Argument p must be an NI in supergraph s.Super. AddNode panics if
// p is not a valid node index of s.Super.
//
// AddNode is idempotent in that it does not add a new node to the subgraph if
// a subgraph node already exists mapped to supergraph node p.
//
// The mapped subgraph NI is returned.
func (s *Subgraph) AddNode(p NI) (b NI) {
if int(p) < 0 || int(p) >= s.Super.Order() {
panic(fmt.Sprint("AddNode: NI ", p, " not in supergraph"))
}
if b, ok := s.SubNI[p]; ok {
return b
}
a := s.AdjacencyList
b = NI(len(a))
s.AdjacencyList = append(a, nil)
s.SuperNI = append(s.SuperNI, p)
s.SubNI[p] = b
return
}
// AddArc adds an arc to a subgraph.
//
// Arguments fr, to must be NIs in supergraph s.Super. As with AddNode,
// AddArc panics if fr and to are not valid node indexes of s.Super.
//
// The arc specfied by fr, to must exist in s.Super. Further, the number of
// parallel arcs in the subgraph cannot exceed the number of corresponding
// parallel arcs in the supergraph. That is, each arc already added to the
// subgraph counts against the arcs available in the supergraph. If a matching
// arc is not available, AddArc returns an error.
//
// If a matching arc is available, subgraph nodes are added as needed, the
// subgraph arc is added, and the method returns nil.
func (s *Subgraph) AddArc(fr NI, to NI) error {
// verify supergraph NIs first, but without adding subgraph nodes just yet.
if int(fr) < 0 || int(fr) >= s.Super.Order() {
panic(fmt.Sprint("AddArc: NI ", fr, " not in supergraph"))
}
if int(to) < 0 || int(to) >= s.Super.Order() {
panic(fmt.Sprint("AddArc: NI ", to, " not in supergraph"))
}
// count existing matching arcs in subgraph
n := 0
a := s.AdjacencyList
if bf, ok := s.SubNI[fr]; ok {
if bt, ok := s.SubNI[to]; ok {
// both NIs already exist in subgraph, need to count arcs
bTo := to
bTo = bt
for _, t := range a[bf] {
if t == bTo {
n++
}
}
}
}
// verify matching arcs are available in supergraph
for _, t := range (*s.Super)[fr] {
if t == to {
if n > 0 {
n-- // match existing arc
continue
}
// no more existing arcs need to be matched. nodes can finally
// be added as needed and then the arc can be added.
bf := s.AddNode(fr)
to = s.AddNode(to)
s.AdjacencyList[bf] = append(s.AdjacencyList[bf], to)
return nil // success
}
}
return errors.New("arc not available in supergraph")
}
func (super AdjacencyList) induceArcs(sub map[NI]NI, sup []NI) AdjacencyList {
s := make(AdjacencyList, len(sup))
for b, p := range sup {
var a []NI
for _, to := range super[p] {
if bt, ok := sub[to]; ok {
to = bt
a = append(a, to)
}
}
s[b] = a
}
return s
}
// InduceList constructs a node-induced subgraph.
//
// The subgraph is induced on receiver graph g. Argument l must be a list of
// NIs in receiver graph g. Receiver g becomes the supergraph of the induced
// subgraph.
//
// Duplicate NIs are allowed in list l. The duplicates are effectively removed
// and only a single corresponding node is created in the subgraph. Subgraph
// NIs are mapped in the order of list l, execpt for ignoring duplicates.
// NIs in l that are not in g will panic.
//
// Returned is the constructed Subgraph object containing the induced subgraph
// and the mappings to the supergraph.
func (g *AdjacencyList) InduceList(l []NI) *Subgraph {
sub, sup := mapList(l)
return &Subgraph{
Super: g,
SubNI: sub,
SuperNI: sup,
AdjacencyList: g.induceArcs(sub, sup)}
}
// InduceBits constructs a node-induced subgraph.
//
// The subgraph is induced on receiver graph g. Argument t must be a bitmap
// representing NIs in receiver graph g. Receiver g becomes the supergraph
// of the induced subgraph. NIs in t that are not in g will panic.
//
// Returned is the constructed Subgraph object containing the induced subgraph
// and the mappings to the supergraph.
func (g *AdjacencyList) InduceBits(t bits.Bits) *Subgraph {
sub, sup := mapBits(t)
return &Subgraph{
Super: g,
SubNI: sub,
SuperNI: sup,
AdjacencyList: g.induceArcs(sub, sup)}
}
// IsSimple checks for loops and parallel arcs.
//
// A graph is "simple" if it has no loops or parallel arcs.
//
// IsSimple returns true, -1 for simple graphs. If a loop or parallel arc is
// found, simple returns false and a node that represents a counterexample
// to the graph being simple.
//
// See also separate methods AnyLoop and AnyParallel.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) IsSimple() (ok bool, n NI) {
if lp, n := g.AnyLoop(); lp {
return false, n
}
if pa, n, _ := g.AnyParallel(); pa {
return false, n
}
return true, -1
}
// IsolatedNodes returns a bitmap of isolated nodes in receiver graph g.
//
// An isolated node is one with no arcs going to or from it.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) IsolatedNodes() (i bits.Bits) {
i = bits.New(len(g))
i.SetAll()
for fr, to := range g {
if len(to) > 0 {
i.SetBit(fr, 0)
for _, to := range to {
i.SetBit(int(to), 0)
}
}
}
return
}
// Order is the number of nodes in receiver g.
//
// It is simply a wrapper method for the Go builtin len().
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) Order() int {
// Why a wrapper for len()? Mostly for Directed and Undirected.
// u.Order() is a little nicer than len(u.LabeledAdjacencyList).
return len(g)
}
// ParallelArcs identifies all arcs from node `fr` to node `to`.
//
// The returned slice contains an element for each arc from node `fr` to node `to`.
// The element value is the index within the slice of arcs from node `fr`.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also the method HasArc, which stops after finding a single arc.
func (g AdjacencyList) ParallelArcs(fr, to NI) (p []int) {
for x, h := range g[fr] {
if h == to {
p = append(p, x)
}
}
return
}
// Permute permutes the node labeling of receiver g.
//
// Argument p must be a permutation of the node numbers of the graph,
// 0 through len(g)-1. A permutation returned by rand.Perm(len(g)) for
// example is acceptable.
//
// The graph is permuted in place. The graph keeps the same underlying
// memory but values of the graph representation are permuted to produce
// an isomorphic graph. The node previously labeled 0 becomes p[0] and so on.
// See example (or the code) for clarification.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) Permute(p []int) {
old := append(AdjacencyList{}, g...) // shallow copy
for fr, arcs := range old {
for i, to := range arcs {
arcs[i] = NI(p[to])
}
g[p[fr]] = arcs
}
}
// ShuffleArcLists shuffles the arc lists of each node of receiver g.
//
// For example a node with arcs leading to nodes 3 and 7 might have an
// arc list of either [3 7] or [7 3] after calling this method. The
// connectivity of the graph is not changed. The resulting graph stays
// equivalent but a traversal will encounter arcs in a different
// order.
//
// If Rand r is nil, the rand package default shared source is used.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g AdjacencyList) ShuffleArcLists(r *rand.Rand) {
ri := rand.Intn
if r != nil {
ri = r.Intn
}
// Knuth-Fisher-Yates
for _, to := range g {
for i := len(to); i > 1; {
j := ri(i)
i--
to[i], to[j] = to[j], to[i]
}
}
}

417
vendor/github.com/soniakeys/graph/adj_cg.go generated vendored Normal file
View File

@ -0,0 +1,417 @@
// Copyright 2014 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
package graph
// adj_RO.go is code generated from adj_cg.go by directives in graph.go.
// Editing adj_cg.go is okay.
// DO NOT EDIT adj_RO.go. The RO is for Read Only.
import (
"errors"
"fmt"
"math/rand"
"github.com/soniakeys/bits"
)
// ArcDensity returns density for an simple directed graph.
//
// See also ArcDensity function.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) ArcDensity() float64 {
return ArcDensity(len(g), g.ArcSize())
}
// ArcSize returns the number of arcs in g.
//
// Note that for an undirected graph without loops, the number of undirected
// edges -- the traditional meaning of graph size -- will be ArcSize()/2.
// On the other hand, if g is an undirected graph that has or may have loops,
// g.ArcSize()/2 is not a meaningful quantity.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) ArcSize() int {
m := 0
for _, to := range g {
m += len(to)
}
return m
}
// BoundsOk validates that all arcs in g stay within the slice bounds of g.
//
// BoundsOk returns true when no arcs point outside the bounds of g.
// Otherwise it returns false and an example arc that points outside of g.
//
// Most methods of this package assume the BoundsOk condition and may
// panic when they encounter an arc pointing outside of the graph. This
// function can be used to validate a graph when the BoundsOk condition
// is unknown.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) BoundsOk() (ok bool, fr NI, to Half) {
for fr, to := range g {
for _, to := range to {
if to.To < 0 || to.To >= NI(len(g)) {
return false, NI(fr), to
}
}
}
return true, -1, to
}
// BreadthFirst traverses a directed or undirected graph in breadth
// first order.
//
// Traversal starts at node start and visits the nodes reachable from
// start. The function visit is called for each node visited. Nodes
// not reachable from start are not visited.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also alt.BreadthFirst, a variant with more options, and
// alt.BreadthFirst2, a direction optimizing variant.
func (g LabeledAdjacencyList) BreadthFirst(start NI, visit func(NI)) {
v := bits.New(len(g))
v.SetBit(int(start), 1)
visit(start)
var next []NI
for frontier := []NI{start}; len(frontier) > 0; {
for _, n := range frontier {
for _, nb := range g[n] {
if v.Bit(int(nb.To)) == 0 {
v.SetBit(int(nb.To), 1)
visit(nb.To)
next = append(next, nb.To)
}
}
}
frontier, next = next, frontier[:0]
}
}
// Copy makes a deep copy of g.
// Copy also computes the arc size ma, the number of arcs.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) Copy() (c LabeledAdjacencyList, ma int) {
c = make(LabeledAdjacencyList, len(g))
for n, to := range g {
c[n] = append([]Half{}, to...)
ma += len(to)
}
return
}
// DepthFirst traverses a directed or undirected graph in depth
// first order.
//
// Traversal starts at node start and visits the nodes reachable from
// start. The function visit is called for each node visited. Nodes
// not reachable from start are not visited.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also alt.DepthFirst, a variant with more options.
func (g LabeledAdjacencyList) DepthFirst(start NI, visit func(NI)) {
v := bits.New(len(g))
var f func(NI)
f = func(n NI) {
visit(n)
v.SetBit(int(n), 1)
for _, to := range g[n] {
if v.Bit(int(to.To)) == 0 {
f(to.To)
}
}
}
f(start)
}
// HasArc returns true if g has any arc from node `fr` to node `to`.
//
// Also returned is the index within the slice of arcs from node `fr`.
// If no arc from `fr` to `to` is present, HasArc returns false, -1.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also the method ParallelArcs, which finds all parallel arcs from
// `fr` to `to`.
func (g LabeledAdjacencyList) HasArc(fr, to NI) (bool, int) {
for x, h := range g[fr] {
if h.To == to {
return true, x
}
}
return false, -1
}
// AnyLoop identifies if a graph contains a loop, an arc that leads from a
// a node back to the same node.
//
// If g contains a loop, the method returns true and an example of a node
// with a loop. If there are no loops in g, the method returns false, -1.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) AnyLoop() (bool, NI) {
for fr, to := range g {
for _, to := range to {
if NI(fr) == to.To {
return true, to.To
}
}
}
return false, -1
}
// AddNode maps a node in a supergraph to a subgraph node.
//
// Argument p must be an NI in supergraph s.Super. AddNode panics if
// p is not a valid node index of s.Super.
//
// AddNode is idempotent in that it does not add a new node to the subgraph if
// a subgraph node already exists mapped to supergraph node p.
//
// The mapped subgraph NI is returned.
func (s *LabeledSubgraph) AddNode(p NI) (b NI) {
if int(p) < 0 || int(p) >= s.Super.Order() {
panic(fmt.Sprint("AddNode: NI ", p, " not in supergraph"))
}
if b, ok := s.SubNI[p]; ok {
return b
}
a := s.LabeledAdjacencyList
b = NI(len(a))
s.LabeledAdjacencyList = append(a, nil)
s.SuperNI = append(s.SuperNI, p)
s.SubNI[p] = b
return
}
// AddArc adds an arc to a subgraph.
//
// Arguments fr, to must be NIs in supergraph s.Super. As with AddNode,
// AddArc panics if fr and to are not valid node indexes of s.Super.
//
// The arc specfied by fr, to must exist in s.Super. Further, the number of
// parallel arcs in the subgraph cannot exceed the number of corresponding
// parallel arcs in the supergraph. That is, each arc already added to the
// subgraph counts against the arcs available in the supergraph. If a matching
// arc is not available, AddArc returns an error.
//
// If a matching arc is available, subgraph nodes are added as needed, the
// subgraph arc is added, and the method returns nil.
func (s *LabeledSubgraph) AddArc(fr NI, to Half) error {
// verify supergraph NIs first, but without adding subgraph nodes just yet.
if int(fr) < 0 || int(fr) >= s.Super.Order() {
panic(fmt.Sprint("AddArc: NI ", fr, " not in supergraph"))
}
if int(to.To) < 0 || int(to.To) >= s.Super.Order() {
panic(fmt.Sprint("AddArc: NI ", to.To, " not in supergraph"))
}
// count existing matching arcs in subgraph
n := 0
a := s.LabeledAdjacencyList
if bf, ok := s.SubNI[fr]; ok {
if bt, ok := s.SubNI[to.To]; ok {
// both NIs already exist in subgraph, need to count arcs
bTo := to
bTo.To = bt
for _, t := range a[bf] {
if t == bTo {
n++
}
}
}
}
// verify matching arcs are available in supergraph
for _, t := range (*s.Super)[fr] {
if t == to {
if n > 0 {
n-- // match existing arc
continue
}
// no more existing arcs need to be matched. nodes can finally
// be added as needed and then the arc can be added.
bf := s.AddNode(fr)
to.To = s.AddNode(to.To)
s.LabeledAdjacencyList[bf] = append(s.LabeledAdjacencyList[bf], to)
return nil // success
}
}
return errors.New("arc not available in supergraph")
}
func (super LabeledAdjacencyList) induceArcs(sub map[NI]NI, sup []NI) LabeledAdjacencyList {
s := make(LabeledAdjacencyList, len(sup))
for b, p := range sup {
var a []Half
for _, to := range super[p] {
if bt, ok := sub[to.To]; ok {
to.To = bt
a = append(a, to)
}
}
s[b] = a
}
return s
}
// InduceList constructs a node-induced subgraph.
//
// The subgraph is induced on receiver graph g. Argument l must be a list of
// NIs in receiver graph g. Receiver g becomes the supergraph of the induced
// subgraph.
//
// Duplicate NIs are allowed in list l. The duplicates are effectively removed
// and only a single corresponding node is created in the subgraph. Subgraph
// NIs are mapped in the order of list l, execpt for ignoring duplicates.
// NIs in l that are not in g will panic.
//
// Returned is the constructed Subgraph object containing the induced subgraph
// and the mappings to the supergraph.
func (g *LabeledAdjacencyList) InduceList(l []NI) *LabeledSubgraph {
sub, sup := mapList(l)
return &LabeledSubgraph{
Super: g,
SubNI: sub,
SuperNI: sup,
LabeledAdjacencyList: g.induceArcs(sub, sup)}
}
// InduceBits constructs a node-induced subgraph.
//
// The subgraph is induced on receiver graph g. Argument t must be a bitmap
// representing NIs in receiver graph g. Receiver g becomes the supergraph
// of the induced subgraph. NIs in t that are not in g will panic.
//
// Returned is the constructed Subgraph object containing the induced subgraph
// and the mappings to the supergraph.
func (g *LabeledAdjacencyList) InduceBits(t bits.Bits) *LabeledSubgraph {
sub, sup := mapBits(t)
return &LabeledSubgraph{
Super: g,
SubNI: sub,
SuperNI: sup,
LabeledAdjacencyList: g.induceArcs(sub, sup)}
}
// IsSimple checks for loops and parallel arcs.
//
// A graph is "simple" if it has no loops or parallel arcs.
//
// IsSimple returns true, -1 for simple graphs. If a loop or parallel arc is
// found, simple returns false and a node that represents a counterexample
// to the graph being simple.
//
// See also separate methods AnyLoop and AnyParallel.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) IsSimple() (ok bool, n NI) {
if lp, n := g.AnyLoop(); lp {
return false, n
}
if pa, n, _ := g.AnyParallel(); pa {
return false, n
}
return true, -1
}
// IsolatedNodes returns a bitmap of isolated nodes in receiver graph g.
//
// An isolated node is one with no arcs going to or from it.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) IsolatedNodes() (i bits.Bits) {
i = bits.New(len(g))
i.SetAll()
for fr, to := range g {
if len(to) > 0 {
i.SetBit(fr, 0)
for _, to := range to {
i.SetBit(int(to.To), 0)
}
}
}
return
}
// Order is the number of nodes in receiver g.
//
// It is simply a wrapper method for the Go builtin len().
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) Order() int {
// Why a wrapper for len()? Mostly for Directed and Undirected.
// u.Order() is a little nicer than len(u.LabeledAdjacencyList).
return len(g)
}
// ParallelArcs identifies all arcs from node `fr` to node `to`.
//
// The returned slice contains an element for each arc from node `fr` to node `to`.
// The element value is the index within the slice of arcs from node `fr`.
//
// There are equivalent labeled and unlabeled versions of this method.
//
// See also the method HasArc, which stops after finding a single arc.
func (g LabeledAdjacencyList) ParallelArcs(fr, to NI) (p []int) {
for x, h := range g[fr] {
if h.To == to {
p = append(p, x)
}
}
return
}
// Permute permutes the node labeling of receiver g.
//
// Argument p must be a permutation of the node numbers of the graph,
// 0 through len(g)-1. A permutation returned by rand.Perm(len(g)) for
// example is acceptable.
//
// The graph is permuted in place. The graph keeps the same underlying
// memory but values of the graph representation are permuted to produce
// an isomorphic graph. The node previously labeled 0 becomes p[0] and so on.
// See example (or the code) for clarification.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) Permute(p []int) {
old := append(LabeledAdjacencyList{}, g...) // shallow copy
for fr, arcs := range old {
for i, to := range arcs {
arcs[i].To = NI(p[to.To])
}
g[p[fr]] = arcs
}
}
// ShuffleArcLists shuffles the arc lists of each node of receiver g.
//
// For example a node with arcs leading to nodes 3 and 7 might have an
// arc list of either [3 7] or [7 3] after calling this method. The
// connectivity of the graph is not changed. The resulting graph stays
// equivalent but a traversal will encounter arcs in a different
// order.
//
// If Rand r is nil, the rand package default shared source is used.
//
// There are equivalent labeled and unlabeled versions of this method.
func (g LabeledAdjacencyList) ShuffleArcLists(r *rand.Rand) {
ri := rand.Intn
if r != nil {
ri = r.Intn
}
// Knuth-Fisher-Yates
for _, to := range g {
for i := len(to); i > 1; {
j := ri(i)
i--
to[i], to[j] = to[j], to[i]
}
}
}

1059
vendor/github.com/soniakeys/graph/dir.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1091
vendor/github.com/soniakeys/graph/dir_RO.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1091
vendor/github.com/soniakeys/graph/dir_cg.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

122
vendor/github.com/soniakeys/graph/doc.go generated vendored Normal file
View File

@ -0,0 +1,122 @@
// Copyright 2014 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
// Graph algorithms: Dijkstra, A*, Bellman Ford, Floyd Warshall;
// Kruskal and Prim minimal spanning tree; topological sort and DAG longest
// and shortest paths; Eulerian cycle and path; degeneracy and k-cores;
// Bron Kerbosch clique finding; connected components; dominance; and others.
//
// This is a graph library of integer indexes. To use it with application
// data, you associate data with integer indexes, perform searches or other
// operations with the library, and then use the integer index results to refer
// back to your application data.
//
// Thus it does not store application data, pointers to application data,
// or require you to implement an interface on your application data.
// The idea is to keep the library methods fast and lean.
//
// Representation overview
//
// The package defines a type for a node index (NI) which is just an integer
// type. It defines types for a number of number graph representations using
// NI. The fundamental graph type is AdjacencyList, which is the
// common "list of lists" graph representation. It is a list as a slice
// with one element for each node of the graph. Each element is a list
// itself, a list of neighbor nodes, implemented as an NI slice. Methods
// on an AdjacencyList generally work on any representable graph, including
// directed or undirected graphs, simple graphs or multigraphs.
//
// The type Undirected embeds an AdjacencyList adding methods specific to
// undirected graphs. Similarly the type Directed adds methods meaningful
// for directed graphs.
//
// Similar to NI, the type LI is a "label index" which labels a
// node-to-neighbor "arc" or edge. Just as an NI can index arbitrary node
// data, an LI can index arbitrary arc or edge data. A number of algorithms
// use a "weight" associated with an arc. This package does not represent
// weighted arcs explicitly, but instead uses the LI as a more general
// mechanism allowing not only weights but arbitrary data to be associated
// with arcs. While AdjacencyList represents an arc with simply an NI,
// the type LabeledAdjacencyList uses a type that pairs an NI with an LI.
// This type is named Half, for half-arc. (A full arc would represent
// both ends.) Types LabeledDirected and LabeledUndirected embed a
// LabeledAdjacencyList.
//
// In contrast to Half, the type Edge represents both ends of an edge (but
// no label.) The type LabeledEdge adds the label. The type WeightedEdgeList
// bundles a list of LabeledEdges with a WeightFunc. (WeightedEdgeList has
// few methods. It exists primarily to support the Kruskal algorithm.)
//
// FromList is a compact rooted tree (or forest) respresentation. Like
// AdjacencyList and LabeledAdjacencyList, it is a list with one element for
// each node of the graph. Each element contains only a single neighbor
// however, its parent in the tree, the "from" node.
//
// Code generation
//
// A number of methods on AdjacencyList, Directed, and Undirected are
// applicable to LabeledAdjacencyList, LabeledDirected, and LabeledUndirected
// simply by ignoring the label. In these cases code generation provides
// methods on both types from a single source implementation. These methods
// are documented with the sentence "There are equivalent labeled and unlabeled
// versions of this method."
//
// Terminology
//
// This package uses the term "node" rather than "vertex." It uses "arc"
// to mean a directed edge, and uses "from" and "to" to refer to the ends
// of an arc. It uses "start" and "end" to refer to endpoints of a search
// or traversal.
//
// The usage of "to" and "from" is perhaps most strange. In common speech
// they are prepositions, but throughout this package they are used as
// adjectives, for example to refer to the "from node" of an arc or the
// "to node". The type "FromList" is named to indicate it stores a list of
// "from" values.
//
// A "half arc" refers to just one end of an arc, either the to or from end.
//
// Two arcs are "reciprocal" if they connect two distinct nodes n1 and n2,
// one arc leading from n1 to n2 and the other arc leading from n2 to n1.
// Undirected graphs are represented with reciprocal arcs.
//
// A node with an arc to itself represents a "loop." Duplicate arcs, where
// a node has multiple arcs to another node, are termed "parallel arcs."
// A graph with no loops or parallel arcs is "simple." A graph that allows
// parallel arcs is a "multigraph"
//
// The "size" of a graph traditionally means the number of undirected edges.
// This package uses "arc size" to mean the number of arcs in a graph. For an
// undirected graph without loops, arc size is 2 * size.
//
// The "order" of a graph is the number of nodes. An "ordering" though means
// an ordered list of nodes.
//
// A number of graph search algorithms use a concept of arc "weights."
// The sum of arc weights along a path is a "distance." In contrast, the
// number of nodes in a path, including start and end nodes, is the path's
// "length." (Yes, mixing weights and lengths would be nonsense physically,
// but the terms used here are just distinct terms for abstract values.
// The actual meaning to an application is likely to be something else
// entirely and is not relevant within this package.)
//
// Finally, this package documentation takes back the word "object" in some
// places to refer to a Go value, especially a value of a type with methods.
//
// Shortest path searches
//
// This package implements a number of shortest path searches. Most work
// with weighted graphs that are directed or undirected, and with graphs
// that may have loops or parallel arcs. For weighted graphs, "shortest"
// is defined as the path distance (sum of arc weights) with path length
// (number of nodes) breaking ties. If multiple paths have the same minimum
// distance with the same minimum length, search methods are free to return
// any of them.
//
// Algorithm Description
// Dijkstra Non-negative arc weights, single or all paths.
// AStar Non-negative arc weights, heuristic guided, single path.
// BellmanFord Negative arc weights allowed, no negative cycles, all paths.
// DAGPath O(n) algorithm for DAGs, arc weights of any sign.
// FloydWarshall all pairs distances, no negative cycles.
package graph

498
vendor/github.com/soniakeys/graph/fromlist.go generated vendored Normal file
View File

@ -0,0 +1,498 @@
// Copyright 2014 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
package graph
import "github.com/soniakeys/bits"
// FromList represents a rooted tree (or forest) where each node is associated
// with a half arc identifying an arc "from" another node.
//
// Other terms for this data structure include "parent list",
// "predecessor list", "in-tree", "inverse arborescence", and
// "spaghetti stack."
//
// The Paths member represents the tree structure. Leaves and MaxLen are
// not always needed. Where Leaves is used it serves as a bitmap where
// Leaves.Bit(n) == 1 for each leaf n of the tree. Where MaxLen is used it is
// provided primarily as a convenience for functions that might want to
// anticipate the maximum path length that would be encountered traversing
// the tree.
//
// Various graph search methods use a FromList to returns search results.
// For a start node of a search, From will be -1 and Len will be 1. For other
// nodes reached by the search, From represents a half arc in a path back to
// start and Len represents the number of nodes in the path. For nodes not
// reached by the search, From will be -1 and Len will be 0.
//
// A single FromList can also represent a forest. In this case paths from
// all leaves do not return to a single root node, but multiple root nodes.
//
// While a FromList generally encodes a tree or forest, it is technically
// possible to encode a cyclic graph. A number of FromList methods require
// the receiver to be acyclic. Graph methods documented to return a tree or
// forest will never return a cyclic FromList. In other cases however,
// where a FromList is not known to by cyclic, the Cyclic method can be
// useful to validate the acyclic property.
type FromList struct {
Paths []PathEnd // tree representation
Leaves bits.Bits // leaves of tree
MaxLen int // length of longest path, max of all PathEnd.Len values
}
// PathEnd associates a half arc and a path length.
//
// A PathEnd list is an element type of FromList.
type PathEnd struct {
From NI // a "from" half arc, the node the arc comes from
Len int // number of nodes in path from start
}
/* NewFromList could be confusing now with bits also needing allocation.
maybe best to not have this function. Maybe a more useful new would be
one that took a PathEnd slice and intitialized everything including roots
and max len. Maybe its time for a separate []PathEnd type when that's
all that's needed. (and reconsider the name PathEnd)
*/
// NewFromList creates a FromList object of given order.
//
// The Paths member is allocated to the specified order n but other members
// are left as zero values.
func NewFromList(n int) FromList {
return FromList{Paths: make([]PathEnd, n)}
}
// BoundsOk validates the "from" values in the list.
//
// Negative values are allowed as they indicate root nodes.
//
// BoundsOk returns true when all from values are less than len(t).
// Otherwise it returns false and a node with a from value >= len(t).
func (f FromList) BoundsOk() (ok bool, n NI) {
for n, e := range f.Paths {
if int(e.From) >= len(f.Paths) {
return false, NI(n)
}
}
return true, -1
}
// CommonStart returns the common start node of minimal paths to a and b.
//
// It returns -1 if a and b cannot be traced back to a common node.
//
// The method relies on populated PathEnd.Len members. Use RecalcLen if
// the Len members are not known to be present and correct.
func (f FromList) CommonStart(a, b NI) NI {
p := f.Paths
if p[a].Len < p[b].Len {
a, b = b, a
}
for bl := p[b].Len; p[a].Len > bl; {
a = p[a].From
if a < 0 {
return -1
}
}
for a != b {
a = p[a].From
if a < 0 {
return -1
}
b = p[b].From
}
return a
}
// Cyclic determines if f contains a cycle, a non-empty path from a node
// back to itself.
//
// Cyclic returns true if g contains at least one cycle. It also returns
// an example of a node involved in a cycle.
//
// Cyclic returns (false, -1) in the normal case where f is acyclic.
// Note that the bool is not an "ok" return. A cyclic FromList is usually
// not okay.
func (f FromList) Cyclic() (cyclic bool, n NI) {
p := f.Paths
vis := bits.New(len(p))
for i := range p {
path := bits.New(len(p))
for n := i; vis.Bit(n) == 0; {
vis.SetBit(n, 1)
path.SetBit(n, 1)
if n = int(p[n].From); n < 0 {
break
}
if path.Bit(n) == 1 {
return true, NI(n)
}
}
}
return false, -1
}
// IsolatedNodeBits returns a bitmap of isolated nodes in receiver graph f.
//
// An isolated node is one with no arcs going to or from it.
func (f FromList) IsolatedNodes() (iso bits.Bits) {
p := f.Paths
iso = bits.New(len(p))
iso.SetAll()
for n, e := range p {
if e.From >= 0 {
iso.SetBit(n, 0)
iso.SetBit(int(e.From), 0)
}
}
return
}
// PathTo decodes a FromList, recovering a single path.
//
// The path is returned as a list of nodes where the first element will be
// a root node and the last element will be the specified end node.
//
// Only the Paths member of the receiver is used. Other members of the
// FromList do not need to be valid, however the MaxLen member can be useful
// for allocating argument p.
//
// Argument p can provide the result slice. If p has capacity for the result
// it will be used, otherwise a new slice is created for the result.
//
// See also function PathTo.
func (f FromList) PathTo(end NI, p []NI) []NI {
return PathTo(f.Paths, end, p)
}
// PathTo decodes a single path from a PathEnd list.
//
// A PathEnd list is the main data representation in a FromList. See FromList.
//
// PathTo returns a list of nodes where the first element will be
// a root node and the last element will be the specified end node.
//
// Argument p can provide the result slice. If p has capacity for the result
// it will be used, otherwise a new slice is created for the result.
//
// See also method FromList.PathTo.
func PathTo(paths []PathEnd, end NI, p []NI) []NI {
n := paths[end].Len
if n == 0 {
return p[:0]
}
if cap(p) >= n {
p = p[:n]
} else {
p = make([]NI, n)
}
for {
n--
p[n] = end
if n == 0 {
return p
}
end = paths[end].From
}
}
// PathToLabeled decodes a FromList, recovering a single path.
//
// The start of the returned path will be a root node of the FromList.
//
// Only the Paths member of the receiver is used. Other members of the
// FromList do not need to be valid, however the MaxLen member can be useful
// for allocating argument p.
//
// Argument p can provide the result slice. If p has capacity for the result
// it will be used, otherwise a new slice is created for the result.
//
// See also function PathTo.
func (f FromList) PathToLabeled(end NI, labels []LI, p []Half) LabeledPath {
n := f.Paths[end].Len - 1
if n <= 0 {
return LabeledPath{end, p[:0]}
}
if cap(p) >= n {
p = p[:n]
} else {
p = make([]Half, n)
}
for {
n--
p[n] = Half{To: end, Label: labels[end]}
end = f.Paths[end].From
if n == 0 {
return LabeledPath{end, p}
}
}
}
// Preorder traverses a FromList in preorder.
//
// Nodes are visited in order such that for any node n with from node fr,
// fr is visited before n. Where f represents a tree, the visit ordering
// corresponds to a preordering, or depth first traversal of the tree.
// Where f represents a forest, the preorderings of the trees can be
// intermingled.
//
// Leaves must be set correctly first. Use RecalcLeaves if leaves are not
// known to be set correctly. FromList f cannot be cyclic.
//
// Traversal continues while visitor function v returns true. It terminates
// if v returns false. Preorder returns true if it completes without v
// returning false. Preorder returns false if traversal is terminated by v
// returning false.
func (f FromList) Preorder(v func(NI) bool) bool {
p := f.Paths
done := bits.New(len(p))
var df func(NI) bool
df = func(n NI) bool {
done.SetBit(int(n), 1)
if fr := p[n].From; fr >= 0 && done.Bit(int(fr)) == 0 {
df(fr)
}
return v(n)
}
for n := range f.Paths {
p[n].Len = 0
}
return f.Leaves.IterateOnes(func(n int) bool {
return df(NI(n))
})
}
// RecalcLeaves recomputes the Leaves member of f.
func (f *FromList) RecalcLeaves() {
p := f.Paths
lv := &f.Leaves
if lv.Num != len(p) {
*lv = bits.New(len(p))
}
lv.SetAll()
for n := range f.Paths {
if fr := p[n].From; fr >= 0 {
lv.SetBit(int(fr), 0)
}
}
}
// RecalcLen recomputes Len for each path end, and recomputes MaxLen.
//
// RecalcLen relies on the Leaves member being valid. If it is not known
// to be valid, call RecalcLeaves before calling RecalcLen.
//
// RecalcLen will panic if the FromList is cyclic. Use the Cyclic method
// if needed to verify that the FromList is acyclic.
func (f *FromList) RecalcLen() {
p := f.Paths
var setLen func(NI) int
setLen = func(n NI) int {
switch {
case p[n].Len > 0:
return p[n].Len
case p[n].From < 0:
p[n].Len = 1
return 1
}
l := 1 + setLen(p[n].From)
p[n].Len = l
return l
}
for n := range f.Paths {
p[n].Len = 0
}
f.MaxLen = 0
f.Leaves.IterateOnes(func(n int) bool {
if l := setLen(NI(n)); l > f.MaxLen {
f.MaxLen = l
}
return true
})
}
// ReRoot reorients the tree containing n to make n the root node.
//
// It keeps the tree connected by "reversing" the path from n to the old root.
//
// After ReRoot, the Leaves and Len members are invalid.
// Call RecalcLeaves or RecalcLen as needed.
func (f *FromList) ReRoot(n NI) {
p := f.Paths
fr := p[n].From
if fr < 0 {
return
}
p[n].From = -1
for {
ff := p[fr].From
p[fr].From = n
if ff < 0 {
return
}
n = fr
fr = ff
}
}
// Root finds the root of a node in a FromList.
func (f FromList) Root(n NI) NI {
for p := f.Paths; ; {
fr := p[n].From
if fr < 0 {
return n
}
n = fr
}
}
// Transpose constructs the directed graph corresponding to FromList f
// but with arcs in the opposite direction. That is, from roots toward leaves.
//
// If non-nil argrument roots is passed, Transpose populates it as roots of
// the resulting forest and returns nRoots as a count of the roots.
//
// The method relies only on the From member of f.Paths. Other members of
// the FromList are not used.
func (f FromList) Transpose(roots *bits.Bits) (forest Directed, nRoots int) {
p := f.Paths
g := make(AdjacencyList, len(p))
if roots != nil {
nRoots = len(p)
if roots.Num != nRoots {
*roots = bits.New(nRoots)
}
roots.SetAll()
}
for i, e := range p {
if e.From == -1 {
continue
}
g[e.From] = append(g[e.From], NI(i))
if roots != nil && roots.Bit(i) == 1 {
roots.SetBit(i, 0)
nRoots--
}
}
return Directed{g}, nRoots
}
// TransposeLabeled constructs the labeled directed graph corresponding
// to FromList f but with arcs in the opposite direction. That is, from
// roots toward leaves.
//
// The argument labels can be nil. In this case labels are generated matching
// the path indexes. This corresponds to the "to", or child node.
//
// If labels is non-nil, it must be the same length as t.Paths and is used
// to look up label numbers by the path index.
//
// If non-nil argrument roots is passed, Transpose populates it as roots of
// the resulting forest and returns nRoots as a count of the roots.
//
// The method relies only on the From member of f.Paths. Other members of
// the FromList are not used.
func (f FromList) TransposeLabeled(labels []LI, roots *bits.Bits) (forest LabeledDirected, nRoots int) {
p := f.Paths
g := make(LabeledAdjacencyList, len(p))
if roots != nil {
nRoots = len(p)
if roots.Num != nRoots {
*roots = bits.New(nRoots)
}
roots.SetAll()
}
for i, p := range f.Paths {
if p.From == -1 {
continue
}
l := LI(i)
if labels != nil {
l = labels[i]
}
g[p.From] = append(g[p.From], Half{NI(i), l})
if roots != nil && roots.Bit(i) == 1 {
roots.SetBit(i, 0)
nRoots--
}
}
return LabeledDirected{g}, nRoots
}
// Undirected constructs the undirected graph corresponding to FromList f.
//
// The resulting graph will be a tree or forest.
//
// If non-nil argrument roots is passed, Transpose populates it as roots of
// the resulting forest and returns nRoots as a count of the roots.
//
// The method relies only on the From member of f.Paths. Other members of
// the FromList are not used.
func (f FromList) Undirected(roots *bits.Bits) (forest Undirected, nRoots int) {
p := f.Paths
g := make(AdjacencyList, len(p))
if roots != nil {
nRoots = len(p)
if roots.Num != nRoots {
*roots = bits.New(nRoots)
}
roots.SetAll()
}
for i, e := range p {
if e.From == -1 {
continue
}
g[i] = append(g[i], e.From)
g[e.From] = append(g[e.From], NI(i))
if roots != nil && roots.Bit(i) == 1 {
roots.SetBit(i, 0)
nRoots--
}
}
return Undirected{g}, nRoots
}
// LabeledUndirected constructs the labeled undirected graph corresponding
// to FromList f.
//
// The resulting graph will be a tree or forest.
//
// The argument labels can be nil. In this case labels are generated matching
// the path indexes. This corresponds to the "to", or child node.
//
// If labels is non-nil, it must be the same length as t.Paths and is used
// to look up label numbers by the path index.
//
// If non-nil argrument roots is passed, LabeledUndirected populates it as
// roots of the resulting forest and returns nRoots as a count of the roots.
//
// The method relies only on the From member of f.Paths. Other members of
// the FromList are not used.
func (f FromList) LabeledUndirected(labels []LI, roots *bits.Bits) (forest LabeledUndirected, nRoots int) {
p := f.Paths
g := make(LabeledAdjacencyList, len(p))
if roots != nil {
nRoots = len(p)
if roots.Num != nRoots {
*roots = bits.New(nRoots)
}
roots.SetAll()
}
for i, p := range f.Paths {
if p.From == -1 {
continue
}
l := LI(i)
if labels != nil {
l = labels[i]
}
g[i] = append(g[i], Half{p.From, l})
g[p.From] = append(g[p.From], Half{NI(i), l})
if roots != nil && roots.Bit(i) == 1 {
roots.SetBit(i, 0)
nRoots--
}
}
return LabeledUndirected{g}, nRoots
}

3
vendor/github.com/soniakeys/graph/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module "github.com/soniakeys/graph"
require "github.com/soniakeys/bits" v1.0.0

767
vendor/github.com/soniakeys/graph/graph.go generated vendored Normal file
View File

@ -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()
}

135
vendor/github.com/soniakeys/graph/hacking.adoc generated vendored Normal file
View File

@ -0,0 +1,135 @@
= Hacking
== Get, install
Basic use of the package is just go get, or git clone; go install. There are
no dependencies outside the standard library.
== Build
CI is currently on travis-ci.org.
The build runs go vet with a few exceptions for things I'm not a big fan of.
https://github.com/client9/misspell has been valuable.
Also I wrote https://github.com/soniakeys/vetc to validate that each source
file has copyright/license statement.
Then, its 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 Ill try just running it manually now and then.
Go fmt is not in the ci script, but I have at least one editor set up to run
it on save, so code should stay formatted pretty well.
== Examples with random output
The math/rand generators with constant seeds used to give consistent numbers
across Go versions and so some examples relied on this. Sometime after Go 1.9
though the numbers changed. The technique for now is to go ahead and write
those examples, get them working, then change the `// Output:` line to
`// Random output:`. This keeps them showing in go doc but keeps them from
being run by go test. This works for now. It might be revisited at some
point.
== Plans
The primary to-do list is the issue tracker on Github.
== Direction, focus, features
The project started with no real goal or purpose, just as a place for some code
that might be useful. Here are some elements that characterize the direction.
* The focus has been on algorithms on adjacency lists. That is, adjacency list
is the fundamental representation for most implemented algorithms. There are
many other interesting representations, many reasons to use them, but
adjacency list is common in literature and practice. It has been useful to
focus on this data representation, at first anyway.
* The focus has been on single threaded algorithms. Again, there is much new
and interesting work being done with concurrent, parallel, and distributed
graph algorithms, and Go might be an excellent language to implement some of
these algorithms. But as a preliminary step, more traditional
single-threaded algorithms are implemented.
* The focus has been on static finite graphs. Again there is much interesting
work in online algorithms, dynamic graphs, and infinite graphs, but these
are not generally considered here.
* Algorithms selected for implementation are generally ones commonly appearing
in beginning graph theory discussions and in general purpose graph libraries
in other programming languages. With these as drivers, there's a big risk
developing a library of curiosities and academic exercises rather than a
library of practical utility. But well, it's a start. The hope is that
there are some practical drivers behind graph theory and behind other graph
libraries.
* There is active current research going on in graph algorithm development.
One goal for this library is to implement newer and faster algorithms.
In some cases where it seems not too much work, older/classic/traditional
algorithms may be implemented for comparison. These generally go in the
alt subdirectory.
== General principles
* The API is rather low level.
* Slices instead of maps. Maps are pretty efficient, and the property of
unique keys can be useful, But slices are still faster and more efficient,
and the unique key property is not always needed or wanted. The Adjacency
list implementation of this library is all done in slices. Slices are used
in algorithms where possible, in preference to maps. Maps are still used in
some cases where uniqueness is needed.
* Interfaces not generally used. Algorithms are implemented directly on
concrete data types and not on interfaces describing the capabilities of
the data types. The abstraction of interfaces is a nice match to graph
theory and the convenience of running graph algorithms on any type that
implements an interface is appealing, but the costs seem too high to me.
Slices are rich with capababilites that get hidden behind interfaces and
direct slice manipulation is always faster than going through interfaces.
An impedance for programs using the library is that they will generally
have to implement a mapping from slice indexes to their application data,
often including for example, some other form of node ID. This seems fair
to push this burden outside the graph library; the library cannot know
the needs of this mapping.
* Bitsets are widely used, particularly to store one bit of information per
node of a graph. I used math/big at first but then moved to a dense bitset
of my own. Yes, I considered other third-party bitsets but had my own
feature set I wanted. A slice of bools is another alternative. Bools will
be faster in almost all cases but the bitset will use less memory. I'm
chosing size over speed for now.
* Code generation is used to provide methods that work on both labeled and
unlabeled graphs. Code is written to labeled types, then transformations
generate the unlabled equivalents.
* Methods are named for what they return rather than what they do, where
reasonable anyway.
* Consistency in method signature and behavior across corresponding methods,
for example directed/undirected, labeled/unlabeled, again, as long as it's
reasonable.
* Sometimes in tension with the consistency principle, methods are lazy about
datatypes of parameters and return values. Sometimes a vale might have
different reasonable representations, a set might be a bitset, map, slice
of bools, or slice of set members for example. Methods will take and return
whatever is convenient for them and not convert the form just for consistency
or to try to guess what a caller would prefer.
* Methods return multiple results for whatever the algorithm produces that
might be of interest. Sometimes an algorithm will have a primary result but
then some secondary values that also might be of interest. If they are
already computed as a byproduct of the algorithm, or can be computed at
negligible cost, return them.
* Sometimes in conflict with the multiple result principle, methods will not
speculatively compute secondary results if there is any significant cost
and if the secondary result can be just as easily computed later.
== Code Maintenance
There are tons of cut and paste variants. There's the basic AdjacencyList,
then Directed and Undirected variants, then Labeled variants of each of those.
Code gen helps avoid some cut and paste but there's a bunch that doesn't
code gen very well and so is duplicated with cut and paste. In particular
the testable examples in the _test files don't cg well and so are pretty much
all duplicated by hand. If you change code, think about where there should
be variants and go look to see if the variants need similar changes.

254
vendor/github.com/soniakeys/graph/mst.go generated vendored Normal file
View File

@ -0,0 +1,254 @@
// Copyright 2014 Sonia Keys
// License MIT: http://opensource.org/licenses/MIT
package graph
import (
"container/heap"
"sort"
"github.com/soniakeys/bits"
)
type dsElement struct {
from NI
rank int
}
type disjointSet struct {
set []dsElement
}
func newDisjointSet(n int) disjointSet {
set := make([]dsElement, n)
for i := range set {
set[i].from = -1
}
return disjointSet{set}
}
// return true if disjoint trees were combined.
// false if x and y were already in the same tree.
func (ds disjointSet) union(x, y NI) bool {
xr := ds.find(x)
yr := ds.find(y)
if xr == yr {
return false
}
switch xe, ye := &ds.set[xr], &ds.set[yr]; {
case xe.rank < ye.rank:
xe.from = yr
case xe.rank == ye.rank:
xe.rank++
fallthrough
default:
ye.from = xr
}
return true
}
func (ds disjointSet) find(n NI) NI {
// fast paths for n == root or from root.
// no updates need in these cases.
s := ds.set
fr := s[n].from
if fr < 0 { // n is root
return n
}
n, fr = fr, s[fr].from
if fr < 0 { // n is from root
return n
}
// otherwise updates needed.
// two iterative passes (rather than recursion or stack)
// pass 1: find root
r := fr
for {
f := s[r].from
if f < 0 {
break
}
r = f
}
// pass 2: update froms
for {
s[n].from = r
if fr == r {
return r
}
n = fr
fr = s[n].from
}
}
// Kruskal implements Kruskal's algorithm for constructing a minimum spanning
// forest on an undirected graph.
//
// The forest is returned as an undirected graph.
//
// Also returned is a total distance for the returned forest.
//
// This method is a convenience wrapper for LabeledEdgeList.Kruskal.
// If you have no need for the input graph as a LabeledUndirected, it may be
// more efficient to construct a LabeledEdgeList directly.
func (g LabeledUndirected) Kruskal(w WeightFunc) (spanningForest LabeledUndirected, dist float64) {
return g.WeightedArcsAsEdges(w).Kruskal()
}
// Kruskal implements Kruskal's algorithm for constructing a minimum spanning
// forest on an undirected graph.
//
// The algorithm allows parallel edges, thus it is acceptable to construct
// the receiver with LabeledUndirected.WeightedArcsAsEdges. It may be more
// efficient though, if you can construct the receiver WeightedEdgeList
// directly without parallel edges.
//
// The forest is returned as an undirected graph.
//
// Also returned is a total distance for the returned forest.
//
// The edge list of the receiver is sorted in place as a side effect of this
// method. See KruskalSorted for a version that relies on the edge list being
// already sorted. This method is a wrapper for KruskalSorted. If you can
// generate the input graph sorted as required for KruskalSorted, you can
// call that method directly and avoid the overhead of the sort.
func (l WeightedEdgeList) Kruskal() (g LabeledUndirected, dist float64) {
e := l.Edges
w := l.WeightFunc
sort.Slice(e, func(i, j int) bool { return w(e[i].LI) < w(e[j].LI) })
return l.KruskalSorted()
}
// KruskalSorted implements Kruskal's algorithm for constructing a minimum
// spanning tree on an undirected graph.
//
// When called, the edge list of the receiver must be already sorted by weight.
// See the Kruskal method for a version that accepts an unsorted edge list.
// As with Kruskal, parallel edges are allowed.
//
// The forest is returned as an undirected graph.
//
// Also returned is a total distance for the returned forest.
func (l WeightedEdgeList) KruskalSorted() (g LabeledUndirected, dist float64) {
ds := newDisjointSet(l.Order)
g.LabeledAdjacencyList = make(LabeledAdjacencyList, l.Order)
for _, e := range l.Edges {
if ds.union(e.N1, e.N2) {
g.AddEdge(Edge{e.N1, e.N2}, e.LI)
dist += l.WeightFunc(e.LI)
}
}
return
}
// Prim implements the Jarník-Prim-Dijkstra algorithm for constructing
// a minimum spanning tree on an undirected graph.
//
// Prim computes a minimal spanning tree on the connected component containing
// the given start node. The tree is returned in FromList f. Argument f
// cannot be a nil pointer although it can point to a zero value FromList.
//
// If the passed FromList.Paths has the len of g though, it will be reused.
// In the case of a graph with multiple connected components, this allows a
// spanning forest to be accumulated by calling Prim successively on
// representative nodes of the components. In this case if leaves for
// individual trees are of interest, pass a non-nil zero-value for the argument
// componentLeaves and it will be populated with leaves for the single tree
// spanned by the call.
//
// If argument labels is non-nil, it must have the same length as g and will
// be populated with labels corresponding to the edges of the tree.
//
// Returned are the number of nodes spanned for the single tree (which will be
// the order of the connected component) and the total spanned distance for the
// single tree.
func (g LabeledUndirected) Prim(start NI, w WeightFunc, f *FromList, labels []LI, componentLeaves *bits.Bits) (numSpanned int, dist float64) {
al := g.LabeledAdjacencyList
if len(f.Paths) != len(al) {
*f = NewFromList(len(al))
}
if f.Leaves.Num != len(al) {
f.Leaves = bits.New(len(al))
}
b := make([]prNode, len(al)) // "best"
for n := range b {
b[n].nx = NI(n)
b[n].fx = -1
}
rp := f.Paths
var frontier prHeap
rp[start] = PathEnd{From: -1, Len: 1}
numSpanned = 1
fLeaves := &f.Leaves
fLeaves.SetBit(int(start), 1)
if componentLeaves != nil {
if componentLeaves.Num != len(al) {
*componentLeaves = bits.New(len(al))
}
componentLeaves.SetBit(int(start), 1)
}
for a := start; ; {
for _, nb := range al[a] {
if rp[nb.To].Len > 0 {
continue // already in MST, no action
}
switch bp := &b[nb.To]; {
case bp.fx == -1: // new node for frontier
bp.from = fromHalf{From: a, Label: nb.Label}
bp.wt = w(nb.Label)
heap.Push(&frontier, bp)
case w(nb.Label) < bp.wt: // better arc
bp.from = fromHalf{From: a, Label: nb.Label}
bp.wt = w(nb.Label)
heap.Fix(&frontier, bp.fx)
}
}
if len(frontier) == 0 {
break // done
}
bp := heap.Pop(&frontier).(*prNode)
a = bp.nx
rp[a].Len = rp[bp.from.From].Len + 1
rp[a].From = bp.from.From
if len(labels) != 0 {
labels[a] = bp.from.Label
}
dist += bp.wt
fLeaves.SetBit(int(bp.from.From), 0)
fLeaves.SetBit(int(a), 1)
if componentLeaves != nil {
componentLeaves.SetBit(int(bp.from.From), 0)
componentLeaves.SetBit(int(a), 1)
}
numSpanned++
}
return
}
type prNode struct {
nx NI
from fromHalf
wt float64 // p.Weight(from.Label)
fx int
}
type prHeap []*prNode
func (h prHeap) Len() int { return len(h) }
func (h prHeap) Less(i, j int) bool { return h[i].wt < h[j].wt }
func (h prHeap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
h[i].fx = i
h[j].fx = j
}
func (p *prHeap) Push(x interface{}) {
nd := x.(*prNode)
nd.fx = len(*p)
*p = append(*p, nd)
}
func (p *prHeap) Pop() interface{} {
r := *p
last := len(r) - 1
*p = r[:last]
return r[last]
}

708
vendor/github.com/soniakeys/graph/random.go generated vendored Normal file
View File

@ -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ősRé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ősRé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ősRé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ősRé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ősRé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ősRé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
}

50
vendor/github.com/soniakeys/graph/readme.adoc generated vendored Normal file
View File

@ -0,0 +1,50 @@
= Graph
A graph library with goals of speed and simplicity, Graph implements
graph algorithms on graphs of zero-based integer node IDs.
image:https://godoc.org/github.com/soniakeys/graph?status.svg[link=https://godoc.org/github.com/soniakeys/graph]
image:http://gowalker.org/api/v1/badge[link=https://gowalker.org/github.com/soniakeys/graph]
image:http://go-search.org/badge?id=github.com%2Fsoniakeys%2Fgraph[link=http://go-search.org/view?id=github.com%2Fsoniakeys%2Fgraph]
image:https://travis-ci.org/soniakeys/graph.svg?branch=master[link=https://travis-ci.org/soniakeys/graph]
The library provides efficient graph representations and many methods on
graph types. It can be imported and used directly in many applications that
require or can benefit from graph algorithms.
The library should also be considered as library of source code that can serve
as starting material for coding variant or more complex algorithms.
== Ancillary material of interest
The directory link:tutorials[tutorials] is a work in progress - there are only
a few tutorials there yet - but the concept is to provide some topical
walk-throughs to supplement godoc. The source-based godoc documentation
remains the primary documentation.
The directory link:anecdote[anecdote] contains a stand-alone program that
performs single runs of a number of methods, collecting one-off or "anecdotal"
timings. It currently runs only a small fraction of the library methods but
may still be of interest for giving a general idea of how fast some methods
run.
The directory link:bench[bench] is another work in progress. The concept is
to present some plots showing benchmark performance approaching some
theoretical asymptote.
link:hacking.adoc[hacking.adoc] has some information about how the library is
developed, built, and tested. It might be of interest if for example you
plan to fork or contribute to the the repository.
== Test coverage
1 Jul 2017
....
graph 93.7%
graph/alt 88.0%
graph/dot 77.7%
graph/treevis 79.4%
....
== License
All files in the repository are licensed with the MIT License,
https://opensource.org/licenses/MIT.

761
vendor/github.com/soniakeys/graph/sssp.go generated vendored Normal file
View File

@ -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

817
vendor/github.com/soniakeys/graph/undir.go generated vendored Normal file
View File

@ -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
}
}

1138
vendor/github.com/soniakeys/graph/undir_RO.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1138
vendor/github.com/soniakeys/graph/undir_cg.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

11
vendor/modules.txt vendored
View File

@ -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