Skip to content

Commit 05460f7

Browse files
committed
Day 4: Over engineered!
1 parent 12b3a6b commit 05460f7

File tree

4 files changed

+437
-0
lines changed

4 files changed

+437
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Each day will be setup as a separate folder.
1010
- [Day 1](/day-1/) - Historian Hysteria
1111
- [Day 2](/day-2/) - Red-Nosed Reports
1212
- [Day 3](/day-3/) - Mull It Over
13+
- [Day 4](/day-4/) - Ceres Search
1314

1415
## Environment Setup
1516

day-4/README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Day 4 - Ceres Search
2+
3+
## Part 1
4+
5+
"Looks like the Chief's not here. Next!" One of The Historians pulls out a device and pushes the only button on it. After a brief flash, you recognize the interior of the Ceres monitoring station!
6+
7+
As the search for the Chief continues, a small Elf who lives on the station tugs on your shirt; she'd like to know if you could help her with her word search (your puzzle input). She only has to find one word: XMAS.
8+
9+
This word search allows words to be horizontal, vertical, diagonal, written backwards, or even overlapping other words. It's a little unusual, though, as you don't merely need to find one instance of XMAS - you need to find all of them. Here are a few ways XMAS might appear, where irrelevant characters have been replaced with .:
10+
11+
```
12+
..X...
13+
.SAMX.
14+
.A..A.
15+
XMAS.S
16+
.X....
17+
```
18+
19+
The actual word search will be full of letters instead. For example:
20+
21+
```
22+
MMMSXXMASM
23+
MSAMXMSMSA
24+
AMXSXMAAMM
25+
MSAMASMSMX
26+
XMASAMXAMM
27+
XXAMMXXAMA
28+
SMSMSASXSS
29+
SAXAMASAAA
30+
MAMMMXMMMM
31+
MXMXAXMASX
32+
```
33+
34+
In this word search, XMAS occurs a total of 18 times; here's the same word search again, but where letters not involved in any XMAS have been replaced with .:
35+
36+
```
37+
....XXMAS.
38+
.SAMXMS...
39+
...S..A...
40+
..A.A.MS.X
41+
XMASAMX.MM
42+
X.....XA.A
43+
S.S.S.S.SS
44+
.A.A.A.A.A
45+
..M.M.M.MM
46+
.X.X.XMASX
47+
```
48+
49+
Take a look at the little Elf's word search. How many times does XMAS appear?
50+
51+
## Part 2
52+
53+
The Elf looks quizzically at you. Did you misunderstand the assignment?
54+
55+
Looking for the instructions, you flip over the word search to find that this isn't actually an XMAS puzzle; it's an X-MAS puzzle in which you're supposed to find two MAS in the shape of an X. One way to achieve that is like this:
56+
57+
```
58+
M.S
59+
.A.
60+
M.S
61+
```
62+
63+
Irrelevant characters have again been replaced with . in the above diagram. Within the X, each MAS can be written forwards or backwards.
64+
65+
Here's the same example from before, but this time all of the X-MASes have been kept instead:
66+
67+
```
68+
.M.S......
69+
..A..MSMS.
70+
.M.S.MAA..
71+
..A.ASMSM.
72+
.M.S.M....
73+
..........
74+
S.S.S.S.S.
75+
.A.A.A.A..
76+
M.M.M.M.M.
77+
..........
78+
```
79+
80+
In this example, an X-MAS appears 9 times.
81+
82+
Flip the word search from the instructions back over to the word search side and try again. How many times does an X-MAS appear?

