Skip to content
This repository was archived by the owner on Dec 28, 2024. It is now read-only.

Commit 7a565d1

Browse files
committed
Add solution day 23 part 1 and 2
1 parent 7b84516 commit 7a565d1

File tree

6 files changed

+221
-2
lines changed

6 files changed

+221
-2
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ automatically rebuilt and redeployed every time the `common` module or their own
5555
| 07 | ⭐ ⭐ | 20 | ⭐ ⭐ |
5656
| 08 | ⭐ ⭐ | 21 | |
5757
| 09 | ⭐ ⭐ | 22 | ⭐ ⭐ |
58-
| 10 | ⭐ ⭐ | 23 | |
58+
| 10 | ⭐ ⭐ | 23 | ⭐ ⭐ |
5959
| 11 | ⭐ ⭐ | 24 | |
6060
| 12 | ⭐ ⭐ | 25 | |
6161
| 13 | ⭐ ⭐ | | |

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/terminalnode/adventofcode2024
33
go 1.23.3
44

55
require (
6+
gonum.org/v1/gonum v0.15.1
67
google.golang.org/grpc v1.68.1
78
google.golang.org/protobuf v1.35.2
89
)

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
22
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
33
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
44
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
5+
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
6+
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
57
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
68
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
79
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
810
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
911
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
1012
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
13+
gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=
14+
gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=
1115
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
1216
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
1317
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=

solutions/day23/bron-kerbosch.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package main
2+
3+
import (
4+
"gonum.org/v1/gonum/graph/simple"
5+
)
6+
7+
// I believe something like this is implemented in gonum already, seems
8+
// like it anyway, but I've already cheated on the graph DS so might at
9+
// least implement the algorithm myself.
10+
11+
func bronKerbosch(
12+
r []int64,
13+
p []int64,
14+
g *simple.UndirectedGraph,
15+
) [][]int64 {
16+
// If P is empty, this is a maximal click
17+
if len(p) == 0 {
18+
return [][]int64{r}
19+
}
20+
out := make([][]int64, 0, len(p))
21+
x := make(map[int64]bool)
22+
23+
existInP := make(map[int64]bool)
24+
for _, pEntry := range p {
25+
existInP[pEntry] = true
26+
}
27+
28+
for _, v := range p {
29+
skipV := false
30+
31+
fromV := g.From(v)
32+
newP := make([]int64, 0, fromV.Len())
33+
for fromV.Next() {
34+
id := fromV.Node().ID()
35+
if !existInP[id] {
36+
continue
37+
}
38+
39+
if x[id] {
40+
// X set would not be empty, and thus can't return anything
41+
skipV = true
42+
break
43+
}
44+
newP = append(newP, id)
45+
}
46+
47+
if !skipV {
48+
newR := append([]int64{v}, r...)
49+
out = append(out, bronKerbosch(newR, newP, g)...)
50+
}
51+
x[v] = true
52+
}
53+
54+
return out
55+
}

solutions/day23/main.go

+63-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,71 @@
11
package main
22

33
import (
4+
"fmt"
45
"github.com/terminalnode/adventofcode2024/common"
6+
"slices"
7+
"strings"
58
)
69

710
func main() {
8-
common.Setup(23, nil, nil)
11+
common.Setup(23, part1, part2)
12+
}
13+
14+
func part1(
15+
input string,
16+
) string {
17+
cm, err := parse(input)
18+
if err != nil {
19+
return fmt.Sprintf("Failed to parse input: %v", err)
20+
}
21+
22+
visited := make(map[string]bool)
23+
for name1, conn1 := range cm {
24+
for name2, conn2 := range conn1.connMap {
25+
for name3, conn3 := range conn2.connMap {
26+
if conn3.connMap[name1] != nil {
27+
if name1[0] == 't' || name2[0] == 't' || name3[0] == 't' {
28+
names := []string{name1, name2, name3}
29+
slices.Sort(names)
30+
visited[strings.Join(names, "-")] = true
31+
}
32+
}
33+
}
34+
}
35+
}
36+
37+
return fmt.Sprintf("Clusters of 3 with computers starting with t: %d", len(visited))
38+
}
39+
40+
func part2(
41+
input string,
42+
) string {
43+
graph, intToName, err := parseGonumGraph(input)
44+
if err != nil {
45+
return fmt.Sprintf("Failed to parse input: %v", err)
46+
}
47+
48+
nodes := graph.Nodes()
49+
p := make([]int64, 0, nodes.Len())
50+
for nodes.Next() {
51+
p = append(p, nodes.Node().ID())
52+
}
53+
54+
cliques := bronKerbosch([]int64{}, p, graph)
55+
56+
var biggest []int64
57+
for _, c := range cliques {
58+
if len(c) > len(biggest) {
59+
biggest = c
60+
}
61+
}
62+
63+
names := make([]string, len(biggest))
64+
for i, n := range biggest {
65+
names[i] = intToName[n]
66+
}
67+
slices.Sort(names)
68+
name := strings.Join(names, ",")
69+
70+
return fmt.Sprintf("Password: %s", name)
971
}

solutions/day23/parse.go

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"gonum.org/v1/gonum/graph"
6+
"gonum.org/v1/gonum/graph/simple"
7+
"strings"
8+
)
9+
10+
type connection struct {
11+
name nodeName
12+
connMap connMap
13+
}
14+
type nodeName = string
15+
type connMap map[nodeName]*connection
16+
17+
type textNode struct {
18+
id int64
19+
name string
20+
}
21+
22+
func (n textNode) ID() int64 {
23+
return n.id
24+
}
25+
26+
func parseGonumGraph(
27+
input string,
28+
) (*simple.UndirectedGraph, map[int64]string, error) {
29+
out := simple.NewUndirectedGraph()
30+
visited := make(map[string]graph.Node)
31+
intToName := make(map[int64]string)
32+
33+
idCounter := int64(0)
34+
35+
lines := strings.Split(input, "\n")
36+
for _, line := range lines {
37+
split := strings.Split(line, "-")
38+
if len(split) != 2 {
39+
return out, intToName, fmt.Errorf("length after splitting '%s' should be 2, was %d", line, len(split))
40+
}
41+
name1 := split[0]
42+
name2 := split[1]
43+
44+
if visited[name1] == nil {
45+
idCounter++
46+
visited[name1] = simple.Node(idCounter)
47+
intToName[idCounter] = name1
48+
out.AddNode(visited[name1])
49+
}
50+
if visited[name2] == nil {
51+
idCounter++
52+
visited[name2] = simple.Node(idCounter)
53+
intToName[idCounter] = name2
54+
out.AddNode(visited[name2])
55+
}
56+
out.SetEdge(simple.Edge{F: visited[name1], T: visited[name2]})
57+
}
58+
59+
return out, intToName, nil
60+
}
61+
62+
func parse(
63+
input string,
64+
) (connMap, error) {
65+
lines := strings.Split(input, "\n")
66+
out := make(connMap)
67+
68+
for _, line := range lines {
69+
split := strings.Split(line, "-")
70+
if len(split) != 2 {
71+
return out, fmt.Errorf("length after splitting '%s' should be 2, was %d", line, len(split))
72+
}
73+
out.addConnection(split[0], split[1])
74+
}
75+
76+
return out, nil
77+
}
78+
79+
func (m connMap) addConnection(
80+
n1 nodeName,
81+
n2 nodeName,
82+
) {
83+
node1 := m.getOrCreateNode(n1)
84+
node2 := m.getOrCreateNode(n2)
85+
node1.connMap[n2] = node2
86+
node2.connMap[n1] = node1
87+
}
88+
89+
func (m connMap) getOrCreateNode(
90+
name nodeName,
91+
) *connection {
92+
if n, ok := m[name]; ok {
93+
return n
94+
}
95+
m[name] = &connection{name: name, connMap: make(connMap)}
96+
return m[name]
97+
}

0 commit comments

Comments
 (0)