I've had the damn song stuck in my head since I saw this post.
The first part was simple enough. Adding in the 3 remaining tilt methods for star 2 was also simple enough, and worked just how I figured it would. Tried the brute force solution first, but realized it was going to take a ridiculous amount of time and went back to figure out an algorithm. It was simple enough to guess that it would hit a point where it just repeats infinitely, but actually coding out the math to extrapolate that took way more time than I want to admit. Not sure why I struggled with it so much, but after some pen and paper mathing, I essentially got there. Ended up having to subract 1
from this calculation, and either I'm just missing something or am way too tired, because I don't know why it's one less than what I thought it would be, but it works so who am I to complain.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day14.rs
use crate::Solver;
#[derive(Debug)]
struct PlatformMap {
tiles: Vec>,
}
impl PlatformMap {
fn from(input: &str) -> PlatformMap {
PlatformMap {
tiles: input.lines().map(|line| line.chars().collect()).collect(),
}
}
fn load(&self) -> usize {
self.tiles
.iter()
.enumerate()
.map(|(row, tiles)| {
tiles.iter().filter(|tile| *tile == &'O').count() * (self.tiles.len() - row)
})
.sum()
}
fn tilt_north(&mut self) {
for row in 1..self.tiles.len() {
for col in 0..self.tiles[0].len() {
if self.tiles[row][col] != 'O' {
continue;
}
let mut new_row = row;
for check_row in (0..row).rev() {
if self.tiles[check_row][col] == '.' {
new_row = check_row;
} else {
break;
}
}
self.tiles[row][col] = '.';
self.tiles[new_row][col] = 'O';
}
}
}
fn tilt_west(&mut self) {
for col in 1..self.tiles[0].len() {
for row in 0..self.tiles.len() {
if self.tiles[row][col] != 'O' {
continue;
}
let mut new_col = col;
for check_col in (0..col).rev() {
if self.tiles[row][check_col] == '.' {
new_col = check_col;
} else {
break;
}
}
self.tiles[row][col] = '.';
self.tiles[row][new_col] = 'O';
}
}
}
fn tilt_south(&mut self) {
for row in (0..(self.tiles.len() - 1)).rev() {
for col in 0..self.tiles[0].len() {
if self.tiles[row][col] != 'O' {
continue;
}
let mut new_row = row;
for check_row in (row + 1)..self.tiles.len() {
if self.tiles[check_row][col] == '.' {
new_row = check_row;
} else {
break;
}
}
self.tiles[row][col] = '.';
self.tiles[new_row][col] = 'O';
}
}
}
fn tilt_east(&mut self) {
for col in (0..(self.tiles[0].len() - 1)).rev() {
for row in 0..self.tiles.len() {
if self.tiles[row][col] != 'O' {
continue;
}
let mut new_col = col;
for check_col in (col + 1)..self.tiles[0].len() {
if self.tiles[row][check_col] == '.' {
new_col = check_col;
} else {
break;
}
}
self.tiles[row][col] = '.';
self.tiles[row][new_col] = 'O';
}
}
}
}
pub struct Day14;
impl Solver for Day14 {
fn star_one(&self, input: &str) -> String {
let mut platform_map = PlatformMap::from(input);
platform_map.tilt_north();
platform_map.load().to_string()
}
fn star_two(&self, input: &str) -> String {
let mut platform_map = PlatformMap::from(input);
let mut map_history: Vec>> = vec![];
for index in 0..1_000_000_000 {
platform_map.tilt_north();
platform_map.tilt_west();
platform_map.tilt_south();
platform_map.tilt_east();
if let Some(repeat_start) = map_history
.iter()
.position(|tiles| tiles == &platform_map.tiles)
{
let repeat_length = index - repeat_start;
let delta = (1_000_000_000 - repeat_start) % repeat_length;
let solution_index = repeat_start + delta - 1;
return PlatformMap {
tiles: map_history[solution_index].clone(),
}
.load()
.to_string();
}
map_history.push(platform_map.tiles.clone());
}
platform_map.load().to_string()
}
}
Had to take a couple days off, but this was a nice one to come back to. Will have to find some time today to go back and do one or two of the 3 that I missed. I don't have much to say about this one - I had an idea almost immediately and it worked out without much struggle. There's probably some cleaner ways to write parts of this, but I'm not too disappointed with how it turned out.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day15.rs
use crate::Solver;
use std::collections::HashMap;
#[derive(Debug)]
struct Lens {
label: String,
focal_length: u32,
}
fn hash_algorithm(input: &str) -> u32 {
input
.chars()
.fold(0, |acc, ch| (acc + ch as u32) * 17 % 256)
}
pub struct Day15;
impl Solver for Day15 {
fn star_one(&self, input: &str) -> String {
input
.trim_end()
.split(',')
.map(hash_algorithm)
.sum::()
.to_string()
}
fn star_two(&self, input: &str) -> String {
let mut boxes: HashMap> = HashMap::new();
for instruction in input.trim_end().split(',') {
let (label, focal_length) = instruction
.split_once(|ch| char::is_ascii_punctuation(&ch))
.unwrap();
let box_number = hash_algorithm(label);
let lenses = boxes.entry(box_number).or_insert(vec![]);
if focal_length == "" {
lenses.retain(|lens| lens.label != label);
continue;
}
let new_lens = Lens {
label: label.to_string(),
focal_length: focal_length.parse().unwrap(),
};
if let Some(lens_index) = lenses.iter().position(|lens| lens.label == new_lens.label) {
lenses[lens_index].focal_length = new_lens.focal_length;
} else {
lenses.push(new_lens);
}
}
boxes
.iter()
.map(|(box_number, lenses)| {
lenses
.iter()
.enumerate()
.map(|(lens_index, lens)| {
(box_number + 1) * (lens_index as u32 + 1) * lens.focal_length
})
.sum::()
})
.sum::()
.to_string()
}
}
That was a fun one. Especially after yesterday. As soon as I saw that star 1 was expanding each gap by 1, I just had a feeling that star 2 would be doing the same calculation with a larger expansion, so I wrote my code in a way that would make that quite simple to modify. When I saw the factor of 1,000,000 I was scared that it was going to be one of those processor-destroying AoC challenges where you either wait for 2 hours to get an answer, or have to come up with a fancy mathematical way of solving things, but after changing my i32
distance to an i64
, it calculated just fine and instantly. I guess only storing the locations of galaxies and not dealing with the entire grid was good enough to keep the performance down.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day11.rs
use crate::Solver;
use itertools::Itertools;
use num::abs;
#[derive(Debug)]
struct Point {
x: usize,
y: usize,
}
struct GalaxyMap {
locations: Vec,
}
impl GalaxyMap {
fn from(input: &str) -> GalaxyMap {
let locations = input
.lines()
.rev()
.enumerate()
.map(|(x, row)| {
row.chars()
.enumerate()
.filter_map(|(y, digit)| {
if digit == '#' {
Some(Point { x, y })
} else {
None
}
})
.collect::>()
})
.flatten()
.collect::>();
GalaxyMap { locations }
}
fn empty_rows(&self) -> Vec {
let occupied_rows = self
.locations
.iter()
.map(|point| point.y)
.unique()
.collect::>();
let max_y = *occupied_rows.iter().max().unwrap();
(0..max_y)
.filter(move |y| !occupied_rows.contains(&y))
.collect()
}
fn empty_cols(&self) -> Vec {
let occupied_cols = self
.locations
.iter()
.map(|point| point.x)
.unique()
.collect::>();
let max_x = *occupied_cols.iter().max().unwrap();
(0..max_x)
.filter(move |x| !occupied_cols.contains(&x))
.collect()
}
fn expand(&mut self, factor: usize) {
let delta = factor - 1;
for y in self.empty_rows().iter().rev() {
for galaxy in &mut self.locations {
if galaxy.y > *y {
galaxy.y += delta;
}
}
}
for x in self.empty_cols().iter().rev() {
for galaxy in &mut self.locations {
if galaxy.x > *x {
galaxy.x += delta;
}
}
}
}
fn galactic_distance(&self) -> i64 {
self.locations
.iter()
.combinations(2)
.map(|pair| {
abs(pair[0].x as i64 - pair[1].x as i64) + abs(pair[0].y as i64 - pair[1].y as i64)
})
.sum::()
}
}
pub struct Day11;
impl Solver for Day11 {
fn star_one(&self, input: &str) -> String {
let mut galaxy = GalaxyMap::from(input);
galaxy.expand(2);
galaxy.galactic_distance().to_string()
}
fn star_two(&self, input: &str) -> String {
let mut galaxy = GalaxyMap::from(input);
galaxy.expand(1_000_000);
galaxy.galactic_distance().to_string()
}
}
Well, star one is solved. I don't love the code, but yet again, it works for now. I don't love the use of a label to continue/break a loop, and the valid_steps
function is a mess that could probably be done much cleaner.
Upon looking at star 2 I don't even have the slightest idea of where to start. I may have to come back to this one at a later date. Sigh.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day10.rs
use crate::Solver;
#[derive(Debug)]
struct PipeMap {
start: usize,
tiles: Vec,
width: usize,
}
impl PipeMap {
fn from(input: &str) -> PipeMap {
let tiles = input
.lines()
.rev()
.flat_map(|row| row.chars())
.collect::>();
let width = input.find('\n').unwrap();
let start = tiles.iter().position(|tile| tile == &'S').unwrap();
PipeMap {
start,
tiles,
width,
}
}
fn valid_steps(&self, index: usize) -> Vec {
let mut tiles = vec![];
let current_tile = *self.tiles.get(index).unwrap();
if "S|LJ".contains(current_tile) {
let north = index + self.width;
if let Some(tile) = self.tiles.get(north) {
if "|7F".contains(*tile) {
tiles.push(north);
}
}
}
if "S|7F".contains(current_tile) {
if let Some(south) = index.checked_sub(self.width) {
if let Some(tile) = self.tiles.get(south) {
if "|LJ".contains(*tile) {
tiles.push(south);
}
}
}
}
if "S-J7".contains(current_tile) {
if let Some(west) = index.checked_sub(1) {
if (west % self.width) != (self.width - 1) {
if let Some(tile) = self.tiles.get(west) {
if "-LF".contains(*tile) {
tiles.push(west);
}
}
}
}
}
if "S-LF".contains(current_tile) {
let east = index + 1;
if east % self.width != 0 {
if let Some(tile) = self.tiles.get(east) {
if "-J7".contains(*tile) {
tiles.push(east);
}
}
}
}
tiles
}
}
pub struct Day10;
impl Solver for Day10 {
fn star_one(&self, input: &str) -> String {
let pipe_map = PipeMap::from(input);
let mut current_pos = pipe_map.start;
let mut last_pos = pipe_map.start;
let mut steps: usize = 0;
'outer: loop {
for pos in pipe_map.valid_steps(current_pos) {
if pos != last_pos {
last_pos = current_pos;
current_pos = pos;
steps += 1;
continue 'outer;
}
}
break;
}
steps.div_ceil(2).to_string()
}
fn star_two(&self, input: &str) -> String {
todo!()
}
}
Two days, a few failed solutions, some misread instructions, and a lot of manually parsing output data and debugging silly tiny mistakes... but it's finally done. I don't really wanna talk about it.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day07.rs
use crate::Solver;
use itertools::Itertools;
use std::cmp::Ordering;
#[derive(Clone, Copy)]
enum JType {
Jokers = 1,
Jacks = 11,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum HandType {
HighCard,
OnePair,
TwoPair,
ThreeOfAKind,
FullHouse,
FourOfAKind,
FiveOfAKind,
}
#[derive(Debug, Eq, PartialEq)]
struct CardHand {
hand: Vec,
bid: u64,
hand_type: HandType,
}
impl CardHand {
fn from(input: &str, j_type: JType) -> CardHand {
let (hand, bid) = input.split_once(' ').unwrap();
let hand = hand
.chars()
.map(|card| match card {
'2'..='9' => card.to_digit(10).unwrap() as u64,
'T' => 10,
'J' => j_type as u64,
'Q' => 12,
'K' => 13,
'A' => 14,
_ => unreachable!("malformed input"),
})
.collect::>();
let bid = bid.parse::().unwrap();
let counts = hand.iter().counts();
let hand_type = match counts.len() {
1 => HandType::FiveOfAKind,
2 => {
if hand.contains(&1) {
HandType::FiveOfAKind
} else {
if counts.values().contains(&4) {
HandType::FourOfAKind
} else {
HandType::FullHouse
}
}
}
3 => {
if counts.values().contains(&3) {
if hand.contains(&1) {
HandType::FourOfAKind
} else {
HandType::ThreeOfAKind
}
} else {
if counts.get(&1) == Some(&2) {
HandType::FourOfAKind
} else if counts.get(&1) == Some(&1) {
HandType::FullHouse
} else {
HandType::TwoPair
}
}
}
4 => {
if hand.contains(&1) {
HandType::ThreeOfAKind
} else {
HandType::OnePair
}
}
_ => {
if hand.contains(&1) {
HandType::OnePair
} else {
HandType::HighCard
}
}
};
CardHand {
hand,
bid,
hand_type,
}
}
}
impl PartialOrd for CardHand {
fn partial_cmp(&self, other: &Self) -> Option {
Some(self.cmp(other))
}
}
impl Ord for CardHand {
fn cmp(&self, other: &Self) -> Ordering {
let hand_type_cmp = self.hand_type.cmp(&other.hand_type);
if hand_type_cmp != Ordering::Equal {
return hand_type_cmp;
} else {
for i in 0..5 {
let value_cmp = self.hand[i].cmp(&other.hand[i]);
if value_cmp != Ordering::Equal {
return value_cmp;
}
}
}
Ordering::Equal
}
}
pub struct Day07;
impl Solver for Day07 {
fn star_one(&self, input: &str) -> String {
input
.lines()
.map(|line| CardHand::from(line, JType::Jacks))
.sorted()
.enumerate()
.map(|(index, hand)| hand.bid * (index as u64 + 1))
.sum::()
.to_string()
}
fn star_two(&self, input: &str) -> String {
input
.lines()
.map(|line| CardHand::from(line, JType::Jokers))
.sorted()
.enumerate()
.map(|(index, hand)| hand.bid * (index as u64 + 1))
.sum::()
.to_string()
}
}
A pretty simple one today, but fun to do. I could probably clean up the parsing code (AKA my theme for this year), and create just one single vector instead of having the original history separated out from all of the sequences, but this is what made sense to me on my first pass so it's how I did it.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day09.rs
pub struct Day09;
fn get_history(input: &str) -> Vec {
input
.split(' ')
.filter_map(|num| num.parse::().ok())
.collect::>()
}
fn get_sequences(history: &Vec) -> Vec> {
let mut sequences = vec![get_steps(&history)];
while !sequences.last().unwrap().iter().all_equal() {
sequences.push(get_steps(sequences.last().unwrap()));
}
sequences
}
fn get_steps(sequence: &Vec) -> Vec {
sequence
.iter()
.tuple_windows()
.map(|(x, y)| y - x)
.collect()
}
impl Solver for Day09 {
fn star_one(&self, input: &str) -> String {
input
.lines()
.map(|line| {
let history = get_history(line);
let add_value = get_sequences(&history)
.iter()
.rev()
.map(|seq| seq.last().unwrap().clone())
.reduce(|acc, x| acc + x)
.unwrap();
history.last().unwrap() + add_value
})
.sum::()
.to_string()
}
fn star_two(&self, input: &str) -> String {
input
.lines()
.map(|line| {
let history = get_history(line);
let minus_value = get_sequences(&history)
.iter()
.rev()
.map(|seq| seq.first().unwrap().clone())
.reduce(|acc, x| x - acc)
.unwrap();
history.first().unwrap() - minus_value
})
.sum::()
.to_string()
}
}
First part was simple enough. Second part was easy logically, but after running the brute force solution with a parallel iterator from rayon
and maxing out all 12 cores of this CPU, it was still taking forever. I always get tripped up by these ones that need fancy math, because although I was always good at math, I've never been good at looking at these problems and figuring out what kind of formula would apply. So I cheated and looked at other people's comments for their solutions, and saw the least common multiple mentioned. This made sense to me, so I implemented it and got a result almost instantly. I hate having to look at other comments to solve these things, but I never would have came to that conclusion myself.
The code still isn't the cleanest, and I'd love to tidy up the parsing, but it works and I'm happy.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day08.rs
impl Solver for Day08 {
fn star_one(&self, input: &str) -> String {
let (directions, map) = input.split_once("\n\n").unwrap();
let mut route_map = HashMap::new();
for line in map.lines() {
let line = line.replace(" ", "").replace("(", "").replace(")", "");
let (position, destinations) = line.split_once('=').unwrap();
let (left, right) = destinations.split_once(',').unwrap();
route_map.insert(position.to_string(), (left.to_string(), right.to_string()));
}
let mut current_position = "AAA".to_string();
for (step, direction) in directions.chars().cycle().enumerate() {
current_position = match direction {
'L' => route_map[¤t_position].0.to_string(),
'R' => route_map[¤t_position].1.to_string(),
_ => unreachable!(),
};
if current_position == "ZZZ" {
return (step + 1).to_string();
}
}
unreachable!()
}
fn star_two(&self, input: &str) -> String {
let (directions, map) = input.split_once("\n\n").unwrap();
let mut route_map = HashMap::new();
for line in map.lines() {
let line = line.replace(" ", "").replace("(", "").replace(")", "");
let (position, destinations) = line.split_once('=').unwrap();
let (left, right) = destinations.split_once(',').unwrap();
route_map.insert(position.to_string(), (left.to_string(), right.to_string()));
}
let positions = route_map
.keys()
.filter(|pos| pos.ends_with('A'))
.collect::>();
let steps = positions
.iter()
.filter(|pos| pos.ends_with('A'))
.map(|pos| {
let mut current_position = pos.to_string();
for (step, direction) in directions.chars().cycle().enumerate() {
current_position = match direction {
'L' => route_map[¤t_position].0.to_string(),
'R' => route_map[¤t_position].1.to_string(),
_ => unreachable!(),
};
if current_position.ends_with('Z') {
return step + 1;
}
}
unreachable!()
})
.collect::>();
steps
.into_iter()
.reduce(|acc, steps| acc.lcm(&steps))
.unwrap()
.to_string()
}
}
A nice simple one today. And only a half second delay for part two instead of half an hour. What a treat. I could probably have nicer input parsing, but that seems to be the theme this year, so that will become a big focus of my next round through these I'm guessing. The algorithm here to get the winning possibilities could also probably be improved upon by figuring out what the number of seconds for the current record is, and only looping from there until hitting a number that doesn't win, as opposed to brute-forcing the whole loop.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day06.rs
#[derive(Debug)]
struct Race {
time: u64,
distance: u64,
}
impl Race {
fn possible_ways_to_win(&self) -> usize {
(0..=self.time)
.filter(|time| time * (self.time - time) > self.distance)
.count()
}
}
pub struct Day06;
impl Solver for Day06 {
fn star_one(&self, input: &str) -> String {
let mut race_data = input
.lines()
.map(|line| {
line.split_once(':')
.unwrap()
.1
.split_ascii_whitespace()
.filter_map(|number| number.parse::().ok())
.collect::>()
})
.collect::>();
let times = race_data.pop().unwrap();
let distances = race_data.pop().unwrap();
let races = distances
.into_iter()
.zip(times)
.map(|(time, distance)| Race { time, distance })
.collect::>();
races
.iter()
.map(|race| race.possible_ways_to_win())
.fold(1, |acc, count| acc * count)
.to_string()
}
fn star_two(&self, input: &str) -> String {
let race_data = input
.lines()
.map(|line| {
line.split_once(':')
.unwrap()
.1
.replace(" ", "")
.parse::()
.unwrap()
})
.collect::>();
let race = Race {
time: race_data[0],
distance: race_data[1],
};
race.possible_ways_to_win().to_string()
}
}
Well, I can't say much about this one. The code is ugly, horribly inefficient, and part two takes a solid half hour to run. It got the right answer though, so that's something I suppose. I think something like nom
to parse the input would be much cleaner, and there's got to be a better way of going about part two than just brute forcing through every possible seed, but hey, it works so that's good enough for now.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day05.rs
#[derive(Clone, Debug)]
struct AlmanacMapEntry {
destination_range: RangeInclusive,
source_range: RangeInclusive,
}
#[derive(Clone, Debug)]
struct AlmanacMap {
entries: Vec,
}
impl AlmanacMap {
fn from(input: &str) -> AlmanacMap {
let entries = input
.lines()
.skip(1)
.map(|line| {
let numbers = line
.split(' ')
.filter_map(|number| number.parse::().ok())
.collect::>();
AlmanacMapEntry {
destination_range: numbers[0]..=(numbers[0] + numbers[2]),
source_range: numbers[1]..=(numbers[1] + numbers[2]),
}
})
.collect();
AlmanacMap { entries }
}
fn convert(&self, source: &u64) -> u64 {
let entry = self
.entries
.iter()
.find(|entry| entry.source_range.contains(&source));
if let Some(entry) = entry {
entry.destination_range.start() + (source - entry.source_range.start())
} else {
source.clone()
}
}
}
#[derive(Debug)]
struct Almanac {
seeds: Vec,
seed_to_soil: AlmanacMap,
soil_to_fertilizer: AlmanacMap,
fertilizer_to_water: AlmanacMap,
water_to_light: AlmanacMap,
light_to_temperature: AlmanacMap,
temperature_to_humidity: AlmanacMap,
humidity_to_location: AlmanacMap,
}
impl Almanac {
fn star_one_from(input: &str) -> Almanac {
let mut input_sections = input
.split("\n\n")
.map(|section| section.split_once(':').unwrap().1);
let seeds = input_sections
.next()
.unwrap()
.split_whitespace()
.filter_map(|seed| seed.parse::().ok())
.collect();
let almanac_maps = input_sections.map(AlmanacMap::from).collect::>();
Almanac {
seeds,
seed_to_soil: almanac_maps[0].clone(),
soil_to_fertilizer: almanac_maps[1].clone(),
fertilizer_to_water: almanac_maps[2].clone(),
water_to_light: almanac_maps[3].clone(),
light_to_temperature: almanac_maps[4].clone(),
temperature_to_humidity: almanac_maps[5].clone(),
humidity_to_location: almanac_maps[6].clone(),
}
}
fn star_two_from(input: &str) -> Almanac {
let mut input_sections = input
.split("\n\n")
.map(|section| section.split_once(':').unwrap().1);
let seeds = input_sections
.next()
.unwrap()
.split_whitespace()
.filter_map(|seed| seed.parse::().ok())
.collect::>()
.chunks(2)
.map(|chunk| (chunk[0]..(chunk[0] + chunk[1])).collect::>())
.flatten()
.collect::>();
let almanac_maps = input_sections.map(AlmanacMap::from).collect::>();
Almanac {
seeds,
seed_to_soil: almanac_maps[0].clone(),
soil_to_fertilizer: almanac_maps[1].clone(),
fertilizer_to_water: almanac_maps[2].clone(),
water_to_light: almanac_maps[3].clone(),
light_to_temperature: almanac_maps[4].clone(),
temperature_to_humidity: almanac_maps[5].clone(),
humidity_to_location: almanac_maps[6].clone(),
}
}
}
pub struct Day05;
impl Solver for Day05 {
fn star_one(&self, input: &str) -> String {
let almanac = Almanac::star_one_from(input);
almanac
.seeds
.iter()
.map(|seed| almanac.seed_to_soil.convert(seed))
.map(|soil| almanac.soil_to_fertilizer.convert(&soil))
.map(|fertilizer| almanac.fertilizer_to_water.convert(&fertilizer))
.map(|water| almanac.water_to_light.convert(&water))
.map(|light| almanac.light_to_temperature.convert(&light))
.map(|temperature| almanac.temperature_to_humidity.convert(&temperature))
.map(|humidity| almanac.humidity_to_location.convert(&humidity))
.min()
.unwrap()
.to_string()
}
fn star_two(&self, input: &str) -> String {
let almanac = Almanac::star_two_from(input);
almanac
.seeds
.iter()
.map(|seed| almanac.seed_to_soil.convert(seed))
.map(|soil| almanac.soil_to_fertilizer.convert(&soil))
.map(|fertilizer| almanac.fertilizer_to_water.convert(&fertilizer))
.map(|water| almanac.water_to_light.convert(&water))
.map(|light| almanac.light_to_temperature.convert(&light))
.map(|temperature| almanac.temperature_to_humidity.convert(&temperature))
.map(|humidity| almanac.humidity_to_location.convert(&humidity))
.min()
.unwrap()
.to_string()
}
}
I enjoyed this one. It was a nice simple break after Days 1 and 3; the type of basic puzzle I expect from the first few days of Advent of Code. Pretty simple logic in this one, I don't think I would change too much. I'm sure I'll find a way to clean up how it's written a bit, but I'm happy with this one today.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day04.rs
struct Scratchcard {
winning_numbers: HashSet,
player_numbers: HashSet,
}
impl Scratchcard {
fn from(input: &str) -> Scratchcard {
let (_, numbers) = input.split_once(':').unwrap();
let (winning_numbers, player_numbers) = numbers.split_once('|').unwrap();
let winning_numbers = winning_numbers
.split_ascii_whitespace()
.filter_map(|number| number.parse::().ok())
.collect::>();
let player_numbers = player_numbers
.split_ascii_whitespace()
.filter_map(|number| number.parse::().ok())
.collect::>();
Scratchcard {
winning_numbers,
player_numbers,
}
}
fn matches(&self) -> u32 {
self.winning_numbers
.intersection(&self.player_numbers)
.count() as u32
}
}
pub struct Day04;
impl Solver for Day04 {
fn star_one(&self, input: &str) -> String {
input
.lines()
.map(Scratchcard::from)
.map(|card| {
let matches = card.matches();
if matches == 0 {
0
} else {
2u32.pow(matches - 1)
}
})
.sum::()
.to_string()
}
fn star_two(&self, input: &str) -> String {
let cards: Vec = input.lines().map(Scratchcard::from).collect();
let mut card_counts = vec![1usize; cards.len()];
for card_number in 0..cards.len() {
let matches = cards[card_number].matches();
if matches == 0 {
continue;
}
for i in 1..=matches {
card_counts[card_number + i as usize] += card_counts[card_number];
}
}
card_counts.iter().sum::().to_string()
}
}
Another day of the 2023 Advent of Code, and another day where I hate looking at my code. This year just seems like it is starting off a lot more complex than I remember in previous years. This one was a little tricky, but I got there without any major setbacks. Another one I am excited to come back to and clean up, but this first pass is all about getting a solution, and this one works.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day03.rs
#[derive(Clone, Copy, Debug)]
struct Location {
row: usize,
start_col: usize,
end_col: usize,
}
#[derive(Debug)]
struct EngineSchematic {
schematic: Vec>,
numbers: Vec,
symbols: Vec,
}
impl EngineSchematic {
fn from(input: &str) -> EngineSchematic {
let schematic: Vec> = input.lines().map(|line| line.chars().collect()).collect();
let mut numbers = vec![];
let mut symbols = vec![];
let mut location: Option = None;
for (row_index, row) in schematic.iter().enumerate() {
for (col, ch) in row.iter().enumerate() {
match ch {
ch if ch.is_ascii_punctuation() => {
if let Some(location) = location {
numbers.push(location);
}
location = None;
if ch != &'.' {
symbols.push(Location {
row: row_index,
start_col: col,
end_col: col,
});
}
}
ch if ch.is_digit(10) => {
if let Some(mut_location) = location.as_mut() {
mut_location.end_col = col;
} else {
location = Some(Location {
row: row_index,
start_col: col,
end_col: col,
});
}
}
_ => {
unreachable!("malformed input");
}
}
}
if let Some(location) = location {
numbers.push(location);
}
location = None;
}
EngineSchematic {
schematic,
numbers,
symbols,
}
}
fn get_number_value(&self, location: &Location) -> u32 {
self.schematic[location.row][location.start_col..=location.end_col]
.iter()
.collect::()
.parse::()
.unwrap()
}
fn is_gear(&self, location: &Location) -> bool {
self.schematic[location.row][location.start_col] == '*'
}
fn are_adjacent(&self, location: &Location, other_location: &Location) -> bool {
location.start_col >= other_location.start_col.checked_sub(1).unwrap_or(0)
&& location.end_col <= other_location.end_col + 1
&& (location.row == other_location.row
|| location.row == other_location.row.checked_sub(1).unwrap_or(0)
|| location.row == other_location.row + 1)
}
}
pub struct Day03;
impl Solver for Day03 {
fn star_one(&self, input: &str) -> String {
let schematic = EngineSchematic::from(input);
schematic
.numbers
.iter()
.filter(|number| {
schematic
.symbols
.iter()
.any(|symbol| schematic.are_adjacent(symbol, number))
})
.map(|number| schematic.get_number_value(number))
.sum::()
.to_string()
}
fn star_two(&self, input: &str) -> String {
let schematic = EngineSchematic::from(input);
schematic
.symbols
.iter()
.filter(|symbol| schematic.is_gear(symbol))
.map(|symbol| {
let adjacent_numbers = schematic
.numbers
.iter()
.filter(|number| schematic.are_adjacent(symbol, number))
.collect::>();
if adjacent_numbers.len() == 2 {
schematic.get_number_value(adjacent_numbers[0])
* schematic.get_number_value(adjacent_numbers[1])
} else {
0
}
})
.sum::()
.to_string()
}
}
Not too tricky today. Part 2 wasn't as big of a curveball as yesterday thankfully. I don't think it's the cleanest code I've ever written, but hey - the whole point of this is to get better at Rust, so I'll definitely be learning as I go, and coming back at the end to clean a lot of these up. I think for this one I'd like to look into a parsing crate like nom to clean up all the spliting and unwrapping in the two from() methods.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day02.rs
#[derive(Debug)]
struct Hand {
blue: usize,
green: usize,
red: usize,
}
impl Hand {
fn from(input: &str) -> Hand {
let mut hand = Hand {
blue: 0,
green: 0,
red: 0,
};
for color in input.split(", ") {
let color = color.split_once(' ').unwrap();
match color.1 {
"blue" => hand.blue = color.0.parse::().unwrap(),
"green" => hand.green = color.0.parse::().unwrap(),
"red" => hand.red = color.0.parse::().unwrap(),
_ => unreachable!("malformed input"),
}
}
hand
}
}
#[derive(Debug)]
struct Game {
id: usize,
hands: Vec,
}
impl Game {
fn from(input: &str) -> Game {
let (id, hands) = input.split_once(": ").unwrap();
let id = id.split_once(" ").unwrap().1.parse::().unwrap();
let hands = hands.split("; ").map(Hand::from).collect();
Game { id, hands }
}
}
pub struct Day02;
impl Solver for Day02 {
fn star_one(&self, input: &str) -> String {
input
.lines()
.map(Game::from)
.filter(|game| {
game.hands
.iter()
.all(|hand| hand.blue <= 14 && hand.green <= 13 && hand.red <= 12)
})
.map(|game| game.id)
.sum::()
.to_string()
}
fn star_two(&self, input: &str) -> String {
input
.lines()
.map(Game::from)
.map(|game| {
let max_blue = game.hands.iter().map(|hand| hand.blue).max().unwrap();
let max_green = game.hands.iter().map(|hand| hand.green).max().unwrap();
let max_red = game.hands.iter().map(|hand| hand.red).max().unwrap();
max_blue * max_green * max_red
})
.sum::()
.to_string()
}
}
Wow, I sure did. I was tired last night. I'll edit my solution in and fix my error.
Solved part one in about thirty seconds. But wow, either my brain is just tired at this hour or I'm lacking in skill, but part two is harder than any other year has been on the first day. Anyway, I managed to solve it, but I absolutely hate it, and will definitely be coming back to try to clean this one up.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day01.rs
impl Solver for Day01 {
fn star_one(&self, input: &str) -> String {
let mut result = 0;
for line in input.lines() {
let line = line
.chars()
.filter(|ch| ch.is_ascii_digit())
.collect::>();
let first = line.first().unwrap();
let last = line.last().unwrap();
let number = format!("{first}{last}").parse::().unwrap();
result += number;
}
result.to_string()
}
fn star_two(&self, input: &str) -> String {
let mut result = 0;
for line in input.lines() {
let mut first = None;
let mut last = None;
while first == None {
for index in 0..line.len() {
let line_slice = &line[index..];
if line_slice.starts_with("one") || line_slice.starts_with("1") {
first = Some(1);
} else if line_slice.starts_with("two") || line_slice.starts_with("2") {
first = Some(2);
} else if line_slice.starts_with("three") || line_slice.starts_with("3") {
first = Some(3);
} else if line_slice.starts_with("four") || line_slice.starts_with("4") {
first = Some(4);
} else if line_slice.starts_with("five") || line_slice.starts_with("5") {
first = Some(5);
} else if line_slice.starts_with("six") || line_slice.starts_with("6") {
first = Some(6);
} else if line_slice.starts_with("seven") || line_slice.starts_with("7") {
first = Some(7);
} else if line_slice.starts_with("eight") || line_slice.starts_with("8") {
first = Some(8);
} else if line_slice.starts_with("nine") || line_slice.starts_with("9") {
first = Some(9);
}
if first.is_some() {
break;
}
}
}
while last == None {
for index in (0..line.len()).rev() {
let line_slice = &line[index..];
if line_slice.starts_with("one") || line_slice.starts_with("1") {
last = Some(1);
} else if line_slice.starts_with("two") || line_slice.starts_with("2") {
last = Some(2);
} else if line_slice.starts_with("three") || line_slice.starts_with("3") {
last = Some(3);
} else if line_slice.starts_with("four") || line_slice.starts_with("4") {
last = Some(4);
} else if line_slice.starts_with("five") || line_slice.starts_with("5") {
last = Some(5);
} else if line_slice.starts_with("six") || line_slice.starts_with("6") {
last = Some(6);
} else if line_slice.starts_with("seven") || line_slice.starts_with("7") {
last = Some(7);
} else if line_slice.starts_with("eight") || line_slice.starts_with("8") {
last = Some(8);
} else if line_slice.starts_with("nine") || line_slice.starts_with("9") {
last = Some(9);
}
if last.is_some() {
break;
}
}
}
result += format!("{}{}", first.unwrap(), last.unwrap())
.parse::()
.unwrap();
}
result.to_string()
}
}