day25: initial graph stuff,

with cycles removal not working
This commit is contained in:
efim 2023-12-25 07:53:17 +00:00
parent 665987395f
commit 9177d35caf
12 changed files with 476 additions and 3 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/.direnv/
/.go
input
*.png

10
day25/Snowerload.go Normal file
View File

@ -0,0 +1,10 @@
package day25
import (
"fmt"
)
func Run() int {
fmt.Println("time to wrap things up")
return 0
}

13
day25/example Normal file
View File

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

View File

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

View File

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

34
day25/example-graph.mmd Normal file
View File

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

3
day25/example2 Normal file
View File

@ -0,0 +1,3 @@
jqt: rhn nvd rsh
rsh: frs pzl lsr
xhk: hfx

254
day25/graph.go Normal file
View File

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

64
day25/graph_test.go Normal file
View File

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

30
day25/notes.org Normal file
View File

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

View File

@ -4,15 +4,15 @@ import (
"log"
"time"
"sunshine.industries/aoc2023/day24"
"sunshine.industries/aoc2023/day25"
)
func main() {
startTime := time.Now()
log.Print("> starting run:")
result := day24.Run()
log.Printf("\n\nday24 result: %d\n****\n", result)
result := day25.Run()
log.Printf("\n\nday25 result: %d\n****\n", result)
endTime := time.Now()
diff := endTime.Sub(startTime)
log.Printf("execution took %s", diff.String())