Skip to content

Commit 0b865bb

Browse files
committed
finished day 23
1 parent f4c5f8f commit 0b865bb

File tree

4 files changed

+245
-2
lines changed

4 files changed

+245
-2
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Advent of Code 2024
22

33
[![Tests](https://github.com/devries/advent_of_code_2024/actions/workflows/test.yml/badge.svg)](https://github.com/devries/advent_of_code_2024/actions/workflows/test.yml)
4-
[![Stars: 44](https://img.shields.io/badge/⭐_Stars-44-yellow)](https://adventofcode.com/2024)
4+
[![Stars: 46](https://img.shields.io/badge/⭐_Stars-46-yellow)](https://adventofcode.com/2024)
55

66
This year I am going to try to do Advent of Code in [Gleam](https://gleam.run).
77
To run a day's problems use the command
@@ -41,3 +41,4 @@ information.
4141
- [Day 20](https://adventofcode.com/2024/day/20): [⭐ ⭐ solution](src/day20/solution.gleam)
4242
- [Day 21](https://adventofcode.com/2024/day/21): [⭐ ⭐ solution](src/day21/solution.gleam)
4343
- [Day 22](https://adventofcode.com/2024/day/22): [⭐ ⭐ solution](src/day22/solution.gleam)
44+
- [Day 23](https://adventofcode.com/2024/day/23): [⭐ ⭐ solution](src/day23/solution.gleam)

inputs

src/day23/solution.gleam

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import gleam/dict.{type Dict}
2+
import gleam/int
3+
import gleam/io
4+
import gleam/list
5+
import gleam/option.{None, Some}
6+
import gleam/result
7+
import gleam/set.{type Set}
8+
import gleam/string
9+
import internal/aoc_utils
10+
11+
pub fn main() {
12+
let filename = "inputs/day23.txt"
13+
14+
let lines_result = aoc_utils.read_lines(from: filename)
15+
case lines_result {
16+
Ok(lines) -> {
17+
// If the file was converting into a list of lines
18+
// successfully then run each part of the problem
19+
aoc_utils.run_part_and_print("Part 1", fn() { solve_p1(lines) })
20+
aoc_utils.run_part_and_print("Part 2", fn() { solve_p2(lines) })
21+
}
22+
Error(_) -> io.println("Error reading file")
23+
}
24+
}
25+
26+
// Part 1
27+
pub fn solve_p1(lines: List(String)) -> Result(String, String) {
28+
let conn = parse(lines)
29+
30+
dict.keys(conn)
31+
|> list.filter(fn(item) { string.starts_with(item, "t") })
32+
|> list.map(find_cycles(_, 3, conn))
33+
|> list.fold(set.new(), fn(combined, single) { set.union(combined, single) })
34+
|> set.size
35+
|> int.to_string
36+
|> Ok
37+
}
38+
39+
// Part 2
40+
pub fn solve_p2(lines: List(String)) -> Result(String, String) {
41+
let conn = parse(lines)
42+
43+
let cycles =
44+
dict.keys(conn)
45+
|> list.map(find_cycles(_, 3, conn))
46+
|> list.fold(set.new(), fn(combined, single) { set.union(combined, single) })
47+
|> set.to_list
48+
49+
let connset =
50+
dict.to_list(conn)
51+
|> list.map(fn(tup) { #(tup.0, set.from_list(tup.1)) })
52+
|> dict.from_list
53+
54+
largest_group(cycles, connset, set.new())
55+
|> set.to_list
56+
|> list.sort(string.compare)
57+
|> string.join(",")
58+
|> Ok
59+
}
60+
61+
fn parse(lines: List(String)) -> Dict(String, List(String)) {
62+
lines
63+
|> list.map(string.split(_, "-"))
64+
|> list.fold(dict.new(), fn(connections, values) {
65+
case values {
66+
[a, b] -> {
67+
let alist = dict.get(connections, a) |> result.unwrap([])
68+
let blist = dict.get(connections, b) |> result.unwrap([])
69+
70+
let connections = dict.insert(connections, a, [b, ..alist])
71+
dict.insert(connections, b, [a, ..blist])
72+
}
73+
_ -> connections
74+
}
75+
})
76+
}
77+
78+
fn find_cycles(
79+
start: String,
80+
length: Int,
81+
conn: Dict(String, List(String)),
82+
) -> Set(Set(String)) {
83+
find_cycles_acc([start], length, conn, start, set.new(), set.new())
84+
}
85+
86+
fn find_cycles_acc(
87+
search: List(String),
88+
length: Int,
89+
conn: Dict(String, List(String)),
90+
end: String,
91+
prev: Set(String),
92+
found: Set(Set(String)),
93+
) -> Set(Set(String)) {
94+
case search {
95+
[first, ..rest] -> {
96+
let next =
97+
dict.get(conn, first)
98+
|> result.unwrap([])
99+
100+
case length == 1 {
101+
True -> {
102+
// Add found set where next element is the end element
103+
case list.contains(next, end) {
104+
True ->
105+
find_cycles_acc(
106+
rest,
107+
length,
108+
conn,
109+
end,
110+
prev,
111+
set.insert(found, set.insert(prev, first)),
112+
)
113+
False -> find_cycles_acc(rest, length, conn, end, prev, found)
114+
}
115+
}
116+
False -> {
117+
// continue traversing the cycle, ignoring where we've already been
118+
let found =
119+
find_cycles_acc(
120+
list.filter(next, fn(item) { !set.contains(prev, item) }),
121+
length - 1,
122+
conn,
123+
end,
124+
set.insert(prev, first),
125+
found,
126+
)
127+
find_cycles_acc(rest, length, conn, end, prev, found)
128+
}
129+
}
130+
}
131+
[] -> found
132+
}
133+
}
134+
135+
// Starting from a group, find another computer which is connected to every
136+
// computer in the group, then continue to grow from that larger group until
137+
// no further systems can be added.
138+
fn grow(group: Set(String), connset: Dict(String, Set(String))) -> Set(String) {
139+
// find largest set containing group
140+
let candidates =
141+
group
142+
|> set.fold(None, fn(u, vertex) {
143+
case u {
144+
None -> {
145+
dict.get(connset, vertex)
146+
|> result.unwrap(set.new())
147+
|> Some
148+
}
149+
Some(u) ->
150+
set.intersection(
151+
u,
152+
dict.get(connset, vertex) |> result.unwrap(set.new()),
153+
)
154+
|> Some
155+
}
156+
})
157+
|> option.unwrap(set.new())
158+
|> set.difference(group)
159+
|> set.to_list
160+
|> list.first
161+
162+
case candidates {
163+
Ok(c) -> grow(set.insert(group, c), connset)
164+
Error(Nil) -> group
165+
}
166+
}
167+
168+
// If a cycle is contained entirely within the group we found, it will only
169+
// grow to be that same group, so we can ignore it and test other cycles.
170+
fn prune(group: Set(String), cycles: List(Set(String))) -> List(Set(String)) {
171+
// remove cycles within group
172+
list.filter(cycles, fn(c) { !set.is_subset(c, group) })
173+
}
174+
175+
// Start growing from each cycle to find the largest group
176+
// pruning searched cycles at each step.
177+
fn largest_group(
178+
cycles: List(Set(String)),
179+
connset: Dict(String, Set(String)),
180+
largest: Set(String),
181+
) -> Set(String) {
182+
// Search all cycles for largest group
183+
case cycles {
184+
[first, ..rest] -> {
185+
let candidate = grow(first, connset)
186+
let remaining = prune(candidate, rest)
187+
case set.size(candidate) > set.size(largest) {
188+
True -> largest_group(remaining, connset, candidate)
189+
False -> largest_group(remaining, connset, largest)
190+
}
191+
}
192+
[] -> largest
193+
}
194+
}

test/day23_test.gleam

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import day23/solution
2+
import gleam/string
3+
import gleeunit/should
4+
5+
const testinput = "kh-tc
6+
qp-kh
7+
de-cg
8+
ka-co
9+
yn-aq
10+
qp-ub
11+
cg-tb
12+
vc-aq
13+
tb-ka
14+
wh-tc
15+
yn-cg
16+
kh-ub
17+
ta-co
18+
de-co
19+
tc-td
20+
tb-wq
21+
wh-td
22+
ta-ka
23+
td-qp
24+
aq-cg
25+
wq-ub
26+
ub-vc
27+
de-ta
28+
wq-aq
29+
wq-vc
30+
wh-yn
31+
ka-de
32+
kh-ta
33+
co-tc
34+
wh-qp
35+
tb-vc
36+
td-yn"
37+
38+
pub fn part1_test() {
39+
let lines = string.split(testinput, "\n")
40+
solution.solve_p1(lines)
41+
|> should.equal(Ok("7"))
42+
}
43+
44+
pub fn part2_test() {
45+
let lines = string.split(testinput, "\n")
46+
solution.solve_p2(lines)
47+
|> should.equal(Ok("co,de,ka,ta"))
48+
}

0 commit comments

Comments
 (0)