day-4/main.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package main
2+
3+
import (
4+
_ "embed"
5+
"flag"
6+
"fmt"
7+
"log"
8+
"strings"
9+
10+
file "github.com/shaunburdick/advent-of-code-2024/lib"
11+
)
12+
13+
var input string
14+
15+
func init() {
16+
// do this in init (not main) so test file has same input
17+
inputFile, err := file.LoadRelativeFile("input.txt")
18+
if err != nil {
19+
log.Println(err)
20+
}
21+
22+
input = strings.TrimRight(inputFile, "\n")
23+
}
24+
25+
func main() {
26+
var part int
27+
flag.IntVar(&part, "part", 1, "part 1 or 2")
28+
flag.Parse()
29+
fmt.Println("Running part", part)
30+
31+
if part == 1 {
32+
ans := part1(input)
33+
fmt.Println("Output:", ans)
34+
} else {
35+
ans := part2(input)
36+
fmt.Println("Output:", ans)
37+
}
38+
}
39+
40+
const MAS = "MAS"
41+
const XMAS = "X" + MAS
42+
43+
func part1(input string) int {
44+
parsed := parseInput(input)
45+
foundWords := 0
46+
// start traversing the grid
47+
for y, row := range parsed {
48+
for x := range row {
49+
coord := Coords{x, y}
50+
foundWords += xmasSearch(parsed, "", coord, AllDirections)
51+
}
52+
}
53+
54+
return foundWords
55+
}
56+
57+
func part2(input string) int {
58+
parsed := parseInput(input)
59+
grid := Grid{parsed}
60+
foundMas := 0
61+
// start traversing the grid
62+
for y, row := range grid.Data {
63+
if y == 0 || y == len(grid.Data)-1 {
64+
// skip edges
65+
continue
66+
}
67+
68+
for x, char := range row {
69+
if x == 0 || x == len(row)-1 {
70+
// skip edges
71+
continue
72+
}
73+
if char == 'A' {
74+
coord := Coords{x, y}
75+
wing1 := string(grid.CharAt(coord.Direction(NorthWest))) + "A" + string(grid.CharAt(coord.Direction(SouthEast)))
76+
wing2 := string(grid.CharAt(coord.Direction(NorthEast))) + "A" + string(grid.CharAt(coord.Direction(SouthWest)))
77+
78+
if (wing1 == "MAS" || wing1 == "SAM") && (wing2 == "MAS" || wing2 == "SAM") {
79+
foundMas += 1
80+
}
81+
}
82+
}
83+
}
84+
85+
return foundMas
86+
}
87+
88+
type Grid struct {
89+
Data []string
90+
}
91+
92+
func (g Grid) CharAt(c Coords) rune {
93+
return rune(g.Data[c.y][c.x])
94+
}
95+
96+
type Coords struct {
97+
x int
98+
y int
99+
}
100+
101+
type Direction int
102+
103+
const (
104+
NorthWest Direction = iota
105+
North
106+
NorthEast
107+
West
108+
East
109+
SouthWest
110+
South
111+
SouthEast
112+
)
113+
114+
var AllDirections []Direction = []Direction{
115+
NorthWest,
116+
North,
117+
NorthEast,
118+
West,
119+
East,
120+
SouthWest,
121+
South,
122+
SouthEast,
123+
}
124+
125+
// Generate a list of valid coordinates from the existing one
126+
// Will only return valid positive values
127+
func (c Coords) Directions() []Coords {
128+
directions := []Coords{}
129+
130+
for _, dir := range AllDirections {
131+
newCoord := c.Direction(dir)
132+
// check for bounds
133+
if newCoord.x >= 0 && newCoord.y >= 0 {
134+
directions = append(directions, newCoord)
135+
}
136+
}
137+
138+
return directions
139+
}
140+
141+
func (c Coords) Direction(d Direction) Coords {
142+
switch d {
143+
case NorthWest:
144+
return Coords{x: c.x - 1, y: c.y - 1}
145+
case North:
146+
return Coords{x: c.x, y: c.y - 1}
147+
case NorthEast:
148+
return Coords{x: c.x + 1, y: c.y - 1}
149+
case West:
150+
return Coords{x: c.x - 1, y: c.y}
151+
case East:
152+
return Coords{x: c.x + 1, y: c.y}
153+
case SouthWest:
154+
return Coords{x: c.x - 1, y: c.y + 1}
155+
case South:
156+
return Coords{x: c.x, y: c.y + 1}
157+
case SouthEast:
158+
return Coords{x: c.x + 1, y: c.y + 1}
159+
default:
160+
return c
161+
}
162+
}
163+
164+
func xmasSearch(grid []string, currentWord string, coords Coords, directions []Direction) int {
165+
foundWords := 0
166+
newWord := currentWord + string(grid[coords.y][coords.x])
167+
if newWord == XMAS {
168+
// we match!
169+
return 1
170+
} else if len(newWord) >= len(XMAS) || !strings.HasPrefix(XMAS, newWord) {
171+
// we don't match!
172+
return 0
173+
} else {
174+
// we partially match, keep looking!
175+
for _, direction := range directions {
176+
newCoord := coords.Direction(direction)
177+
// check for bounds
178+
if newCoord.x >= 0 && newCoord.y >= 0 && newCoord.y < len(grid) && newCoord.x < len(grid[newCoord.y]) {
179+
foundWords += xmasSearch(grid, newWord, newCoord, []Direction{direction})
180+
}
181+
}
182+
}
183+
184+
return foundWords
185+
}
186+
187+
func parseInput(input string) (ans []string) {
188+
return strings.Split(input, "\n")
189+
}

0 commit comments

Comments
 (0)