Add 2023 solutions
This commit is contained in:
parent
7f8a4ebb89
commit
10f30eb274
48 changed files with 7418 additions and 0 deletions
60
2023/src/bin/day1.rs
Normal file
60
2023/src/bin/day1.rs
Normal 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(®ex_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 doesn’t 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
100
2023/src/bin/day10.rs
Normal 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
64
2023/src/bin/day11.rs
Normal 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
57
2023/src/bin/day12.rs
Normal 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
41
2023/src/bin/day13.rs
Normal 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
45
2023/src/bin/day14.rs
Normal 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
67
2023/src/bin/day15.rs
Normal 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
96
2023/src/bin/day16.rs
Normal 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
3
2023/src/bin/day17.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
|
||||
}
|
163
2023/src/bin/day2.rs
Normal file
163
2023/src/bin/day2.rs
Normal 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
81
2023/src/bin/day3.rs
Normal 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
68
2023/src/bin/day4.rs
Normal 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
100
2023/src/bin/day5.rs
Normal 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
36
2023/src/bin/day6.rs
Normal 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
172
2023/src/bin/day7-2.rs
Normal 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
152
2023/src/bin/day7.rs
Normal 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
107
2023/src/bin/day8.rs
Normal 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
42
2023/src/bin/day9.rs
Normal 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
150
2023/src/grid.rs
Normal 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
22
2023/src/lib.rs
Normal 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
23
2023/src/parsing.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue