In previous years, Advent of Code has had several problems dealing with items in a grid. After trying various options of storing this information, the method I have landed on is a dictionary (key value pair) where the key contains a tuple (pair) of coordinates. This allows me to quickly look up a value given a coordinate. In Gleam, this is even more advantageous over a 2-D (nested) list because Gleam does not have any function that lets you easily access an item in a list by index.
The other thing this approach encourages is treating more of the program as data. This is probably the functional programming concept that has had the most impact on the way I think. Instead of trying to figure out what my program should do, I tend to think about how I need to transform my inputs into outputs. (Advent of Code is particularly suited for this way of thinking because the whole point is to produce some integer from an input file.)
Instead of thinking "I need to check each direction around each 'X' that I find one at a time", then writing code to do that, I represent each direction as data and loop the same logic over it. I know that fold
(sometimes called reduce) can be hard to get used to, but after a while, it has really clicked in my head, and I tend to reach for fold
over recursion or any other looping method.
Alright, I guess that's enough blabber. I try and add a little interesting discussion relevant to the puzzle in some way. The puzzle itself is rather straightforward without a whole lot of "tricks" going on:
import gleam/int
import gleam/io
import gleam/list
import gleam/string
import gleam/dict.{type Dict, get, insert}
import simplifile as file
const example = "
MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX
"
pub fn main() {
let assert Ok(input) = file.read("input")
let assert 18 = part1(example)
let assert 9 = part2(example)
part1(input) |> int.to_string |> io.println
part2(input) |> int.to_string |> io.println
}
fn parse_word_search(input: String) -> Dict(#(Int, Int), String) {
input
|> string.trim()
|> string.split("\n")
|> list.index_fold(dict.new(), fn(dict, line, x) {
string.to_graphemes(line)
|> list.index_fold(dict, fn(dict, char, y) {
insert(dict, #(x, y), char)
})
})
}
fn xmas_search(position, ws) {
let #(x, y) = position
let search_space = [
#(#(x, y - 1), #(x, y - 2), #(x, y - 3)),
#(#(x - 1, y), #(x - 2, y), #(x - 3, y)),
#(#(x, y + 1), #(x, y + 2), #(x, y + 3)),
#(#(x + 1, y), #(x + 2, y), #(x + 3, y)),
#(#(x - 1, y - 1), #(x - 2, y - 2), #(x - 3, y - 3)),
#(#(x + 1, y - 1), #(x + 2, y - 2), #(x + 3, y - 3)),
#(#(x - 1, y + 1), #(x - 2, y + 2), #(x - 3, y + 3)),
#(#(x + 1, y + 1), #(x + 2, y + 2), #(x + 3, y + 3))
]
search_space
|> list.fold(0, fn(count, direction) {
let #(m, a, s) = direction
case get(ws, m), get(ws, a), get(ws, s) {
Ok("M"), Ok("A"), Ok("S") -> count + 1
_, _, _ -> count
}
})
}
fn part1(input: String) -> Int {
let word_search = parse_word_search(input)
dict.fold(word_search, 0, fn(count, position, char) {
case char {
"X" -> count + xmas_search(position, word_search)
_ -> count
}
})
}
fn x_mas_search(position, ws) {
let #(x, y) = position
let tl = #(x - 1, y - 1)
let tr = #(x - 1, y + 1)
let br = #(x + 1, y + 1)
let bl = #(x + 1, y - 1)
case get(ws, tl), get(ws, tr), get(ws, br), get(ws, bl) {
Ok("M"), Ok("M"), Ok("S"), Ok("S") -> 1
Ok("S"), Ok("M"), Ok("M"), Ok("S") -> 1
Ok("S"), Ok("S"), Ok("M"), Ok("M") -> 1
Ok("M"), Ok("S"), Ok("S"), Ok("M") -> 1
_, _, _, _ -> 0
}
}
fn part2(input: String) -> Int {
let word_search = parse_word_search(input)
dict.fold(word_search, 0, fn(count, position, char) {
case char {
"A" -> count + x_mas_search(position, word_search)
_ -> count
}
})
}
Top comments (0)