Add 2023 solutions

This commit is contained in:
Sven Weidauer 2024-12-25 12:52:38 +01:00
parent 7f8a4ebb89
commit 10f30eb274
48 changed files with 7418 additions and 0 deletions

60
2023/src/bin/day1.rs Normal file
View file

@ -0,0 +1,60 @@
use std::io;
use std::collections::HashMap;
use std::str::FromStr;
use regex::Regex;
fn main() {
let mut sum = 0u32;
let mut sum2: u32 = 0;
let digit_names: HashMap<&str, u32> = HashMap::from([
("one", 1),
("two", 2),
("three", 3),
("four", 4),
("five", 5),
("six", 6),
("seven", 7),
("eight", 8),
("nine", 9)
]);
let regex_string = digit_names.keys()
.fold("\\d".to_owned(), |a,b| a + "|" + b);
println!("Regex: {regex_string}");
let regex = Regex::from_str(&regex_string).expect("Invalid regex");
let get_digit = |m: regex::Match<'_>| {
let digit = m.as_str();
digit.parse::<u32>()
.map_or(None, |x| Some(x))
.or(digit_names.get(digit).copied())
.expect("Regex doesnt work")
};
for line in io::stdin().lines() {
let line = line.unwrap();
let line = line.trim();
let digits: Vec<_> = line.chars()
.flat_map(|digit| digit.to_digit(10))
.collect();
if let (Some(first), Some(last)) = (digits.first(), digits.last()) {
sum += first * 10 + last
}
let match1 = regex.find(line)
.map(get_digit);
let match2 = (0..line.len()).rev().filter_map(|pos| regex.find_at(line, pos)).next()
.map(get_digit);
if let (Some(first), Some(last)) = (match1, match2) {
sum2 += first * 10 + last
}
}
println!("Part 1: {sum}");
println!("Part 2: {sum2}");
}

100
2023/src/bin/day10.rs Normal file
View file

@ -0,0 +1,100 @@
use aoc::grid::{Grid, GridVec};
use std::iter::Iterator;
trait MoreGrid: Grid {
fn trace(&self, start: (usize, usize), start_direction: Direction) -> Option<usize> {
let mut position = start;
let mut direction = start_direction;
let mut count: usize = 0;
while let Some(new_position) = self.next_position(position, direction) {
count += 1;
let (x, y) = new_position;
position = new_position;
if start == position {
return Some(count);
}
if let Some(dir) = next_direction(direction.opposite(), self.char_at(x, y)) {
direction = dir;
} else {
return None
}
}
None
}
fn longest_loop(&self) -> Option<usize> {
if let Some(start) = self.find('S') {
vec![Direction::Top, Direction::Right, Direction::Bottom, Direction::Left]
.iter()
.filter_map(|dir| self.trace(start, *dir))
.max()
} else {
None
}
}
fn next_position(&self, current: (usize, usize), direction: Direction) -> Option<(usize, usize)> {
let (x, y) = current;
match direction {
Direction::Left => if x > 0 { Some((x - 1, y)) } else { None },
Direction::Right => if x + 1 < self.width() { Some((x + 1, y))} else { None },
Direction::Top => if y > 0 { Some((x, y - 1)) } else { None },
Direction::Bottom => if y + 1 < self.height() { Some((x, y + 1)) } else { None }
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Direction {
Top,
Left,
Right,
Bottom
}
fn connections(ch: char) -> Option<(Direction, Direction)> {
match ch {
'|' => Some((Direction::Top, Direction::Bottom)),
'-' => Some((Direction::Left, Direction::Right)),
'L' => Some((Direction::Top, Direction::Right)),
'J' => Some((Direction::Left, Direction::Top)),
'7' => Some((Direction::Left, Direction::Bottom)),
'F' => Some((Direction::Bottom, Direction::Right)),
'.' => None,
_ => panic!("Invalid direction")
}
}
fn next_direction(from: Direction, ch: char) -> Option<Direction> {
if let Some(pair) = connections(ch) {
match pair {
(f, result) if f == from => Some(result),
(result, f) if f == from => Some(result),
_ => None
}
} else {
None
}
}
impl Direction {
fn opposite(self) -> Direction {
match self {
Self::Top => Self::Bottom,
Self::Bottom => Self::Top,
Self::Left => Self::Right,
Self::Right => Self::Left
}
}
}
impl <T: Grid> MoreGrid for T {}
fn main() {
let grid = GridVec::read();
println!("Part 1: {}", grid.longest_loop().expect("No solution") / 2);
}

64
2023/src/bin/day11.rs Normal file
View file

@ -0,0 +1,64 @@
use aoc::grid::{Grid, GridVec};
use itertools::Itertools;
fn find_galaxies(grid: &GridVec, empty_columns: &Vec<usize>, empty_rows: &Vec<usize>, expansion_factor: usize) -> Vec<(usize, usize)> {
let mut offset_y: usize = 0;
let mut empty_rows = empty_rows.iter();
let mut next_empty_row = empty_rows.next();
let mut galaxies: Vec<(usize, usize)> = Vec::new();
for y in 0..grid.height() {
if Some(&y) == next_empty_row {
offset_y += expansion_factor;
next_empty_row = empty_rows.next();
}
let mut offset_x: usize = 0;
let mut empty_columns = empty_columns.iter();
let mut next_empty_column = empty_columns.next();
for x in 0..grid.width() {
if Some(&x) == next_empty_column {
offset_x += expansion_factor;
next_empty_column = empty_columns.next();
}
if grid.char_at(x, y) == '#' {
galaxies.insert(galaxies.len(), (x + offset_x, y + offset_y));
}
}
}
galaxies
}
fn sum_galaxy_distances(galaxies: &Vec<(usize, usize)>) -> usize {
let mut sum: usize = 0;
for i in 0..galaxies.len() {
for j in 0..i {
let (ax, ay) = galaxies[i];
let (bx, by) = galaxies[j];
sum += if ax < bx { bx - ax } else { ax - bx };
sum += if ay < by { by - ay } else { ay - by };
}
}
sum
}
fn main() {
let grid = GridVec::read();
let empty_columns: Vec<_> = (0..grid.width()).filter(|x| grid.column(*x).all(|ch| ch == '.')).collect();
let empty_rows: Vec<_> = (0..grid.height()).filter(|y| grid.row(*y).all(|ch| ch == '.')).collect();
let galaxies = find_galaxies(&grid, &empty_columns, &empty_rows, 1);
let part1 = sum_galaxy_distances(&galaxies);
println!("Part 1: {part1}");
let galaxies = find_galaxies(&grid, &empty_columns, &empty_rows, 1000000 - 1);
let part2 = sum_galaxy_distances(&galaxies);
println!("Part 2: {part2}");
}

57
2023/src/bin/day12.rs Normal file
View file

@ -0,0 +1,57 @@
use std::iter::zip;
use aoc::{parsing, read_input};
use nom::{character::complete::{space1, newline}, Parser, bytes::complete::{take_while1, tag}, sequence::{tuple, terminated}, multi::{separated_list0, many0}};
use aoc::parsing::number;
fn matches(string: &str, groups: &Vec<usize>) -> bool {
let parts = string.split('.')
.filter(|s| !s.is_empty())
.map(|s| s.chars().filter(|ch| *ch == '#').count());
let group_iter = groups.iter();
let all_equal = zip(parts.clone(), group_iter).all(|(a, b)| a == *b);
parts.count() == groups.len() && all_equal
}
fn combinations(string: &str, groups: &Vec<usize>) -> usize {
if let Some(index) = string.find('?') {
let first = &string[..index];
let rest = &string[(index + 1)..];
let mut a = first.to_owned();
a.push('.');
a.push_str(rest);
let mut b = first.to_owned();
b.push('#');
b.push_str(rest);
combinations(&a, groups) + combinations(&b, groups)
} else if matches(string, groups) {
1
} else {
0
}
}
fn springs<'a>(input: &'a str) -> parsing::Result<'a, &'a str> {
take_while1(|ch| ch == '#' || ch == '.' || ch == '?').parse(input)
}
fn line<'a>(input: &'a str) -> parsing::Result<'a, (&'a str, Vec<usize>)> {
tuple((
terminated(springs, space1),
separated_list0(tag(","), number)
)).parse(input)
}
fn main() {
let input = read_input();
let (_, input) = separated_list0(newline, line).parse(&input).expect("Cannot parse input");
let mut sum: usize = 0;
for (str, groups) in input {
sum += combinations(&str, &groups);
}
println!("Part 1: {sum}");
}

