day25: initial graph stuff,
with cycles removal not working
This commit is contained in:
parent
665987395f
commit
9177d35caf
|
@ -1,3 +1,4 @@
|
||||||
/.direnv/
|
/.direnv/
|
||||||
/.go
|
/.go
|
||||||
input
|
input
|
||||||
|
*.png
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package day25
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run() int {
|
||||||
|
fmt.Println("time to wrap things up")
|
||||||
|
return 0
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
jqt: rhn xhk nvd
|
||||||
|
rsh: frs pzl lsr
|
||||||
|
xhk: hfx
|
||||||
|
cmg: qnr nvd lhk bvb
|
||||||
|
rhn: xhk bvb hfx
|
||||||
|
bvb: xhk hfx
|
||||||
|
pzl: lsr hfx nvd
|
||||||
|
qnr: nvd
|
||||||
|
ntq: jqt hfx bvb xhk
|
||||||
|
nvd: lhk
|
||||||
|
lsr: lhk
|
||||||
|
rzs: qnr cmg lsr rsh
|
||||||
|
frs: qnr lhk lsr
|
|
@ -0,0 +1,29 @@
|
||||||
|
flowchart TD
|
||||||
|
hfx --- xhk
|
||||||
|
frs --- qnr
|
||||||
|
qnr --- rzs
|
||||||
|
rsh --- rzs
|
||||||
|
bvb --- rhn
|
||||||
|
jqt --- ntq
|
||||||
|
lsr --- rsh
|
||||||
|
jqt --- xhk
|
||||||
|
frs --- rsh
|
||||||
|
bvb --- xhk
|
||||||
|
frs --- lsr
|
||||||
|
hfx --- pzl
|
||||||
|
cmg --- rzs
|
||||||
|
cmg --- lhk
|
||||||
|
jqt --- nvd
|
||||||
|
bvb --- cmg
|
||||||
|
hfx --- ntq
|
||||||
|
nvd --- pzl
|
||||||
|
rhn --- xhk
|
||||||
|
lsr --- rzs
|
||||||
|
cmg --- qnr
|
||||||
|
cmg --- nvd
|
||||||
|
bvb --- ntq
|
||||||
|
lhk --- lsr
|
||||||
|
hfx --- rhn
|
||||||
|
pzl --- rsh
|
||||||
|
lsr --- pzl
|
||||||
|
frs --- lhk
|
|
@ -0,0 +1,34 @@
|
||||||
|
flowchart TD
|
||||||
|
lhk --- lsr
|
||||||
|
cmg --- lhk
|
||||||
|
frs --- lhk
|
||||||
|
frs --- qnr
|
||||||
|
jqt --- xhk
|
||||||
|
lsr --- rsh
|
||||||
|
jqt --- ntq
|
||||||
|
bvb --- xhk
|
||||||
|
hfx --- rhn
|
||||||
|
jqt --- rhn
|
||||||
|
lsr --- pzl
|
||||||
|
nvd --- pzl
|
||||||
|
ntq --- xhk
|
||||||
|
bvb --- ntq
|
||||||
|
cmg --- qnr
|
||||||
|
rhn --- xhk
|
||||||
|
rsh --- rzs
|
||||||
|
frs --- lsr
|
||||||
|
frs --- rsh
|
||||||
|
hfx --- xhk
|
||||||
|
hfx --- pzl
|
||||||
|
cmg --- nvd
|
||||||
|
lhk --- nvd
|
||||||
|
nvd --- qnr
|
||||||
|
jqt --- nvd
|
||||||
|
lsr --- rzs
|
||||||
|
qnr --- rzs
|
||||||
|
cmg --- rzs
|
||||||
|
hfx --- ntq
|
||||||
|
bvb --- hfx
|
||||||
|
bvb --- cmg
|
||||||
|
bvb --- rhn
|
||||||
|
pzl --- rsh
|
|
@ -0,0 +1,34 @@
|
||||||
|
flowchart TD
|
||||||
|
hfx --- pzl
|
||||||
|
cmg --- lhk
|
||||||
|
lsr --- rsh
|
||||||
|
rsh --- rzs
|
||||||
|
bvb --- rhn
|
||||||
|
jqt --- xhk
|
||||||
|
nvd --- pzl
|
||||||
|
lsr --- pzl
|
||||||
|
frs --- qnr
|
||||||
|
frs --- lsr
|
||||||
|
lhk --- lsr
|
||||||
|
lsr --- rzs
|
||||||
|
rhn --- xhk
|
||||||
|
hfx --- ntq
|
||||||
|
nvd --- qnr
|
||||||
|
qnr --- rzs
|
||||||
|
bvb --- xhk
|
||||||
|
hfx --- xhk
|
||||||
|
jqt --- rhn
|
||||||
|
jqt --- nvd
|
||||||
|
cmg --- nvd
|
||||||
|
lhk --- nvd
|
||||||
|
frs --- rsh
|
||||||
|
ntq --- xhk
|
||||||
|
cmg --- rzs
|
||||||
|
bvb --- ntq
|
||||||
|
cmg --- qnr
|
||||||
|
hfx --- rhn
|
||||||
|
jqt --- ntq
|
||||||
|
bvb --- cmg
|
||||||
|
frs --- lhk
|
||||||
|
pzl --- rsh
|
||||||
|
bvb --- hfx
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 37 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
jqt: rhn nvd rsh
|
||||||
|
rsh: frs pzl lsr
|
||||||
|
xhk: hfx
|
|
@ -0,0 +1,254 @@
|
||||||
|
package day25
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
mapset "github.com/deckarep/golang-set/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Graph struct {
|
||||||
|
Nodes map[string]*Node
|
||||||
|
}
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
Name string
|
||||||
|
Neighbors mapset.Set[string]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Node) String() string {
|
||||||
|
return fmt.Sprintf("[%s : %+v]", n.Name, n.Neighbors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadGraphFile(filename string) (g Graph) {
|
||||||
|
g.Nodes = map[string]*Node{}
|
||||||
|
|
||||||
|
bytes, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
text := strings.TrimSpace(string(bytes))
|
||||||
|
for _, line := range strings.Split(text, "\n") {
|
||||||
|
g.readGraphLine(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) readGraphLine(l string) (n Node) {
|
||||||
|
firstSplit := strings.Split(l, ":")
|
||||||
|
n.Name = firstSplit[0]
|
||||||
|
n.Neighbors = mapset.NewSet[string]()
|
||||||
|
|
||||||
|
secondSplit := strings.Fields(firstSplit[1])
|
||||||
|
|
||||||
|
for _, neighborName := range secondSplit {
|
||||||
|
neighbor, exists := g.Nodes[neighborName]
|
||||||
|
if !exists {
|
||||||
|
neighbor = &Node{
|
||||||
|
Name: neighborName,
|
||||||
|
Neighbors: mapset.NewSet[string](),
|
||||||
|
}
|
||||||
|
g.Nodes[neighborName] = neighbor
|
||||||
|
}
|
||||||
|
neighbor.Neighbors.Add(n.Name)
|
||||||
|
n.Neighbors.Add(neighbor.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Nodes[n.Name] = &n
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) RemoveEdge(a, b string) {
|
||||||
|
// log.Printf("entering remove edge for %s and %s", a, b)
|
||||||
|
nodeA, existsA := g.Nodes[a]
|
||||||
|
// log.Println("got first node", nodeA, existsA)
|
||||||
|
nodeB, existsB := g.Nodes[b]
|
||||||
|
// log.Println("got second node", nodeB, existsB)
|
||||||
|
if !existsA || !existsB {
|
||||||
|
panic("requesting not found node")
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Println("before removals")
|
||||||
|
newANeighbors := mapset.NewSet[string]()
|
||||||
|
for oldNeighbor := range nodeA.Neighbors.Iter() {
|
||||||
|
if oldNeighbor != nodeB.Name {
|
||||||
|
newANeighbors.Add(oldNeighbor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodeA.Neighbors = newANeighbors
|
||||||
|
// nodeA.Neighbors.Remove(nodeB.Name)
|
||||||
|
|
||||||
|
// log.Println("removed first", nodeA)
|
||||||
|
newBNeighbors := mapset.NewSet[string]()
|
||||||
|
for oldNeighbor := range nodeB.Neighbors.Iter() {
|
||||||
|
if oldNeighbor != nodeB.Name {
|
||||||
|
newBNeighbors.Add(oldNeighbor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodeB.Neighbors = newBNeighbors
|
||||||
|
// nodeB.Neighbors.Remove(nodeA.Name)
|
||||||
|
// log.Println("removed second", nodeB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) findCycle() (from, to string, exists bool) {
|
||||||
|
log.Printf(">>>> starting new find cycle")
|
||||||
|
var firstNode *Node
|
||||||
|
for _, n := range g.Nodes {
|
||||||
|
firstNode = n
|
||||||
|
break
|
||||||
|
}
|
||||||
|
initialVisited := mapset.NewSet[string](firstNode.Name)
|
||||||
|
for neighborName := range firstNode.Neighbors.Iter() {
|
||||||
|
from, to, exists = g.dfcCycle(firstNode.Name, neighborName, initialVisited)
|
||||||
|
if exists {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("<<<< cycle %t, from %s to %s", exists, from, to)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) dfcCycle(fromName, atName string, visited mapset.Set[string]) (cycleFrom, cycleTo string, cycleExists bool) {
|
||||||
|
// log.Printf("> step from %+v to %+v. visited : %+v", fromName, atName, visited)
|
||||||
|
if visited.Cardinality() == len(g.Nodes) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
atNode := g.Nodes[atName]
|
||||||
|
|
||||||
|
if visited.Contains(atName) {
|
||||||
|
return fromName, atName, true
|
||||||
|
}
|
||||||
|
|
||||||
|
newVisited := visited.Clone()
|
||||||
|
newVisited.Add(atName)
|
||||||
|
for neighborName := range atNode.Neighbors.Iter() {
|
||||||
|
cycleFrom, cycleTo, cycleExists = g.dfcCycle(atName, neighborName, newVisited)
|
||||||
|
if cycleExists {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) DoThreeRemovals() {
|
||||||
|
from, to, found := g.findCycle()
|
||||||
|
log.Printf("first result %s, %s, %t", from, to, found)
|
||||||
|
// graph_test.go:26: first result xhk, rhn, true
|
||||||
|
g.RemoveEdge("xhk", "rhn")
|
||||||
|
|
||||||
|
from, to, found = g.findCycle()
|
||||||
|
log.Printf("second result %s, %s, %t", from, to, found)
|
||||||
|
// graph_test.go:32: second result qnr, nvd, true
|
||||||
|
g.RemoveEdge("qnr", "nvd")
|
||||||
|
|
||||||
|
from, to, found = g.findCycle()
|
||||||
|
log.Printf("third result %s, %s, %t", from, to, found)
|
||||||
|
// graph_test.go:36: third result lhk, cmg, true
|
||||||
|
|
||||||
|
g.RemoveEdge("lhk", "cmg")
|
||||||
|
from, to, found = g.findCycle()
|
||||||
|
log.Printf("fourth result %s, %s, %t", from, to, found)
|
||||||
|
|
||||||
|
// OK, so there could be more than 3 cycles.
|
||||||
|
// but if i want to break things into 2 components, i need to do more investigating
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) ComponentFrom(fromName string) (component mapset.Set[string]) {
|
||||||
|
startNode := g.Nodes[fromName]
|
||||||
|
component = mapset.NewSet[string](startNode.Name)
|
||||||
|
toVisit := startNode.Neighbors.Clone()
|
||||||
|
|
||||||
|
for toVisit.Cardinality() > 0 {
|
||||||
|
runnerNodeName, _ := toVisit.Pop()
|
||||||
|
if component.Contains(runnerNodeName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
component.Add(runnerNodeName)
|
||||||
|
runnerNode := g.Nodes[runnerNodeName]
|
||||||
|
unvisitedNeighbors := runnerNode.Neighbors.Difference(component)
|
||||||
|
// log.Printf("adding %s to component. neighbors %+v, adding %+v to visit",
|
||||||
|
// runnerNodeName, runnerNode.Neighbors, unvisitedNeighbors)
|
||||||
|
toVisit = toVisit.Union(unvisitedNeighbors)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) ToMermaid() (result string) {
|
||||||
|
result += "flowchart TD\n"
|
||||||
|
edges := mapset.NewSet[string]()
|
||||||
|
|
||||||
|
for _, node := range g.Nodes {
|
||||||
|
for neighborName := range node.Neighbors.Iter() {
|
||||||
|
var first, second string
|
||||||
|
if node.Name < neighborName {
|
||||||
|
first = node.Name
|
||||||
|
second = neighborName
|
||||||
|
} else {
|
||||||
|
first = neighborName
|
||||||
|
second = node.Name
|
||||||
|
}
|
||||||
|
edges.Add(fmt.Sprintf("\t%s --- %s\n", first, second))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for line := range edges.Iter() {
|
||||||
|
result += line
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph)SaveAsMermaid(filename string) {
|
||||||
|
mmd := g.ToMermaid()
|
||||||
|
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
file.WriteString(mmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Edge struct {
|
||||||
|
smaller, bigger string
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateEdge(a, b string) Edge {
|
||||||
|
var smaller, bigger string
|
||||||
|
if a < b {
|
||||||
|
smaller = a
|
||||||
|
bigger = b
|
||||||
|
} else {
|
||||||
|
smaller = b
|
||||||
|
bigger = a
|
||||||
|
}
|
||||||
|
return Edge{smaller, bigger}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) RemoveAllCycles() (removedEdges mapset.Set[Edge]) {
|
||||||
|
removedEdges = mapset.NewSet[Edge]()
|
||||||
|
hasCycle := true
|
||||||
|
var from, to string
|
||||||
|
for hasCycle {
|
||||||
|
from, to, hasCycle = g.findCycle()
|
||||||
|
if hasCycle {
|
||||||
|
edgeToRemove := CreateEdge(from, to)
|
||||||
|
removedEdges.Add(edgeToRemove)
|
||||||
|
g.RemoveEdge(from, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package day25
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadFileExample(t *testing.T) {
|
||||||
|
filename := "example"
|
||||||
|
g := ReadGraphFile(filename)
|
||||||
|
t.Logf("read graph %+v", g)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveEdge(t *testing.T) {
|
||||||
|
filename := "example"
|
||||||
|
g := ReadGraphFile(filename)
|
||||||
|
t.Logf("read graph %+v", g)
|
||||||
|
|
||||||
|
g.RemoveEdge("bvb", "hfx")
|
||||||
|
t.Logf("after removing bvb-hfv %+v", g)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunFindCycle(t *testing.T) {
|
||||||
|
filename := "example"
|
||||||
|
g := ReadGraphFile(filename)
|
||||||
|
g.DoThreeRemovals()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateExampleMermaid(t *testing.T) {
|
||||||
|
filename := "example"
|
||||||
|
g := ReadGraphFile(filename)
|
||||||
|
|
||||||
|
g.SaveAsMermaid("example-graph.mmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComponentOnInitial(t *testing.T) {
|
||||||
|
// should be all nodes
|
||||||
|
filename := "example"
|
||||||
|
g := ReadGraphFile(filename)
|
||||||
|
comp := g.ComponentFrom("bvb")
|
||||||
|
t.Logf("got component %+v", comp)
|
||||||
|
if comp.Cardinality() != len(g.Nodes) {
|
||||||
|
t.Errorf("should have same size!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComponentOnMini(t *testing.T) {
|
||||||
|
// should be all nodes
|
||||||
|
filename := "example2"
|
||||||
|
g := ReadGraphFile(filename)
|
||||||
|
comp := g.ComponentFrom("jqt")
|
||||||
|
t.Logf("got component %+v", comp)
|
||||||
|
if comp.Cardinality() == len(g.Nodes) {
|
||||||
|
t.Errorf("should have different size!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveAllCycles(t *testing.T) {
|
||||||
|
filename := "example"
|
||||||
|
g := ReadGraphFile(filename)
|
||||||
|
g.SaveAsMermaid("example-before-removing.mmd")
|
||||||
|
edges := g.RemoveAllCycles()
|
||||||
|
t.Logf("removed edges %+v", edges)
|
||||||
|
g.SaveAsMermaid("example-after-removing.mmd")
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
#+title: Notes
|
||||||
|
* ok, not so simple
|
||||||
|
'how to find 3 edges which if removed split graph into 2 components'
|
||||||
|
|
||||||
|
i guess i could find all cycles?
|
||||||
|
and then what?
|
||||||
|
|
||||||
|
then if i have graph without cycles and all of the removed edges,
|
||||||
|
maybe i can figure out how to remove one edge, to make two components, and all (k - 2) edges back in a way that would not connect those components.
|
||||||
|
|
||||||
|
well, the cycles maybe should have been broken elsewhere?
|
||||||
|
for most cycles - it doesn't matter.
|
||||||
|
|
||||||
|
for cycles that break into 2 components, does it?
|
||||||
|
|
||||||
|
ok. if i have the no-cycle graph.
|
||||||
|
then we can maybe start searching for 'third' edge to remove.
|
||||||
|
|
||||||
|
i remove it, i get two components.
|
||||||
|
then as a test - exactly two edges when added should connect those components.
|
||||||
|
if that happens - i have an answer
|
||||||
|
|
||||||
|
sounds good
|
||||||
|
** making code to get components.
|
||||||
|
now.
|
||||||
|
** make method to remove all cycles.
|
||||||
|
save the removed edges to slice
|
||||||
|
|
||||||
|
i guess could be a method that modifies the graph and returns all removed edges.
|
||||||
|
and test that
|
6
main.go
6
main.go
|
@ -4,15 +4,15 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sunshine.industries/aoc2023/day24"
|
"sunshine.industries/aoc2023/day25"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
log.Print("> starting run:")
|
log.Print("> starting run:")
|
||||||
|
|
||||||
result := day24.Run()
|
result := day25.Run()
|
||||||
log.Printf("\n\nday24 result: %d\n****\n", result)
|
log.Printf("\n\nday25 result: %d\n****\n", result)
|
||||||
endTime := time.Now()
|
endTime := time.Now()
|
||||||
diff := endTime.Sub(startTime)
|
diff := endTime.Sub(startTime)
|
||||||
log.Printf("execution took %s", diff.String())
|
log.Printf("execution took %s", diff.String())
|
||||||
|
|
Loading…
Reference in New Issue