41
2023/src/bin/day13.rs Normal file
View file

@ -0,0 +1,41 @@
use std::{io::stdin, cmp::min};
use aoc::grid::{Grid, GridVec};
fn main() {
let mut input = stdin().lines().map(|x| x.expect("Cannot read line"));
let mut sum: usize = 0;
while let Some(grid) = GridVec::read_one(&mut input) {
'outer: for i in 1..grid.width() {
let left = i;
let right = grid.width() - i;
let compare = min(left, right);
for k in 1..=compare {
if !grid.column(i - k).zip(grid.column(i + k - 1)).all(|(a,b)| a == b) {
continue 'outer;
}
}
sum += i;
}
'outer: for i in 1..grid.height() {
let top = i;
let bottom = grid.height() - i;
let compare = min(top, bottom);
for k in 1..=compare {
if !grid.row(i - k).zip(grid.row(i + k - 1)).all(|(a,b)| a == b) {
continue 'outer;
}
}
sum += 100 * i;
}
}
println!("Part 1: {sum}");
}

45
2023/src/bin/day14.rs Normal file
View file

@ -0,0 +1,45 @@
use aoc::grid::{GridVec, Grid};
fn main() {
let mut grid = GridVec::read();
for y in 0..grid.height() {
for x in 0..grid.width() {
if grid.char_at(x, y) != 'O' {
continue;
}
if let Some(open_y) = find_open_spot(&grid, x, y) {
grid.set_char_at(x, open_y, 'O');
grid.set_char_at(x, y, '.');
}
}
}
grid.print();
let mut sum = 0;
for y in 0..grid.height() {
let rocks = grid.line_at(y).chars().filter(|ch| *ch == 'O').count();
let weight = grid.height() - y;
sum += weight * rocks;
}
println!("Part 1: {sum}");
}
fn find_open_spot(grid: &GridVec, x: usize, y: usize) -> Option<usize> {
if y == 0 {
return None;
}
let mut new_y = y;
while new_y > 0 && grid.char_at(x, new_y - 1) == '.' {
new_y -= 1;
}
if new_y != y {
Some(new_y)
} else {
None
}
}

67
2023/src/bin/day15.rs Normal file
View file

@ -0,0 +1,67 @@
use std::collections::HashMap;
use aoc::read_input;
use itertools::Itertools;
use linked_hash_map::LinkedHashMap;
fn hash_hash(string: &str) -> usize {
let mut current = 0;
for ch in string.bytes() {
current += ch as usize;
current *= 17;
current = current % 256;
}
current
}
struct LensBox<'a> {
content: LinkedHashMap<&'a str, usize>
}
impl <'a> LensBox<'a> {
fn new() -> LensBox<'a> {
LensBox {
content: LinkedHashMap::new()
}
}
fn remove(&mut self, label: &str) {
self.content.remove(label);
}
fn add(&mut self, label: &'a str, power: usize) {
*self.content.entry(label).or_insert(power) = power;
}
fn focus_power(&self, box_index: usize) -> usize {
self.content.iter().enumerate().fold(0, |a, (lens_index, (_, focus))| a + (box_index + 1) * (lens_index + 1) * *focus)
}
}
fn main() {
let input = read_input();
let input = input.split(',').collect_vec();
let result = input.iter().map(|str| hash_hash(str)).fold(0, |a,b| a+ b);
println!("Part 1: {result}");
let mut boxes = HashMap::new();
for instruction in input.iter() {
let index = instruction.find(|ch| ch == '=' || ch == '-').expect("Invalid instruction");
let label = &instruction[..index];
let box_index = hash_hash(label);
let lens_box = boxes.entry(box_index).or_insert_with(|| LensBox::new());
if &instruction[index..=index] == "-" {
lens_box.remove(label)
} else {
let power = instruction[(index + 1)..].parse().expect("Invalid instruction");
lens_box.add(label, power)
}
}
let part2 = (0..=256)
.filter_map(|n| Some(boxes.get(&n)?.focus_power(n)) )
.fold(0, |a, b| a + b);
println!("Part 2: {part2}");
}

96
2023/src/bin/day16.rs Normal file
View file

@ -0,0 +1,96 @@
use std::{collections::{HashMap, HashSet}, cmp::max};
use aoc::grid::{GridVec, Grid};
use array2d::Array2D;
fn grid_position(grid: &GridVec, x: isize, y: isize) -> Option<(usize, usize)> {
if x < 0 || y < 0 {
return None;
}
let cur_x: usize = x.try_into().unwrap();
let cur_y: usize = y.try_into().unwrap();
if cur_x >= grid.width() || cur_y >= grid.height() {
return None;
}
return Some((cur_x, cur_y));
}
fn trace(grid: &GridVec, energized: &mut Array2D<bool>, cache: &mut HashMap<(isize, isize, isize, isize), ()>, mut x: isize, mut y: isize, mut dx: isize, mut dy: isize) {
let key = (x, y, dx, dy);
if let Some(cached) = cache.get(&key) {
println!("Cached result found for {:?}", key);
return *cached;
}
cache.insert(key, ());
let mut steps = HashSet::new();
while let Some((cur_x, cur_y)) = grid_position(grid, x, y) {
energized[(cur_y, cur_x)] = true;
let cur_ch = grid.char_at(cur_x, cur_y);
// println!("{x},{y} ({dx},{dy}) {cur_ch} ");
match cur_ch {
'/' => (dx, dy) = (-dy, -dx),
'\\' => (dx, dy) = (dy, dx),
'|' if dy == 0 => {
trace(grid, energized, cache, x, y - 1, 0, -1);
(dx, dy) = (0, 1);
}
'-' if dx == 0 => {
trace(grid, energized, cache, x - 1, y, -1, 0);
(dx, dy) = (1, 0);
},
'-' | '|' | '.' => {},
_ => panic!("Unknown character")
};
assert!(dx == 0 || dy == 0);
x += dx;
y += dy;
if !steps.insert((x, y, dx, dy)) {
println!("Loop detected, stopping");
break;
}
}
}
fn energized(grid: &GridVec, x: isize, y: isize, dx: isize, dy: isize) -> usize {
let mut energized = Array2D::filled_with(false, grid.height(), grid.width());
let mut cache = HashMap::new();
trace(&grid, &mut energized, &mut cache, x, y, dx, dy);
energized.elements_row_major_iter().filter(|energized| **energized).count()
}
fn main() {
let grid = GridVec::read();
let part1 = energized(&grid, 0, 0, 1, 0);
println!("Part 1: {part1}");
let mut part2 = 0;
let x_max: isize = grid.width().try_into().unwrap();
let y_max: isize = grid.height().try_into().unwrap();
for y in 0..y_max {
let a = energized(&grid, 0, y, 1, 0);
let b = energized(&grid, x_max - 1, y, -1, 0);
part2 = max(part2, max(a, b));
}
for x in 0..x_max {
let a = energized(&grid, x, 0, 0, 1);
let b = energized(&grid, x, y_max - 1, 0, -1);
part2 = max(part2, max(a, b));
}
println!("Part 2: {part2}");
}

3
2023/src/bin/day17.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
}

163
2023/src/bin/day2.rs Normal file
View file

@ -0,0 +1,163 @@
use std::{io, cmp::max};
use std::io::Read;
use nom::{
branch::alt,
bytes::complete::tag,
IResult,
Parser,
multi::separated_list0,
sequence::{separated_pair, tuple},
combinator::map_res,
character::{streaming::digit1, complete::line_ending},
};
#[derive(Debug)]
struct Reveal {
red: usize,
green: usize,
blue: usize
}
#[derive(Debug)]
struct Game {
id: usize,
reveals: Vec<Reveal>
}
impl Game {
fn new(id: usize, reveals: Vec<Reveal>) -> Game {
Game { id, reveals }
}
fn is_valid(&self) -> bool {
self.reveals.iter().all(|reveal| reveal.is_valid())
}
fn min_cubes(&self) -> Reveal {
self.reveals.iter().fold(Reveal::new(), |acc, reveal| {
Reveal {
red: max(acc.red, reveal.red),
green: max(acc.green, reveal.green),
blue: max(acc.blue, reveal.blue)
}
})
}
}
impl Reveal {
fn new() -> Reveal {
Reveal { red: 0, green: 0, blue: 0 }
}
fn with_red(&self, red: usize) -> Reveal {
Reveal { red: self.red + red, green: self.green, blue: self.blue }
}
fn with_green(&self, green: usize) -> Reveal {
Reveal { red: self.red, green: self.green + green, blue: self.blue }
}
fn with_blue(&self, blue: usize) -> Reveal {
Reveal { red: self.red, green: self.green, blue: self.blue + blue }
}
fn with(&self, color: Color, count: usize) -> Reveal {
match color {
Color::Red => self.with_red(count),
Color::Green => self.with_green(count),
Color::Blue => self.with_blue(count),
}
}
fn is_valid(&self) -> bool {
self.red <= 12 && self.green <= 13 && self.blue <= 14
}
fn power(&self) -> usize {
self.red * self.green * self.blue
}
}
#[derive(Clone, Copy)]
enum Color {
Red,
Green,
Blue
}
impl From<&str> for Color {
fn from(value: &str) -> Self {
match value {
"red" => Color::Red,
"blue" => Color::Blue,
"green" => Color::Green,
_ => panic!("Invalid color")
}
}
}
fn color(input: &str) -> IResult<&str, Color> {
Parser::into(alt((
tag("red"),
tag("green"),
tag("blue")
))).parse(input)
}
fn number(input: &str) -> IResult<&str, usize> {
map_res(digit1, str::parse).parse(input)
}
fn color_count(input: &str) -> IResult<&str, (usize, Color)> {
separated_pair(
number,
tag(" "),
color
).parse(input)
}
fn reveal(input: &str) -> IResult<&str, Reveal> {
separated_list0(tag(", "), color_count)
.map(|list| {
list.iter().fold(Reveal::new(), |acc, (count, color)| {
acc.with(*color, *count)
})
})
.parse(input)
}
fn reveals(input: &str) -> IResult<&str, Vec<Reveal>> {
separated_list0(tag("; "), reveal).parse(input)
}
fn game(input: &str) -> IResult<&str, Game> {
tuple((
tag("Game "),
number,
tag(": "),
reveals,
))
.map(|(_, id, _, reveals)| Game::new(id, reveals))
.parse(input)
}
fn input_file(input: &str) -> IResult<&str, Vec<Game>> {
separated_list0(line_ending, game).parse(input)
}
fn main() {
let mut input = String::new();
io::stdin().read_to_string(&mut input).expect("Reading failed");
let (_, games) = input_file(input.as_str()).expect("Parsing failed");
let part1 = games.iter().filter(|game| game.is_valid()).fold(0, |acc, game| acc + game.id);
println!("Part 1: {part1}");
let part2 = games.iter().fold(0, |acc, game| acc + game.min_cubes().power());
println!("Part 2: {part2}");
}

81
2023/src/bin/day3.rs Normal file
View file

@ -0,0 +1,81 @@
use std::{io, cmp::min};
use regex::Regex;
#[derive(Debug)]
struct Grid {
lines: Vec<String>,
width: usize
}
impl Grid {
fn read() -> Grid {
let lines: Vec<String> = io::stdin().lines().map(|line| line.expect("Cannot read line")).collect();
let width = lines.first().map_or(0, |l| l.len());
let ok = lines.iter().skip(1).all(|line| line.len() == width);
if !ok {
panic!("Not all lines same length");
}
Grid { lines, width }
}
fn char_at(&self, x: usize, y: usize) -> char {
self.lines[y].chars().nth(x).expect("Invalid index")
}
fn height(&self) -> usize { self.lines.len() }
fn numbers<'a>(&'a self, line: usize) -> Vec<(usize, usize, usize)> {
let regex: Regex = Regex::new("\\d+").unwrap();
regex.find_iter(&self.lines[line])
.map(|x| {
let index = x.start();
let str = x.as_str();
let result = (index, index + str.len() - 1, str.parse::<usize>().expect("Must be numeric"));
result
})
.filter(move |(start, end, _)| self.has_adjacent_symbol(line, *start, *end))
.collect::<Vec<(usize, usize, usize)>>()
}
fn all_numbers<'a>(&'a self)-> impl Iterator<Item = usize> + 'a {
(0..self.height())
.flat_map(|l| self.numbers(l))
.map(|(_, _, num)| num)
}
fn has_adjacent_symbol(&self, line: usize, start: usize, end: usize) -> bool {
if start > 0 && is_symbol(self.char_at(start - 1, line)) {
return true;
}
if end + 1 < self.width && is_symbol(self.char_at(end + 1, line)) {
return true;
}
let xmin = if start > 0 { start - 1 } else { start };
let xmax = min(end + 1, self.width - 1);
if line > 0 && self.lines[line - 1][xmin..=xmax].contains(is_symbol) {
return true;
}
if line + 1 < self.height() && self.lines[line + 1][xmin..=xmax].contains(is_symbol) {
return true;
}
false
}
}
fn is_symbol(ch: char) -> bool {
ch != '.' && !ch.is_numeric()
}
fn main() {
let grid = Grid::read();
let part1 = grid.all_numbers().fold(0, |a, b| a + b);
println!("Part 1: {part1}");
}

68
2023/src/bin/day4.rs Normal file
View file

@ -0,0 +1,68 @@
use std::collections::HashSet;
#[derive(Debug)]
struct Card {
number: usize,
winning: HashSet<usize>,
numbers: Vec<usize>
}
impl Card {
fn new(number: usize, winning: HashSet<usize>, numbers: Vec<usize>) -> Self { Self { number, winning, numbers } }
fn winning_numbers(&self) -> usize {
self.numbers.iter().filter(|x| self.winning.contains(x)).count()
}
fn score(&self) -> usize {
let winners = self.winning_numbers();
if winners > 0 {
2usize.pow((winners - 1).try_into().unwrap())
} else {
0
}
}
}
mod parser {
use std::collections::{HashSet, hash_map::RandomState};
use nom::{IResult, multi::{many0, separated_list0}, Parser, sequence::{tuple, preceded}, bytes::complete::tag, character::complete::{multispace0, digit1}, combinator::map_res};
use nom::character::complete::multispace1;
use crate::Card;
fn number(input: &str) -> IResult<&str, usize> {
map_res(digit1, str::parse)(input)
}
fn list(input: &str) -> IResult<&str, Vec<usize>> {
preceded(multispace0, separated_list0(multispace1, number)).parse(input)
}
fn card(input: &str) -> IResult<&str, Card> {
tuple((
tag("Card"),
preceded(multispace1, number),
tag(":"),
list
.map(|list| HashSet::<usize, RandomState>::from_iter(list.iter().map(|x| *x))),
preceded(multispace0, tag("|")),
list,
multispace0
)).map(|(_, card_index, _, winning, _, given, _)| Card::new(card_index, winning, given))
.parse(input)
}
pub fn input(input: &str) -> IResult<&str, Vec<Card>> {
many0(card).parse(input)
}
}
fn main() {
let data = aoc::read_input();
let cards = parser::input(&data).expect("Parsing failed").1;
let part1 = cards.into_iter().fold(0, |a, b| a + b.score());
println!("Part 1: {part1}");
}

100
2023/src/bin/day5.rs Normal file
View file

@ -0,0 +1,100 @@
use aoc::read_input;
#[derive(Debug)]
struct MapEntry {
destination: usize,
source: usize,
length: usize,
}
impl MapEntry {
fn map(&self, input: usize) -> Option<usize> {
if input < self.source || self.source + self.length <= input {
return None
}
return Some(self.destination + ( input - self.source))
}
}
#[derive(Debug)]
struct Map {
entries: Vec<MapEntry>
}
impl Map {
fn map(&self, input: usize) -> usize {
self.entries.iter()
.find_map( |e| e.map(input))
.unwrap_or(input)
}
}
mod parser {
use aoc::parsing::number;
use nom::{IResult, sequence::{tuple, preceded, terminated}, character::complete::{newline, multispace1}, multi::{separated_list0, count}, error::Error, bytes::complete::tag};
use nom::Parser;
use crate::MapEntry;
fn map_entry(input: &str) -> IResult<&str, MapEntry> {
tuple((
number,
preceded(multispace1, number),
preceded(multispace1, number),
))
.map(|(a, b, c)| MapEntry{destination: a, source: b, length: c})
.parse(input)
}
fn map(input: &str) -> IResult<&str, crate::Map> {
terminated(separated_list0(newline, map_entry),newlines)
.map(|x| crate::Map{entries: x})
.parse(input)
}
fn map_with_title(title: &str) -> impl Parser<&str, crate::Map, Error<&str>>{
preceded(tuple((tag(title), tag(":"), newline)), map)
}
fn newlines(input: &str) -> IResult<&str, ()> {
count(newline, 2).map(|_| ()).parse(input)
}
fn seeds(input: &str) -> IResult<&str, Vec<usize>> {
terminated(preceded(tag("seeds: "), separated_list0(multispace1, number)), newlines)
.parse(input)
}
pub fn input(input: &str) -> IResult<&str, (Vec<usize>, crate::Map, crate::Map, crate::Map, crate::Map, crate::Map, crate::Map, crate::Map)> {
tuple((
seeds,
map_with_title("seed-to-soil map"),
map_with_title("soil-to-fertilizer map"),
map_with_title("fertilizer-to-water map"),
map_with_title("water-to-light map"),
map_with_title("light-to-temperature map"),
map_with_title("temperature-to-humidity map"),
map_with_title("humidity-to-location map")
))
.parse(input)
}
}
fn main() {
let data = read_input();
let (_, input) = parser::input(&data).expect("Stuff");
let (seeds, seed_soil, soil_fertilizer, fertilizer_water, water_light, light_temp, temp_humid, humid_location) = input;
let x = seeds.iter()
.map(|seed| seed_soil.map(*seed) )
.map(|soil| soil_fertilizer.map(soil) )
.map(|fert| fertilizer_water.map(fert) )
.map(|water| water_light.map(water) )
.map(|light| light_temp.map(light) )
.map(|temp| temp_humid.map(temp) )
.map(|humid| humid_location.map(humid) )
.min()
.expect("Need a solution");
println!("Part 1: {x}");
}

36
2023/src/bin/day6.rs Normal file
View file

@ -0,0 +1,36 @@
struct Race {
time: usize,
distance: usize,
}
impl Race {
fn calculate_distance(&self, charge: usize) -> usize {
let speed = charge;
let move_time = self.time - charge;
move_time * speed
}
fn ways_to_win(&self) -> usize {
(1..self.time).filter(|charge| self.calculate_distance(*charge) > self.distance).count()
}
}
/*
Time: 48 87 69 81
Distance: 255 1288 1117 1623
*/
fn main() {
let input = vec![
Race { time: 48, distance: 255 },
Race { time: 87, distance: 1288 },
Race { time: 69, distance: 1117 },
Race { time: 81, distance: 1623 },
];
let part1 = input.iter().map(Race::ways_to_win).fold(1, |a,b| a * b);
println!("Part 1: {part1}");
let input2 = Race { time: 48876981, distance: 255128811171623 };
println!("Part 2: {}", input2.ways_to_win());
}

172
2023/src/bin/day7-2.rs Normal file
View file

@ -0,0 +1,172 @@
use std::{error::Error, collections::HashMap, cmp::Ordering};
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy, Hash)]
enum Label {
J,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
T,
Q,
K,
A
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
struct Hand {
cards: [Label; 5]
}
struct WrongCardCount;
impl TryFrom<Vec<Label>> for Hand {
type Error = WrongCardCount;
fn try_from(value: Vec<Label>) -> Result<Self, Self::Error> {
let cards: [Label; 5] = value.try_into().map_err(|_| WrongCardCount)?;
Ok(Hand { cards })
}
}
mod parser {
use nom::{character::complete::{one_of, multispace1, newline}, Parser, combinator::{map_opt, map_res}, multi::{count, separated_list0}, sequence::{preceded, tuple}};
use aoc::parsing::number;
fn label(input: &str) -> aoc::parsing::Result<crate::Label> {
map_opt(one_of("23456789TJQKA"), crate::Label::from_char).parse(input)
}
fn hand(input: &str) -> aoc::parsing::Result<crate::Hand> {
map_res(count(label, 5), |vec| vec.try_into()).parse(input)
}
fn line(input: &str) -> aoc::parsing::Result<(crate::Hand, usize)> {
tuple((
hand,
preceded(multispace1, number)
)).parse(input)
}
pub fn input(input: &str) -> aoc::parsing::Result<Vec<(crate::Hand, usize)>> {
separated_list0(newline, line).parse(input)
}
}
impl Label {
fn from_char(ch: char) -> Option<Label> {
Some(match ch {
'2' => Label::Two,
'3' => Label::Three,
'4' => Label::Four,
'5' => Label::Five,
'6' => Label::Six,
'7' => Label::Seven,
'8' => Label::Eight,
'9' => Label::Nine,
'T' => Label::T,
'J' => Label::J,
'Q' => Label::Q,
'K' => Label::K,
'A' => Label::A,
_ => return None
})
}
}
impl Hand {
fn counts(&self) -> (Vec<usize>, usize) {
let mut result: HashMap<Label, usize> = HashMap::new();
for card in self.cards {
*result.entry(card).or_insert(0) += 1;
}
let jokers = result.remove(&Label::J).unwrap_or(0);
let mut values: Vec<usize> = result.values().map(|x| *x).collect();
values.sort_by(|a, b| b.cmp(a));
return (values, jokers);
}
fn value(&self) -> Value {
let (counts, mut jokers) = self.counts();
let mut counts = counts.iter();
let first = counts.next().unwrap_or(&0);
if first + jokers >= 5 {
return Value::Five;
}
if first + jokers >= 4 {
return Value::Four;
}
if first + jokers >= 3 {
jokers -= 3 - first;
let next = *counts.next().unwrap_or(&0);
if next + jokers >= 2 {
return Value::FullHouse;
}
return Value::Three;
}
if first + jokers >= 2 {
jokers -= 2 - first;
let next = *counts.next().unwrap_or(&0);
if next + jokers >= 2 {
return Value::TwoPair;
}
return Value::OnePair;
}
Value::HighCard
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
enum Value {
HighCard,
OnePair,
TwoPair,
Three,
FullHouse,
Four,
Five,
}
impl Ord for Hand {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let value = self.value().cmp(&other.value());
if value == Ordering::Equal {
self.cards.cmp(&other.cards)
} else {
value
}
}
}
fn main() {
let input = aoc::read_input();
let (_, mut result) = parser::input(&input).expect("Parse error");
result.sort_by(|a,b| a.0.cmp(&b.0));
for (rank, (hand, _)) in result.iter().enumerate() {
println!("{}: {:?} {:?}", rank + 1, hand, hand.value());
}
let part1 = result.iter()
.enumerate()
.map(|(rank, (_, value))| (rank + 1) * value)
.fold(0, |a, b| a + b);
println!("Part 1: {part1}");
}

152
2023/src/bin/day7.rs Normal file
View file

@ -0,0 +1,152 @@
use std::{error::Error, collections::HashMap, cmp::Ordering};
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy, Hash)]
enum Label {
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
T,
J,
Q,
K,
A
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
struct Hand {
cards: [Label; 5]
}
struct WrongCardCount;
impl TryFrom<Vec<Label>> for Hand {
type Error = WrongCardCount;
fn try_from(value: Vec<Label>) -> Result<Self, Self::Error> {
let cards: [Label; 5] = value.try_into().map_err(|_| WrongCardCount)?;
Ok(Hand { cards })
}
}
mod parser {
use nom::{character::complete::{one_of, multispace1, newline}, Parser, combinator::{map_opt, map_res}, multi::{count, separated_list0}, sequence::{preceded, tuple}};
use aoc::parsing::number;
fn label(input: &str) -> aoc::parsing::Result<crate::Label> {
map_opt(one_of("23456789TJQKA"), crate::Label::from_char).parse(input)
}
fn hand(input: &str) -> aoc::parsing::Result<crate::Hand> {
map_res(count(label, 5), |vec| vec.try_into()).parse(input)
}
fn line(input: &str) -> aoc::parsing::Result<(crate::Hand, usize)> {
tuple((
hand,
preceded(multispace1, number)
)).parse(input)
}
pub fn input(input: &str) -> aoc::parsing::Result<Vec<(crate::Hand, usize)>> {
separated_list0(newline, line).parse(input)
}
}
impl Label {
fn from_char(ch: char) -> Option<Label> {
Some(match ch {
'2' => Label::Two,
'3' => Label::Three,
'4' => Label::Four,
'5' => Label::Five,
'6' => Label::Six,
'7' => Label::Seven,
'8' => Label::Eight,
'9' => Label::Nine,
'T' => Label::T,
'J' => Label::J,
'Q' => Label::Q,
'K' => Label::K,
'A' => Label::A,
_ => return None
})
}
}
impl Hand {
fn counts(&self) -> Vec<usize> {
let mut result: HashMap<Label, usize> = HashMap::new();
for card in self.cards {
*result.entry(card).or_insert(0) += 1;
}
let mut values: Vec<usize> = result.values().map(|x| *x).collect();
values.sort_by(|a, b| b.cmp(a));
return values;
}
fn value(&self) -> Value {
let counts = self.counts();
let mut counts = counts.iter();
let first = counts.next().expect("Cannot be empty");
match first {
5 => Value::Five,
4 => Value::Four,
3 => {
let next = counts.next().expect("Cannot be empty");
if *next == 2 { Value::FullHouse } else { Value::Three }
}
2 => {
let next = counts.next().expect("Cannot be empty");
if *next == 2 { Value::TwoPair } else { Value::OnePair }
}
_ => Value::HighCard
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
enum Value {
HighCard,
OnePair,
TwoPair,
Three,
FullHouse,
Four,
Five,
}
impl Ord for Hand {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let value = self.value().cmp(&other.value());
if value == Ordering::Equal {
self.cards.cmp(&other.cards)
} else {
value
}
}
}
fn main() {
let input = aoc::read_input();
let (_, mut result) = parser::input(&input).expect("Parse error");
result.sort_by(|a,b| a.0.cmp(&b.0));
for (rank, (hand, _)) in result.iter().enumerate() {
println!("{}: {:?} {:?}", rank + 1, hand, hand.value());
}
let part1 = result.iter()
.enumerate()
.map(|(rank, (_, value))| (rank + 1) * value)
.fold(0, |a, b| a + b);
println!("Part 1: {part1}");
}

107
2023/src/bin/day8.rs Normal file
View file

@ -0,0 +1,107 @@
use std::collections::HashMap;
mod parsing {
use nom::{Parser, sequence::{tuple, terminated}, character::complete::{newline, one_of}, multi::{many_till, separated_list0}, combinator::{map}, bytes::complete::{take, tag}};
use std::collections::HashMap;
fn instructions(input: &str) -> aoc::parsing::Result<Vec<char>> {
map(many_till(one_of("LR"), newline), |(result,_)| result).parse(input)
}
fn node(input: &str) -> aoc::parsing::Result<&str> {
take(3usize).parse(input)
}
fn map_line(input: &str) -> aoc::parsing::Result<(&str, &str, &str)> {
tuple((
terminated(node, tag(" = (")),
terminated(node, tag(", ")),
terminated(node, tag(")"))
)).parse(input)
}
fn parse_map(input: &str) -> aoc::parsing::Result<HashMap<&str, (&str, &str)>> {
map(separated_list0(newline, map_line), |list| {
let mut result = HashMap::with_capacity(list.len());
for (node, left, right) in list {
result.insert(node, (left, right));
}
result
}).parse(input)
}
pub fn input(input: &str) -> aoc::parsing::Result<(Vec<char>, HashMap<&str, (&str, &str)>)> {
tuple((
terminated(instructions, newline),
parse_map
)).parse(input)
}
}
type Map<'a> = HashMap<&'a str, (&'a str, &'a str)>;
struct Camel<'a> {
position: &'a str,
map: &'a Map<'a>
}
impl Camel<'_> {
fn new<'a>(position: &'a str, map: &'a Map) -> Camel<'a> {
assert!(position.len() == 3);
Camel { position, map }
}
fn go(&mut self, direction: char) {
let (left, right) = self.map.get(self.position).expect(&format!("Position {} not in map", self.position));
self.position = match direction {
'L' => left,
'R' => right,
_ => panic!("Invalid direction {direction}")
}
}
fn at_goal(&self) -> bool {
self.position.chars().nth(2).unwrap() == 'Z'
}
}
fn main() {
let input = aoc::read_input();
let (_, (instructions, map)) = parsing::input(&input).expect("Failed to parse");
let mut iter = instructions.iter().cycle();
let mut camel = Camel::new("AAA", &map);
let mut steps = 0;
while camel.position != "ZZZ" {
camel.go(*iter.next().unwrap());
steps += 1;
}
println!("Part 1: {steps}");
let mut camels: Vec<_> = map.keys().filter_map(|key| if key.chars().last().unwrap() == 'A' {
Some(Camel::new(key, &map))
} else {
None
}).collect();
println!("{:?}", camels.iter().map(|x| x.position).collect::<Vec<_>>());
let mut iter = instructions.iter().cycle();
let mut steps = 0;
while !camels.iter().all(Camel::at_goal) {
let direction = *iter.next().unwrap();
for camel in camels.iter_mut() {
camel.go(direction);
}
steps += 1;
}
println!("Part 2: {steps}");
}

42
2023/src/bin/day9.rs Normal file
View file

@ -0,0 +1,42 @@
use aoc::read_input;
use aoc::{parse_input, parsing::numbers};
use nom::multi::many0;
use nom::Parser;
fn next_element(list: &Vec<isize>) -> isize
{
if list.iter().all(|x| *x == 0) {
0
} else {
let last = list.last().expect("Cannot use with empty list");
last + next_element(&differences(&list))
}
}
fn previous_element(list: &Vec<isize>) -> isize
{
if list.iter().all(|x| *x == 0) {
0
} else {
let first = list.first().expect("Cannot use with empty list");
first - previous_element(&differences(&list))
}
}
fn differences(list: &Vec<isize>) -> Vec<isize>
{
use itertools::Itertools;
list.iter().tuple_windows().map(|(a, b)| b - a).collect()
}
fn main() {
let input = read_input();
let (_, lists) = many0(numbers).parse(&input).unwrap();
let part1 = lists.iter().fold(0, |a, b| a + next_element(b));
println!("Part 1: {part1}");
let part2 = lists.iter().fold(0, |a, b| a + previous_element(b));
println!("Part 2: {part2}");
}

150
2023/src/grid.rs Normal file
View file

@ -0,0 +1,150 @@
use std::{io, str::Chars, iter::FusedIterator};
use itertools::Itertools;
#[derive(Debug)]
pub struct GridVec {
lines: Vec<String>,
width: usize,
}
pub struct Column<'a> {
grid: &'a GridVec,
x: usize,
y: usize
}
impl <'a> Column<'a> {
fn new(grid: &'a GridVec, x: usize) -> Column<'a> {
Column { grid, x, y: 0 }
}
}
impl <'a> Iterator for Column<'a> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
if self.y < self.grid.height() {
let result = self.grid.char_at(self.x, self.y);
self.y += 1;
Some(result)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl ExactSizeIterator for Column<'_> {
fn len(&self) -> usize {
self.grid.height() - self.y
}
}
impl FusedIterator for Column<'_> {}
pub trait Grid where Self: Sized {
type ColumnIter<'a>: ExactSizeIterator<Item = char> + 'a where Self: 'a;
type RowIter<'a>: Iterator<Item = char> + 'a where Self: 'a;
fn new(lines: Vec<String>) -> Self;
fn read() -> Self {
let lines: Vec<String> = io::stdin().lines().map(|line| line.expect("Cannot read line")).collect();
Self::new(lines)
}
fn read_one<I>(input: &mut I) -> Option<Self>
where I: Iterator<Item = String>
{
let lines = input.take_while(|l| !l.is_empty()).collect_vec();
if lines.is_empty() {
None
} else {
Some(Self::new(lines))
}
}
fn char_at(&self, x: usize, y: usize) -> char {
self.line_at(y).chars().nth(x).expect("Invalid index")
}
fn set_char_at(&mut self, x: usize, y: usize, ch: char);
fn height(&self) -> usize;
fn width(&self) -> usize;
fn line_at(&self, y: usize) -> &str;
fn find(&self, ch: char) -> Option<(usize, usize)> {
for y in 0..self.height() {
if let Some(pos) = self.line_at(y).find(ch) {
return Some((pos, y));
}
}
None
}
fn column<'a>(&'a self, x: usize) -> Self::ColumnIter<'a>;
fn row<'a>(&'a self, y: usize) -> Self::RowIter<'a>;
}
impl Grid for GridVec {
type ColumnIter<'a> = Column<'a>;
type RowIter<'a> = Chars<'a>;
fn column<'a>(&'a self, x: usize) -> Self::ColumnIter<'a> {
Column::new(self, x)
}
fn row<'a>(&'a self, y: usize) -> Self::RowIter<'a> {
self.line_at(y).chars()
}
fn new(lines: Vec<String>) -> Self {
let width = lines.first().map_or(0, |l| l.len());
let ok = lines.iter().skip(1).all(|line| line.len() == width);
if !ok {
println!("Read lines: {:?}", lines);
panic!("Not all lines same length");
}
GridVec {lines, width }
}
fn char_at(&self, x: usize, y: usize) -> char {
self.lines[y].chars().nth(x).expect("Invalid index")
}
fn set_char_at(&mut self, x: usize, y: usize, ch: char) {
let line = &mut self.lines[y];
line.replace_range(x..=x, &ch.to_string());
}
fn height(&self) -> usize { self.lines.len() }
fn width(&self) -> usize { self.width }
fn line_at(&self, y: usize) -> &str {
&self.lines[y]
}
}
impl GridVec {
pub fn print(&self) {
for line in self.lines.iter() {
println!("{line}");
}
}
}

22
2023/src/lib.rs Normal file
View file

@ -0,0 +1,22 @@
pub mod parsing;
pub mod grid;
use std::io::{self, Read};
pub fn read_input() -> String
{
let mut input = String::new();
io::stdin().read_to_string(&mut input).expect("Reading failed");
input
}
pub fn parse_input<T, P>(parser: P) -> T
where for<'a> P: parsing::Parser<'a, T>
{
let mut parser = parser;
let input = read_input();
parser.result(&input).expect("Could not parse input")
}

23
2023/src/parsing.rs Normal file
View file

@ -0,0 +1,23 @@
use nom::{IResult, character::complete::{i64, u64, newline, space1}, combinator::map, error::Error, sequence::terminated, multi::separated_list0};
pub fn number(input: &str) -> Result<usize> {
map(u64, |x| x as usize)(input)
}
pub fn signed_number(input: &str) -> Result<isize> {
map(i64, |x| x as isize)(input)
}
pub fn numbers(input: &str) -> Result<Vec<isize>> {
use nom::Parser;
terminated(separated_list0(space1, signed_number), newline).parse(input)
}
pub type Result<'a, T> = IResult<&'a str, T>;
pub trait Parser<'a, T>: nom::Parser<&'a str, T, Error<&'a str>> {
fn result(&mut self, input: &'a str) -> std::result::Result<T, nom::Err<Error<&'a str>>> {
self.parse(input).map(|(_, result)| result)
}
}