Compare commits

..

36 Commits

Author SHA1 Message Date
000003902f wip: cloned graph 2023-12-20 12:58:47 +01:00
00000380c1 wip: part 1 debugging 2023-12-20 12:30:22 +01:00
0000037022 wip: day 20 2023-12-20 10:38:29 +01:00
00000360db solution: day 19 clean 2023-12-19 18:12:27 +01:00
00000350d1 solution: day 19 2023-12-19 17:56:15 +01:00
0000034077 solution: day 18 2023-12-18 21:02:50 +01:00
0000033086 solution: day 17 2023-12-17 14:41:03 +01:00
0000032032 solution: day 16 2023-12-17 14:12:57 +01:00
00000310ba chore: fix clippy suggestions for day 14 2023-12-17 14:11:49 +01:00
0000030011 solution: day 15 2023-12-15 12:28:25 +01:00
0000029007 solution: day 14 2023-12-15 12:20:27 +01:00
00000280ae solution: day 13 clean 2023-12-13 23:15:21 +01:00
000002707d fix: tests for day 11 2023-12-13 15:01:34 +01:00
0000026098 fix: tests for day 9 2023-12-13 14:46:33 +01:00
000002508c solution: day 13 2023-12-13 14:44:59 +01:00
0000024016 solution: day 12 2023-12-12 23:10:56 +01:00
000002301f solution: day 11 2023-12-11 17:51:58 +01:00
000002200d solution: day 10 xray 2023-12-10 19:23:55 +01:00
0000021091 solution: day 10 2023-12-10 15:28:03 +01:00
000002005e chore: reorder lib root 2023-12-09 13:39:27 +01:00
00000190b5 solution: day 9 2023-12-09 13:38:57 +01:00
00000180c1 solution: day 8 2023-12-08 08:01:10 +01:00
0000017058 feat: add read_file_part 2023-12-08 08:00:44 +01:00
00000160a1 solution: day 7 clean 2023-12-07 18:57:48 +01:00
0000015082 solution: day5 ranges
Squashed commit of the following:

commit 000001603e10102f634c2f4ca7eb310ec72f1863
Author: Matej Janežič <janezic.mj@gmail.com>
Date:   Wed Dec 6 20:47:26 2023 +0100

    day(5): ranges

commit 0000015049
Author: Matej Janežič <janezic.mj@gmail.com>
Date:   Wed Dec 6 18:58:58 2023 +0100

    wip: solve with range type
2023-12-06 20:49:10 +01:00
00000140d3 solution: day 6 clean 2023-12-06 19:39:39 +01:00
00000130fa solution: day 6 2023-12-06 07:06:16 +01:00
000001200a solution: day 5 clean 2023-12-06 00:48:46 +01:00
00000110da solution: day 5 2023-12-05 07:41:42 +01:00
0000010044 solution: day 4 2023-12-04 06:42:15 +01:00
000000906f solution: day 3 2023-12-03 06:48:30 +01:00
00000080fc solution: day 1 shorter version 2023-12-02 14:54:55 +01:00
000000701f solution: day 2 clean 2023-12-02 14:23:33 +01:00
00000060b5 solution: day 2 2023-12-02 06:43:58 +01:00
00000050ae fix: remove ci badge 2023-12-01 07:01:32 +01:00
000000402b solution: day 1 clean 2023-12-01 06:53:06 +01:00
44 changed files with 2795 additions and 47 deletions

View File

@@ -1,4 +1,3 @@
![ci](https://github.com/janezicmatej/aoc2023/actions/workflows/ci.yml/badge.svg)
# Advent-of-Code 2023
*This is a dumbed down version of [fspoettel/advent-of-code-rust](https://github.com/fspoettel/advent-of-code-rust) with some extra features*

6
data/examples/02.txt Normal file
View File

@@ -0,0 +1,6 @@
Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green

10
data/examples/03.txt Normal file
View File

@@ -0,0 +1,10 @@
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..

6
data/examples/04.txt Normal file
View File

@@ -0,0 +1,6 @@
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11

33
data/examples/05.txt Normal file
View File

@@ -0,0 +1,33 @@
seeds: 79 14 55 13
seed-to-soil map:
50 98 2
52 50 48
soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15
fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4
water-to-light map:
88 18 7
18 25 70
light-to-temperature map:
45 77 23
81 45 19
68 64 13
temperature-to-humidity map:
0 69 1
1 0 69
humidity-to-location map:
60 56 37
56 93 4

2
data/examples/06.txt Normal file
View File

@@ -0,0 +1,2 @@
Time: 7 15 30
Distance: 9 40 200

5
data/examples/07.txt Normal file
View File

@@ -0,0 +1,5 @@
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483

5
data/examples/08-1.txt Normal file
View File

@@ -0,0 +1,5 @@
LLR
AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)

10
data/examples/08-2.txt Normal file
View File

@@ -0,0 +1,10 @@
LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)

3
data/examples/09.txt Normal file
View File

@@ -0,0 +1,3 @@
0 3 6 9 12 15
1 3 6 10 15 21
10 13 16 21 30 45

5
data/examples/10-1.txt Normal file
View File

@@ -0,0 +1,5 @@
..F7.
.FJ|.
SJ.L7
|F--J
LJ...

10
data/examples/10-2.txt Normal file
View File

@@ -0,0 +1,10 @@
FF7FSF7F7F7F7F7F---7
L|LJ||||||||||||F--J
FL-7LJLJ||||||LJL-77
F--JF--7||LJLJ7F7FJ-
L---JF-JLJ.||-FJLJJ7
|F|F-JF---7F7-L7L|7|
|FFJF7L7F-JF7|JL---7
7-L-JL7||F7|L7F-7F7|
L.L7LFJ|||||FJL7||LJ
L7JLJL-JLJLJL--JLJ.L

10
data/examples/11.txt Normal file
View File

@@ -0,0 +1,10 @@
...#......
.......#..
#.........
..........
......#...
.#........
.........#
..........
.......#..
#...#.....

6
data/examples/12.txt Normal file
View File

@@ -0,0 +1,6 @@
???.### 1,1,3
.??..??...?##. 1,1,3
?#?#?#?#?#?#?#? 1,3,1,6
????.#...#... 4,1,1
????.######..#####. 1,6,5
?###???????? 3,2,1

15
data/examples/13.txt Normal file
View File

@@ -0,0 +1,15 @@
#.##..##.
..#.##.#.
##......#
##......#
..#.##.#.
..##..##.
#.#.##.#.
#...##..#
#....#..#
..##..###
#####.##.
#####.##.
..##..###
#....#..#

10
data/examples/14.txt Normal file
View File

@@ -0,0 +1,10 @@
O....#....
O.OO#....#
.....##...
OO.#O....O
.O.....O#.
O.#..O.#.#
..O..#O..O
.......O..
#....###..
#OO..#....

1
data/examples/15.txt Normal file
View File

@@ -0,0 +1 @@
rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7

10
data/examples/16.txt Normal file
View File

@@ -0,0 +1,10 @@
.|...\....
|.-.\.....
.....|-...
........|.
..........
.........\
..../.\\..
.-.-/..|..
.|....-|.\
..//.|....

13
data/examples/17.txt Normal file
View File

@@ -0,0 +1,13 @@
2413432311323
3215453535623
3255245654254
3446585845452
4546657867536
1438598798454
4457876987766
3637877979653
4654967986887
4564679986453
1224686865563
2546548887735
4322674655533

14
data/examples/18.txt Normal file
View File

@@ -0,0 +1,14 @@
R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)

17
data/examples/19.txt Normal file
View File

@@ -0,0 +1,17 @@
px{a<2006:qkq,m>2090:A,rfg}
pv{a>1716:R,A}
lnx{m>1548:A,A}
rfg{s<537:gd,x>2440:R,A}
qs{s>3448:A,lnx}
qkq{x<1416:A,crn}
crn{x>2662:A,R}
in{s<1351:px,qqz}
qqz{s>2770:qs,m<1801:hdj,R}
gd{a>3333:R,R}
hdj{m>838:A,pv}
{x=787,m=2655,a=1222,s=2876}
{x=1679,m=44,a=2067,s=496}
{x=2036,m=264,a=79,s=2244}
{x=2461,m=1339,a=466,s=291}
{x=2127,m=1623,a=2188,s=1013}

5
data/examples/20.txt Normal file
View File

@@ -0,0 +1,5 @@
broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a

View File

@@ -1,66 +1,46 @@
pub fn part_one(input: &str) -> Option<u32> {
let mut c = 0;
for line in input.lines() {
let mut itr = line.chars().filter(|x| x.is_ascii_digit());
let mut itr = line.chars().filter(char::is_ascii_digit);
let f = itr.next().unwrap();
let l = itr.last().unwrap_or(f);
let first = itr.next().unwrap();
let last = itr.last().unwrap_or(first);
c += format!("{f}{l}").parse::<u32>().unwrap();
c += format!("{first}{last}").parse::<u32>().unwrap();
}
Some(c)
}
pub fn part_two(input: &str) -> Option<u32> {
let mut c = 0;
let nums = [
const NUMS: [&str; 9] = [
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
];
];
pub fn part_two(input: &str) -> Option<u32> {
// NOTE:(matej) this solution is O(n^2) since we search string for each substring separately
let mut c = 0;
let nums: Vec<(u32, String)> = (1..=9).zip(NUMS.into_iter().map(String::from)).collect();
for line in input.lines() {
let mut first = (line.len(), None);
let mut last = (0, None);
let mut all_matches = Vec::new();
for (idx, n) in nums.iter().enumerate() {
let matches: Vec<_> = line.match_indices(n).collect();
if matches.is_empty() {
continue;
for (n, str_n) in nums.iter() {
all_matches.extend(line.match_indices(str_n).map(|(x, _)| (x, *n)));
}
let m = matches.first().unwrap();
all_matches.extend(
line.chars()
.enumerate()
.filter(|(_, c)| c.is_ascii_digit())
.map(|(idx, c)| (idx, c.to_digit(10).unwrap())),
);
if m.0 < first.0 {
first = (m.0, Some(idx + 1));
}
let first = all_matches.iter().min_by_key(|&(idx, _)| idx).unwrap().1;
let last = all_matches.iter().max_by_key(|&(idx, _)| idx).unwrap().1;
let m = matches.last().unwrap();
if m.0 >= last.0 {
last = (m.0, Some(idx + 1));
}
}
let mut itr = line.chars().enumerate().filter(|(_, x)| x.is_ascii_digit());
let Some(f) = itr.next() else {
c += format!("{}{}", first.1.unwrap(), last.1.unwrap())
.parse::<u32>()
.unwrap();
continue;
};
let l = itr.last().unwrap_or(f);
if f.0 < first.0 {
first = (f.0, f.1.to_string().parse().ok());
}
if l.0 >= last.0 {
last = (l.0, l.1.to_string().parse().ok());
}
c += format!("{}{}", first.1.unwrap(), last.1.unwrap())
.parse::<u32>()
.unwrap();
c += first * 10 + last;
}
Some(c)

84
src/bin/02.rs Normal file
View File

@@ -0,0 +1,84 @@
use std::str::FromStr;
use aoc::parsers::to_vec;
struct Game {
id: u32,
balls: Vec<(u32, String)>,
}
impl FromStr for Game {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let rest = s.strip_prefix("Game ").unwrap();
let (id, rest) = rest.split_once(':').unwrap();
let id = id.parse().unwrap();
let balls = rest
.split([',', ';'])
.map(|x| x.strip_prefix(' ').unwrap().split_once(' ').unwrap())
.map(|(n, c)| (n.parse::<u32>().unwrap(), c.to_string()))
.collect();
Ok(Game { id, balls })
}
}
pub fn part_one(input: &str) -> Option<u32> {
let games: Vec<Game> = to_vec(input, '\n');
let rules = [(12, "red"), (13, "green"), (14, "blue")];
Some(
games
.iter()
.filter(|g| {
!g.balls
.iter()
.any(|(n, c)| rules.iter().any(|(rn, rc)| c == rc && n > rn))
})
.map(|g| g.id)
.sum(),
)
}
pub fn part_two(input: &str) -> Option<u32> {
let games: Vec<Game> = to_vec(input, '\n');
let mut total_power = 0;
for g in games {
let power: u32 = ["blue", "red", "green"]
.iter()
.map(|&c| {
g.balls
.iter()
.filter(|(_, cd)| c == cd.as_str())
.map(|(n, _)| n)
.max()
.unwrap()
})
.product();
total_power += power;
}
Some(total_power)
}
aoc::solution!(2);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 2)), Some(8));
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 2)),
Some(2286)
);
}
}

104
src/bin/03.rs Normal file
View File

@@ -0,0 +1,104 @@
struct MappedPart {
number: u32,
line: usize,
start: usize,
end: usize,
}
impl MappedPart {
fn is_adjacent(&self, line: usize, column: usize) -> bool {
self.line.abs_diff(line) <= 1
&& (self.start.abs_diff(column) <= 1 || self.end.abs_diff(column) <= 1)
}
}
fn build_map(input: &str) -> Vec<MappedPart> {
let mut numbers = Vec::new();
for (idl, line) in input.lines().enumerate() {
let mut index = 0;
for n in line.split(|c: char| !c.is_ascii_digit()) {
if n.is_empty() {
index += 1;
continue;
}
let num: u32 = n.parse().unwrap();
let num_len = n.len();
numbers.push(MappedPart {
number: num,
line: idl,
start: index,
end: index + num_len - 1,
});
index += num_len + 1;
}
}
numbers
}
pub fn part_one(input: &str) -> Option<u32> {
let numbers = build_map(input);
let mut part_numbers = 0;
for (idl, line) in input.lines().enumerate() {
for (idc, _) in line
.chars()
.enumerate()
.filter(|(_, c)| !(*c == '.' || c.is_ascii_digit()))
{
part_numbers += numbers
.iter()
.filter(|mp| mp.is_adjacent(idl, idc))
.map(|mp| mp.number)
.sum::<u32>();
}
}
Some(part_numbers)
}
pub fn part_two(input: &str) -> Option<u32> {
let numbers = build_map(input);
let mut gear_ratios = 0;
for (idl, line) in input.lines().enumerate() {
for (idc, _) in line.chars().enumerate().filter(|(_, c)| *c == '*') {
let touching: Vec<_> = numbers
.iter()
.filter(|mp| mp.is_adjacent(idl, idc))
.map(|mp| mp.number)
.collect();
if touching.len() == 2 {
gear_ratios += touching.iter().product::<u32>();
}
}
}
Some(gear_ratios)
}
aoc::solution!(3);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 3)),
Some(4361)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 3)),
Some(467835)
);
}
}

79
src/bin/04.rs Normal file
View File

@@ -0,0 +1,79 @@
use std::str::FromStr;
use aoc::parsers::to_vec;
struct ParseCardError;
struct Card {
winning: Vec<u32>,
numbers: Vec<u32>,
}
impl FromStr for Card {
type Err = ParseCardError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (_, rest) = s.split_once(": ").unwrap();
let (win, my) = rest.split_once(" | ").unwrap();
let winning: Vec<u32> = to_vec(win, ' ');
let numbers: Vec<u32> = to_vec(my, ' ');
Ok(Card { winning, numbers })
}
}
impl Card {
fn n_matches(&self) -> usize {
self.numbers
.iter()
.filter(|n| self.winning.contains(n))
.count()
}
fn score(&self) -> u32 {
let c = self.n_matches();
if c == 0 {
return 0;
}
2_u32.pow((self.n_matches() - 1) as u32)
}
}
pub fn part_one(input: &str) -> Option<u32> {
Some(
input
.lines()
.filter_map(|l| l.parse::<Card>().ok())
.map(|g| g.score())
.sum(),
)
}
pub fn part_two(input: &str) -> Option<u32> {
let cards: Vec<Card> = to_vec(input, '\n');
let mut multiples = vec![1; cards.len()];
for (i, card) in cards.iter().enumerate() {
for j in 0..card.n_matches() {
multiples[i + j + 1] += multiples[i];
}
}
Some(multiples.iter().sum::<u32>())
}
aoc::solution!(4);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 4)), Some(13));
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 4)), Some(30));
}
}

170
src/bin/05.rs Normal file
View File

@@ -0,0 +1,170 @@
use std::{
cmp::{max, min},
fmt::Debug,
ops::RangeInclusive,
str::FromStr,
};
use aoc::parsers::to_vec;
trait RangeInclusiveExt {
fn overlaps(&self, other: &Self) -> bool;
}
impl<T> RangeInclusiveExt for RangeInclusive<T>
where
T: PartialOrd,
{
fn overlaps(&self, other: &Self) -> bool {
self.contains(other.start()) || self.contains(other.end())
}
}
fn build_range(start: u64, range: u64) -> RangeInclusive<u64> {
start..=(start + range - 1)
}
#[derive(Debug)]
struct Mapping {
pub source: RangeInclusive<u64>,
pub destination: RangeInclusive<u64>,
}
struct ParseMappingError;
impl FromStr for Mapping {
type Err = ParseMappingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let nums: Vec<u64> = to_vec(s, ' ');
Ok(Self {
destination: build_range(nums[0], nums[2]),
source: build_range(nums[1], nums[2]),
})
}
}
impl Mapping {
fn map(&self, n: u64) -> u64 {
let shift = n - self.source.start();
self.destination.start() + shift
}
fn map_range(&self, r: RangeInclusive<u64>) -> RangeInclusive<u64> {
self.map(*r.start())..=(self.map(*r.end()))
}
fn split_range(&self, r: RangeInclusive<u64>) -> [Option<RangeInclusive<u64>>; 3] {
let mut fences = [
*r.start(),
*r.end() + 1,
max(*self.source.start(), *r.start()),
min(*self.source.end() + 1, *r.end() + 1),
];
fences.sort();
const ARRAY_REPEAT_VALUE: Option<RangeInclusive<u64>> = None;
let mut v = [ARRAY_REPEAT_VALUE; 3];
for i in 0..3 {
let f = fences[i];
let nf = fences[i + 1];
if f != nf {
v[i] = Some(f..=(nf - 1))
}
}
v
}
}
fn parse_map(maps: &str) -> Vec<Vec<Mapping>> {
let mut res = Vec::new();
for mapper in maps.split("\n\n") {
res.push(
mapper
.lines()
.skip(1)
.filter_map(|m| m.parse().ok())
.collect(),
)
}
res
}
pub fn part_one(input: &str) -> Option<u64> {
let (first, rest) = input.split_once("\n\n").unwrap();
let maps = parse_map(rest);
let seeds = to_vec::<u64, _>(first.strip_prefix("seeds: ").unwrap(), ' ');
let mut m = u64::MAX;
for seed in seeds.iter() {
let mut s = *seed;
'maps: for map in maps.iter() {
for inner in map.iter() {
if inner.source.contains(&s) {
s = inner.map(s);
continue 'maps;
}
}
}
m = min(m, s)
}
Some(m)
}
pub fn part_two(input: &str) -> Option<u64> {
let (first, rest) = input.split_once("\n\n").unwrap();
let maps = parse_map(rest);
let mut seeds_ranges: Vec<_> = to_vec::<u64, _>(first.strip_prefix("seeds: ").unwrap(), ' ')
.chunks(2)
.map(|x| build_range(x[0], x[1]))
.collect();
for mapping in maps.iter() {
let mut new_ranges = Vec::new();
'queue: while let Some(rng) = seeds_ranges.pop() {
for map in mapping {
if map.source.overlaps(&rng) {
let [pre, to_map, post] = map.split_range(rng);
new_ranges.push(map.map_range(to_map.unwrap()));
for r in [pre, post].into_iter().flatten() {
seeds_ranges.push(r);
}
continue 'queue;
}
}
new_ranges.push(rng);
}
seeds_ranges = new_ranges;
}
seeds_ranges
.iter()
.map(RangeInclusive::start)
.min()
.copied()
}
aoc::solution!(5);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 5)), Some(35));
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 5)), Some(46));
}
}

67
src/bin/06.rs Normal file
View File

@@ -0,0 +1,67 @@
fn win_options((time, distance): (u64, u64)) -> u64 {
let discriminant = ((time.pow(2) - 4 * distance) as f64).sqrt();
let first_zero = (time as f64 - discriminant) / 2.0;
let mut second_zero = (time as f64 + discriminant) / 2.0;
if second_zero.fract() == 0.0 {
second_zero -= 1.0
}
second_zero as u64 - first_zero as u64
}
pub fn part_one(input: &str) -> Option<u64> {
let [time, distance] = input
.lines()
.map(|l| {
l.split_whitespace()
.skip(1)
.filter_map(|n| n.parse().ok())
.collect::<Vec<u64>>()
})
.collect::<Vec<_>>()
.try_into()
.ok()?;
Some(time.into_iter().zip(distance).map(win_options).product())
}
pub fn part_two(input: &str) -> Option<u64> {
let [time, distance] = input
.lines()
.filter_map(|l| {
l.split_whitespace()
.skip(1)
.flat_map(|x| x.chars())
.collect::<String>()
.parse()
.ok()
})
.collect::<Vec<_>>()
.try_into()
.ok()?;
Some(win_options((time, distance)))
}
aoc::solution!(6);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 6)),
Some(288)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 6)),
Some(71503)
);
}
}

191
src/bin/07.rs Normal file
View File

@@ -0,0 +1,191 @@
use std::{cmp::Ordering, collections::HashMap, str::FromStr};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
enum Label {
Ace = 14,
King = 13,
Queen = 12,
Jack = 11,
Ten = 10,
Nine = 9,
Eight = 8,
Seven = 7,
Six = 6,
Five = 5,
Four = 4,
Three = 3,
Two = 2,
Joker = 1,
}
impl From<char> for Label {
fn from(value: char) -> Self {
use Label::*;
match value {
'A' => Ace,
'K' => King,
'Q' => Queen,
'J' => Jack,
'T' => Ten,
'9' => Nine,
'8' => Eight,
'7' => Seven,
'6' => Six,
'5' => Five,
'4' => Four,
'3' => Three,
'2' => Two,
'X' => Joker,
_ => unreachable!(),
}
}
}
#[derive(Debug)]
struct ParseHandError;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct Hand {
labels: [Label; 5],
}
impl FromStr for Hand {
type Err = ParseHandError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let labels = s
.chars()
.map(Label::from)
.collect::<Vec<_>>()
.try_into()
.map_err(|_| ParseHandError)?;
Ok(Hand { labels })
}
}
impl PartialOrd for Hand {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Hand {
fn cmp(&self, other: &Self) -> Ordering {
let sf = HandType::from(*self);
let so = HandType::from(*other);
sf.cmp(&so).then({
let mut c = Ordering::Equal;
for i in 0..5 {
c = self.labels[i].cmp(&other.labels[i]);
if c != Ordering::Equal {
break;
}
}
c
})
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum HandType {
Five = 6,
Four = 5,
FullHouse = 4,
Three = 3,
TwoPair = 2,
Pair = 1,
HighCard = 0,
}
impl From<Hand> for HandType {
fn from(value: Hand) -> Self {
let mut map = HashMap::new();
for c in value.labels {
*map.entry(c).or_insert(0) += 1;
}
let joker = map.remove(&Label::Joker).unwrap_or(0);
let is_n_k = |n| joker == n || map.values().filter(|&x| *x == n - joker).count() > 0;
if is_n_k(5) {
return Self::Five;
}
if is_n_k(4) {
return Self::Four;
}
// full house
if map.values().count() <= 2 {
return Self::FullHouse;
}
if is_n_k(3) {
return Self::Three;
}
// two pair
if map.values().count() <= 3 {
return Self::TwoPair;
}
if is_n_k(2) {
return Self::Pair;
}
Self::HighCard
}
}
pub fn part_one(input: &str) -> Option<u32> {
let mut v = input
.lines()
.filter_map(|l| l.split_once(' '))
.map(|(f, s)| (f.parse::<Hand>().unwrap(), s.parse::<u32>().unwrap()))
.collect::<Vec<_>>();
v.sort_by(|f, o| f.0.cmp(&o.0));
Some(
v.into_iter()
.enumerate()
.map(|(i, x)| (i as u32 + 1) * x.1)
.sum::<u32>(),
)
}
pub fn part_two(input: &str) -> Option<u32> {
let mut v = input
.replace('J', "X")
.lines()
.filter_map(|l| l.split_once(' '))
.map(|(f, s)| (f.parse::<Hand>().unwrap(), s.parse::<u32>().unwrap()))
.collect::<Vec<_>>();
v.sort_by(|f, o| f.0.cmp(&o.0));
Some(
v.into_iter()
.enumerate()
.map(|(i, x)| (i as u32 + 1) * x.1)
.sum::<u32>(),
)
}
aoc::solution!(7);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 7)),
Some(6440)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 7)),
Some(5905)
);
}
}

95
src/bin/08.rs Normal file
View File

@@ -0,0 +1,95 @@
use std::collections::HashMap;
use aoc::lcm;
pub fn part_one(input: &str) -> Option<u32> {
let mut map = HashMap::new();
let (directions, map_data) = input.split_once("\n\n")?;
for line in map_data.lines() {
let (k, v) = line.split_once(" = ")?;
let v = v.strip_prefix('(')?;
let v = v.strip_suffix(')')?;
let (l, r) = v.split_once(", ")?;
*map.entry(k).or_default() = (l, r);
}
let mut counter = 0;
let mut loc = "AAA";
for d in directions.chars().cycle() {
if loc == "ZZZ" {
break;
}
counter += 1;
let (l, r) = map[loc];
match d {
'L' => loc = l,
'R' => loc = r,
_ => unimplemented!(),
}
}
Some(counter)
}
pub fn part_two(input: &str) -> Option<u64> {
let mut map = HashMap::new();
let (directions, map_data) = input.split_once("\n\n")?;
for line in map_data.lines() {
let (k, v) = line.split_once(" = ")?;
let v = v.strip_prefix('(')?;
let v = v.strip_suffix(')')?;
let (l, r) = v.split_once(", ")?;
*map.entry(k).or_default() = (l, r);
}
let mut res = 1;
for k in map.keys().filter(|x| x.ends_with('A')) {
let mut location = *k;
for (i, d) in directions.chars().cycle().enumerate() {
let (l, r) = map[location];
location = match d {
'L' => l,
'R' => r,
_ => unimplemented!(),
};
if location.ends_with('Z') {
res = lcm(res, i + 1);
break;
}
}
}
Some(res as u64)
}
aoc::solution!(8);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file_part("examples", 8, 1)),
Some(6)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file_part("examples", 8, 2)),
Some(6)
);
}
}

90
src/bin/09.rs Normal file
View File

@@ -0,0 +1,90 @@
use std::collections::VecDeque;
fn find_history(v: VecDeque<i32>) -> VecDeque<VecDeque<i32>> {
let mut s = VecDeque::new();
s.push_back(v);
loop {
let mut all_zeros = true;
let last = s.back().unwrap();
let mut new = VecDeque::new();
for i in 0..(last.len() - 1) {
let diff = last[i + 1] - last[i];
if diff != 0 {
all_zeros = false;
}
new.push_back(diff);
}
s.push_back(new);
if all_zeros {
break;
}
}
s
}
fn extrapolate_forward(v: VecDeque<i32>) -> i32 {
let mut s = find_history(v);
for i in (1..s.len()).rev() {
let adder = *s[i].back().unwrap();
let last = *s[i - 1].back().unwrap();
s[i - 1].push_back(last + adder);
}
*s[0].back().unwrap()
}
fn extrapolate_back(v: VecDeque<i32>) -> i32 {
let mut s = find_history(v);
for i in (1..s.len()).rev() {
let adder = *s[i].front().unwrap();
let first = *s[i - 1].front().unwrap();
s[i - 1].push_front(first - adder);
}
*s[0].front().unwrap()
}
pub fn part_one(input: &str) -> Option<i32> {
Some(
input
.lines()
.map(|x| x.split(' ').filter_map(|y| y.parse().ok()).collect())
.map(extrapolate_forward)
.sum(),
)
}
pub fn part_two(input: &str) -> Option<i32> {
Some(
input
.lines()
.map(|x| x.split(' ').filter_map(|y| y.parse().ok()).collect())
.map(extrapolate_back)
.sum(),
)
}
aoc::solution!(9);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 9)),
Some(114)
);
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 9)), Some(2));
}
}

158
src/bin/10.rs Normal file
View File

@@ -0,0 +1,158 @@
use std::collections::HashSet;
const DIRS: [(isize, isize); 4] = [(1, 0), (0, 1), (-1, 0), (0, -1)];
fn next_move(dy: isize, dx: isize, c: char) -> Option<(isize, isize)> {
Some(match (dy, dx) {
(1, 0) => match c {
'|' => (1, 0),
'J' => (0, -1),
'L' => (0, 1),
_ => None?,
},
(0, 1) => match c {
'-' => (0, 1),
'7' => (1, 0),
'J' => (-1, 0),
_ => None?,
},
(-1, 0) => match c {
'|' => (-1, 0),
'7' => (0, -1),
'F' => (0, 1),
_ => None?,
},
(0, -1) => match c {
'-' => (0, -1),
'F' => (1, 0),
'L' => (-1, 0),
_ => None?,
},
_ => unreachable!(),
})
}
fn parse_input(input: &str) -> (Vec<Vec<char>>, (isize, isize)) {
let pipes: Vec<Vec<char>> = input.lines().map(|x| x.chars().collect()).collect();
let start = pipes
.iter()
.enumerate()
.find_map(|(i, x)| {
x.iter()
.enumerate()
.map(|(j, y)| (i, j, y))
.find(|(_, _, &x)| x == 'S')
})
.map(|(j, i, _)| (j as isize, i as isize))
.unwrap();
(pipes, start)
}
pub fn part_one(input: &str) -> Option<u32> {
let (pipes, start) = parse_input(input);
'start_dir: for d in DIRS {
let (mut dy, mut dx) = d;
let (mut ly, mut lx) = (start.0 + d.0, start.1 + d.1);
let mut counter = 0;
while start != (ly, lx) {
let p = match pipes.get(ly as usize).and_then(|x| x.get(lx as usize)) {
Some(x) => *x,
None => continue 'start_dir,
};
match next_move(dy, dx, p) {
Some((ndy, ndx)) => (dy, dx) = (ndy, ndx),
None => continue 'start_dir,
}
counter += 1;
(ly, lx) = (ly + dy, lx + dx);
}
return Some((counter + 1) / 2);
}
None
}
pub fn part_two(input: &str) -> Option<u32> {
let (pipes, start) = parse_input(input);
let mut border = HashSet::with_capacity(pipes.len() * pipes[0].len());
'start_dir: for d in DIRS {
border.clear();
border.insert(start);
let (mut dy, mut dx) = d;
let (mut ly, mut lx) = (start.0 + d.0, start.1 + d.1);
while start != (ly, lx) {
let p = match pipes.get(ly as usize).and_then(|x| x.get(lx as usize)) {
Some(x) => *x,
None => continue 'start_dir,
};
match next_move(dy, dx, p) {
Some((ndx, ndy)) => {
(dy, dx) = (ndx, ndy);
border.insert((ly, lx));
}
None => continue 'start_dir,
}
(ly, lx) = (ly + dy, lx + dx);
}
break;
}
let mut counter = 0;
for (sy, sx) in (1..pipes.len())
.map(|x| (x, 0))
.chain((0..pipes[0].len()).map(|x| (0, x)))
{
let mut inside = false;
for range in 0.. {
if let Some(c) = pipes.get(sy + range).and_then(|x| x.get(sx + range)) {
let is_border = border.contains(&((sy + range) as isize, (sx + range) as isize));
if is_border && !['7', 'L'].contains(c) {
inside = !inside;
}
if !is_border && inside {
counter += 1;
}
} else {
break;
}
}
}
Some(counter)
}
aoc::solution!(10);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file_part("examples", 10, 1)),
Some(8)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file_part("examples", 10, 2)),
Some(10)
);
}
}

74
src/bin/11.rs Normal file
View File

@@ -0,0 +1,74 @@
use std::collections::HashSet;
fn parse_input(input: &str) -> (Vec<(usize, usize)>, HashSet<usize>, HashSet<usize>) {
let mut galaxies = Vec::new();
let mut rows = HashSet::from_iter(0..input.lines().count());
let mut columns = HashSet::from_iter(0..input.lines().next().unwrap().len());
for (y, line) in input.lines().enumerate() {
for (x, c) in line.chars().enumerate() {
if c == '#' {
galaxies.push((y, x));
rows.remove(&y);
columns.remove(&x);
}
}
}
(galaxies, rows, columns)
}
fn solve(input: &str, explode: usize) -> Option<usize> {
let (galaxies, rows, columns) = parse_input(input);
let mut counter = 0;
for ((y1, x1), (y2, x2)) in galaxies
.iter()
.copied()
.enumerate()
.flat_map(|(i, x)| galaxies.iter().skip(i + 1).copied().map(move |y| (x, y)))
{
let y_abs = y1.abs_diff(y2);
let x_abs = x1.abs_diff(x2);
let x_extra = columns
.iter()
.filter(|x| (x1..=x2).contains(x) || (x2..=x1).contains(x))
.count();
let y_extra = rows
.iter()
.filter(|y| (y1..=y2).contains(y) || (y2..=y1).contains(y))
.count();
counter += x_abs + y_abs + (x_extra + y_extra) * (explode - 1);
}
Some(counter)
}
pub fn part_one(input: &str) -> Option<usize> {
solve(input, 2)
}
pub fn part_two(input: &str) -> Option<usize> {
solve(input, 1_000_000)
}
aoc::solution!(11);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 11)),
Some(374)
);
}
#[test]
fn test_part_two() {
let input = aoc::template::read_file("examples", 11);
assert_eq!(solve(&input, 10), Some(1030));
assert_eq!(solve(&input, 100), Some(8410));
}
}

114
src/bin/12.rs Normal file
View File

@@ -0,0 +1,114 @@
use std::{collections::HashMap, iter::once};
fn is_valid(sequence: &[char], ptr: usize, group: usize) -> bool {
let edges_front = *once(&'.').chain(sequence.iter()).nth(ptr).unwrap() != '#';
let edges_back = *sequence.iter().chain(once(&'.')).nth(ptr + group).unwrap() != '#';
let filled = sequence
.iter()
.chain(once(&'.').cycle())
.skip(ptr)
.take(group)
.all(|x| *x != '.');
edges_front && edges_back && filled
}
fn count(
memo: &mut HashMap<(usize, usize), usize>,
sequence: &[char],
groups: &[usize],
ptr: usize,
) -> usize {
match groups.split_first() {
None => !sequence.iter().skip(ptr).any(|c| *c == '#') as usize,
Some((group, r_groups)) => {
let remaining = r_groups.iter().sum();
let mut total = 0;
for idx in ptr..(sequence.len() - group - remaining + 1) {
if is_valid(sequence, idx, *group) {
let next = idx + *group + 1;
match memo.get(&(remaining, next)) {
Some(m) => total += m,
None => {
let count = count(memo, sequence, r_groups, next);
memo.insert((remaining, next), count);
total += count
}
}
}
if sequence[idx] == '#' {
break;
}
}
total
}
}
}
fn parse_line(line: &str) -> (Vec<char>, Vec<usize>) {
let (str_seq, str_grp) = line.split_once(' ').unwrap();
let sequence = str_seq.chars().collect();
let groups = str_grp.split(',').filter_map(|x| x.parse().ok()).collect();
(sequence, groups)
}
fn unfold(sequence: Vec<char>, groups: Vec<usize>, n: usize) -> (Vec<char>, Vec<usize>) {
let seq_len = sequence.len();
let grp_len = groups.len();
let new_sequence = sequence
.into_iter()
.chain(once('?'))
.cycle()
.take(seq_len * n + n - 1)
.collect();
let new_groups = groups.into_iter().cycle().take(grp_len * n).collect();
(new_sequence, new_groups)
}
pub fn part_one(input: &str) -> Option<usize> {
Some(
input
.lines()
.map(parse_line)
.map(|(sequence, groups)| count(&mut HashMap::new(), &sequence, &groups, 0))
.sum(),
)
}
pub fn part_two(input: &str) -> Option<usize> {
Some(
input
.lines()
.map(parse_line)
.map(|(sequence, groups)| unfold(sequence, groups, 5))
.map(|(sequence, groups)| count(&mut HashMap::new(), &sequence, &groups, 0))
.sum(),
)
}
aoc::solution!(12);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 12)),
Some(21)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 12)),
Some(525152)
);
}
}

64
src/bin/13.rs Normal file
View File

@@ -0,0 +1,64 @@
fn mirror_h(shape: &[Vec<char>], smudges: usize) -> Option<usize> {
(1..shape.len()).find(|&i| {
shape
.iter()
.skip(i)
.zip(shape.iter().take(i).rev())
.map(|(x, y)| {
x.iter()
.zip(y.iter())
.map(|(xx, yy)| (xx != yy) as usize)
.sum::<usize>()
})
.sum::<usize>()
== smudges
})
}
fn mirror_v(shape: &[Vec<char>], smudges: usize) -> Option<usize> {
let shape: Vec<Vec<char>> = (0..shape[0].len())
.map(|col| (0..shape.len()).map(|row| shape[row][col]).collect())
.collect();
mirror_h(&shape, smudges)
}
fn solve(input: &str, smudges: usize) -> usize {
input
.split("\n\n")
.map(|x| x.lines().map(|line| line.chars().collect()).collect())
.map(|shape: Vec<Vec<char>>| {
mirror_v(&shape, smudges).unwrap_or_default()
+ mirror_h(&shape, smudges).unwrap_or_default() * 100
})
.sum()
}
pub fn part_one(input: &str) -> Option<usize> {
Some(solve(input, 0))
}
pub fn part_two(input: &str) -> Option<usize> {
Some(solve(input, 1))
}
aoc::solution!(13);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 13)),
Some(405)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 13)),
Some(400)
);
}
}

115
src/bin/14.rs Normal file
View File

@@ -0,0 +1,115 @@
use std::{collections::HashMap, iter::once};
enum Tilt {
North,
West,
South,
East,
}
fn get_load(floor: &[Vec<char>]) -> usize {
floor
.iter()
.enumerate()
.flat_map(|(i, x)| x.iter().map(move |y| (i, y)))
.filter(|x| *x.1 == 'O')
.map(|x| floor.len() - x.0)
.sum()
}
fn swap<T: Copy>(floor: &mut [Vec<T>], from: (usize, usize), to: (usize, usize)) {
let a = floor[from.0][from.1];
let b = floor[to.0][to.1];
floor[from.0][from.1] = b;
floor[to.0][to.1] = a;
}
fn tilt(floor: &mut [Vec<char>], tilt: Tilt) {
let (inner, outer) = match tilt {
Tilt::North | Tilt::South => (floor[0].len(), floor.len()),
Tilt::West | Tilt::East => (floor.len(), floor[0].len()),
};
let inx_n = |(i, j)| (j, i);
let inx_s = |(i, j)| (inner - 1 - j, i);
let inx_w = |(i, j)| (i, j);
let inx_e = |(i, j)| (i, inner - 1 - j);
for i in 0..outer {
let mut ptr = 0;
for j in 0..inner {
let ((ii, jj), (pi, pj)) = match tilt {
Tilt::North => (inx_n((i, j)), inx_n((i, ptr))),
Tilt::South => (inx_s((i, j)), inx_s((i, ptr))),
Tilt::East => (inx_e((i, j)), inx_e((i, ptr))),
Tilt::West => (inx_w((i, j)), inx_w((i, ptr))),
};
match floor[ii][jj] {
'O' => {
swap(floor, (ii, jj), (pi, pj));
ptr += 1;
}
'#' => ptr = j + 1,
_ => (),
}
}
}
}
fn tilt_cycle(floor: &mut [Vec<char>]) {
use Tilt::*;
tilt(floor, North);
tilt(floor, West);
tilt(floor, South);
tilt(floor, East);
}
pub fn part_one(input: &str) -> Option<usize> {
let mut f: Vec<Vec<_>> = input.lines().map(|x| x.chars().collect()).collect();
tilt(&mut f, Tilt::North);
Some(get_load(&f))
}
pub fn part_two(input: &str) -> Option<usize> {
let mut f: Vec<Vec<_>> = input.lines().map(|x| x.chars().collect()).collect();
let mut memo: HashMap<String, usize> = HashMap::new();
for i in 1.. {
tilt_cycle(&mut f);
let repr = f
.iter()
.flat_map(|x| x.iter().chain(once(&'\n')))
.collect::<String>();
if let Some(ii) = memo.insert(repr, i) {
let m = i - ii;
let shift = (1_000_000_000 - ii) % m;
for _ in 0..shift {
tilt_cycle(&mut f);
}
break;
}
}
Some(get_load(&f))
}
aoc::solution!(14);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 14)),
Some(136)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 14)),
Some(64)
);
}
}

68
src/bin/15.rs Normal file
View File

@@ -0,0 +1,68 @@
fn hash(s: &str) -> usize {
let mut total = 0;
for c in s.chars().map(|x| x as usize) {
total += c;
total *= 17;
total %= 256;
}
total
}
pub fn part_one(input: &str) -> Option<usize> {
Some(input.lines().flat_map(|x| x.split(',')).map(hash).sum())
}
pub fn part_two(input: &str) -> Option<usize> {
let mut map = vec![Vec::<(&str, usize)>::new(); 256];
for instruction in input.lines().flat_map(|x| x.split(',')) {
let (label, n) = instruction.split_once(|x| x == '-' || x == '=').unwrap();
let hash = hash(label);
let indexed_map = map.get_mut(hash).unwrap();
if instruction.contains('-') {
if let Some((i, _)) = indexed_map.iter().enumerate().find(|(_, &x)| x.0 == label) {
indexed_map.remove(i);
}
} else {
let nbr = n.parse().unwrap();
if let Some((i, _)) = indexed_map.iter().enumerate().find(|(_, &x)| x.0 == label) {
indexed_map[i] = (label, nbr);
} else {
indexed_map.push((label, nbr))
}
}
}
Some(
map.into_iter()
.enumerate()
.flat_map(|(i, x)| {
x.into_iter()
.enumerate()
.map(move |(j, y)| (i + 1) * (j + 1) * y.1)
})
.sum(),
)
}
aoc::solution!(15);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 15)),
Some(1320)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 15)),
Some(145)
);
}
}

173
src/bin/16.rs Normal file
View File

@@ -0,0 +1,173 @@
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
fn next(&self, (y, x): (usize, usize)) -> (usize, usize) {
use Direction::*;
match self {
Up => (y.checked_sub(1).unwrap_or(usize::MAX), x),
Down => (y + 1, x),
Left => (y, x.checked_sub(1).unwrap_or(usize::MAX)),
Right => (y, x + 1),
}
}
}
#[derive(Debug, Clone, Copy)]
enum Mirror {
Vertical,
Horizontal,
EvenSymmetric,
OddSymmetric,
}
struct ParseMirrorError;
impl TryFrom<u8> for Mirror {
type Error = ParseMirrorError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
b'|' => Self::Vertical,
b'-' => Self::Horizontal,
b'\\' => Self::EvenSymmetric,
b'/' => Self::OddSymmetric,
_ => return Err(ParseMirrorError),
})
}
}
impl Mirror {
fn bounce(&self, d: &Direction) -> (Direction, Option<Direction>) {
use Direction::*;
use Mirror::*;
match self {
Vertical => match d {
Up | Down => (*d, None),
Left | Right => (Up, Some(Down)),
},
Horizontal => match d {
Up | Down => (Left, Some(Right)),
Left | Right => (*d, None),
},
EvenSymmetric => (
match d {
Up => Left,
Down => Right,
Left => Up,
Right => Down,
},
None,
),
OddSymmetric => (
match d {
Up => Right,
Down => Left,
Left => Down,
Right => Up,
},
None,
),
}
}
}
fn solve_with_start(layout: &[Vec<Option<Mirror>>], start: (usize, usize, Direction)) -> usize {
let mut queue = vec![start];
let mut visited = HashSet::new();
while let Some((y, x, d)) = queue.pop() {
let point = layout.get(y).and_then(|row| row.get(x));
if point.is_none() {
continue;
}
if !visited.insert((y, x, d)) {
continue;
}
if let Some(Some(m)) = point {
let (new_d, opt_new_d) = m.bounce(&d);
let (ny, nx) = new_d.next((y, x));
queue.push((ny, nx, new_d));
if let Some(od) = opt_new_d {
let (ony, onx) = od.next((y, x));
queue.push((ony, onx, od));
}
} else {
let (ny, nx) = d.next((y, x));
queue.push((ny, nx, d));
}
}
HashSet::<(usize, usize)>::from_iter(visited.into_iter().map(|(y, x, _)| (y, x))).len()
}
pub fn part_one(input: &str) -> Option<usize> {
let layout: Vec<Vec<_>> = input
.lines()
.map(|x| x.as_bytes().iter().map(|&x| x.try_into().ok()).collect())
.collect();
Some(solve_with_start(&layout, (0, 0, Direction::Right)))
}
pub fn part_two(input: &str) -> Option<usize> {
let layout: Vec<Vec<_>> = input
.lines()
.map(|x| x.as_bytes().iter().map(|&x| x.try_into().ok()).collect())
.collect();
let h = layout.len();
let w = layout[0].len();
let mut scores = Vec::with_capacity(2 * (h + w) + 1);
for hh in 0..h {
let left = solve_with_start(&layout, (hh, 0, Direction::Right));
let right = solve_with_start(&layout, (hh, w - 1, Direction::Left));
scores.push(left);
scores.push(right);
}
for ww in 0..w {
let downward = solve_with_start(&layout, (0, ww, Direction::Down));
let upward = solve_with_start(&layout, (h - 1, ww, Direction::Up));
scores.push(downward);
scores.push(upward);
}
scores.into_iter().max()
}
aoc::solution!(16);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 16)),
Some(46)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 16)),
Some(51)
);
}
}

192
src/bin/17.rs Normal file
View File

@@ -0,0 +1,192 @@
use std::{
collections::{BinaryHeap, HashMap},
ops::Neg,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
const ALL: [Direction; 4] = [
Direction::Up,
Direction::Down,
Direction::Left,
Direction::Right,
];
}
impl Neg for Direction {
type Output = Self;
fn neg(self) -> Self::Output {
use Direction::*;
match self {
Up => Down,
Down => Up,
Left => Right,
Right => Left,
}
}
}
#[derive(Debug, PartialEq, Eq)]
struct State {
heat: usize,
position: (usize, usize),
direction: Direction,
steps: usize,
}
impl PartialOrd for State {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for State {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other
.heat
.cmp(&self.heat)
.then_with(|| self.position.cmp(&other.position))
}
}
fn find_path(
grid: &[Vec<usize>],
start: (usize, usize),
target: (usize, usize),
min_steps: usize,
max_steps: usize,
) -> Option<usize> {
let valid_indexing = |y: usize, x: usize| grid.get(y).and_then(|row| row.get(x)).is_some();
let mut heap = BinaryHeap::new();
heap.push(State {
heat: 0,
position: (0, 0),
direction: Direction::Right,
steps: 0,
});
let mut heatmap = HashMap::new();
heatmap.insert((start, Direction::Down, 0), 0);
heatmap.insert((start, Direction::Up, 0), 0);
while let Some(State {
heat,
position,
direction,
steps,
}) = heap.pop()
{
if position == target {
return Some(heat);
}
if *heatmap
.get(&(position, direction, steps))
.unwrap_or(&usize::MAX)
< heat
{
continue;
}
let (y, x) = position;
for d in Direction::ALL.iter().filter(|&x| *x != -direction) {
if steps < min_steps && *d != direction {
continue;
}
let (ny, nx) = match d {
Direction::Up => (y.wrapping_sub(1), x),
Direction::Down => (y + 1, x),
Direction::Left => (y, x.wrapping_sub(1)),
Direction::Right => (y, x + 1),
};
if !valid_indexing(ny, nx) {
continue;
}
let new_steps = if *d == direction { steps + 1 } else { 1 };
if new_steps > max_steps {
continue;
}
let state = State {
heat: heat + grid[ny][nx],
position: (ny, nx),
direction: *d,
steps: new_steps,
};
if *heatmap
.get(&((ny, nx), *d, new_steps))
.unwrap_or(&usize::MAX)
> state.heat
{
heatmap.insert(((ny, nx), *d, new_steps), state.heat);
heap.push(state);
}
}
}
None
}
pub fn part_one(input: &str) -> Option<usize> {
let grid: Vec<Vec<_>> = input
.lines()
.map(|x| {
x.chars()
.filter_map(|x| x.to_digit(10).map(|x| x as usize))
.collect()
})
.collect();
let target = (grid.len() - 1, grid[0].len() - 1);
find_path(&grid, (0, 0), target, 0, 3)
}
pub fn part_two(input: &str) -> Option<usize> {
let grid: Vec<Vec<_>> = input
.lines()
.map(|x| {
x.chars()
.filter_map(|x| x.to_digit(10).map(|x| x as usize))
.collect()
})
.collect();
let target = (grid.len() - 1, grid[0].len() - 1);
find_path(&grid, (0, 0), target, 4, 10)
}
aoc::solution!(17);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 17)),
Some(102)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 17)),
Some(94)
);
}
}

126
src/bin/18.rs Normal file
View File

@@ -0,0 +1,126 @@
use std::str::FromStr;
#[derive(Debug, Clone, Copy)]
enum Direction {
Up,
Down,
Left,
Right,
}
#[derive(Debug)]
struct ParseDirectionError;
impl FromStr for Direction {
type Err = ParseDirectionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use Direction::*;
Ok(match s {
"U" | "3" => Up,
"D" | "1" => Down,
"L" | "2" => Left,
"R" | "0" => Right,
_ => return Err(ParseDirectionError),
})
}
}
impl From<Direction> for (isize, isize) {
fn from(value: Direction) -> Self {
use Direction::*;
match value {
Up => (-1, 0),
Down => (1, 0),
Left => (0, -1),
Right => (0, 1),
}
}
}
fn get_area(border: &[(isize, isize)], border_length: isize) -> isize {
// get area with shoelace formula (trapezoid variant)
// https://en.wikipedia.org/wiki/Shoelace_formula
let mut shoelace: isize = 0;
for n in 0..border.len() {
let (y1, x1) = border[n];
let (y2, x2) = border[(n + 1) % border.len()];
shoelace += (y1 + y2) * (x1 - x2);
}
let area = shoelace / 2;
// get interior by inverting pick's theorem formula
// https://en.wikipedia.org/wiki/Pick%27s_theorem
let interior = area + 1 - border_length / 2;
interior + border_length
}
fn get_border(instructions: &[(Direction, isize)]) -> (Vec<(isize, isize)>, isize) {
let mut border = Vec::new();
let mut border_length = 0;
let (mut sy, mut sx) = (0, 0);
border.push((sy, sx));
for (d, l) in instructions.iter().copied() {
let (dy, dx) = d.into();
(sy, sx) = (sy + l * dy, sx + l * dx);
border.push((sy, sx));
border_length += l;
}
border.pop();
(border, border_length)
}
pub fn part_one(input: &str) -> Option<isize> {
let instructions: Vec<(Direction, isize)> = input
.lines()
.map(|line| {
let [d, l, _] = line.splitn(3, ' ').collect::<Vec<_>>().try_into().unwrap();
(d.parse().unwrap(), l.parse::<isize>().unwrap())
})
.collect();
let (border, border_length) = get_border(&instructions);
Some(get_area(&border, border_length))
}
fn join_option_tuple<T, U>((a, b): (Option<T>, Option<U>)) -> Option<(T, U)> {
Some((a?, b?))
}
pub fn part_two(input: &str) -> Option<isize> {
let instructions: Vec<(Direction, isize)> = input
.lines()
.filter_map(|line| line.split_once(" (#"))
.filter_map(|(_, h)| h.strip_suffix(')'))
.map(|h| h.split_at(h.len() - 1))
.map(|(hex, dir)| (dir.parse().ok(), isize::from_str_radix(hex, 16).ok()))
.filter_map(join_option_tuple)
.collect();
let (border, border_length) = get_border(&instructions);
Some(get_area(&border, border_length))
}
aoc::solution!(18);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 18)),
Some(62)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 18)),
Some(952408144115)
);
}
}

387
src/bin/19.rs Normal file
View File

@@ -0,0 +1,387 @@
use std::{
collections::HashMap,
ops::{Index, IndexMut},
str::FromStr,
};
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
enum Gear {
X,
M,
A,
S,
}
#[derive(Debug)]
struct GearParseError;
impl TryFrom<&str> for Gear {
type Error = GearParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
use Gear::*;
Ok(match value {
"x" => X,
"m" => M,
"a" => A,
"s" => S,
_ => return Err(GearParseError),
})
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
enum Comparator {
Lt,
Gt,
}
#[derive(Debug)]
struct ComparatorParseError;
impl TryFrom<char> for Comparator {
type Error = ComparatorParseError;
fn try_from(value: char) -> Result<Self, Self::Error> {
use Comparator::*;
Ok(match value {
'<' => Lt,
'>' => Gt,
_ => return Err(ComparatorParseError),
})
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
enum Resolver {
Accepted,
Rejected,
Delegated(String),
}
impl From<&str> for Resolver {
fn from(value: &str) -> Self {
use Resolver::*;
match value {
"A" => Accepted,
"R" => Rejected,
x => Delegated(x.to_string()),
}
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
struct Workflow {
workflows: Vec<WorkflowInner>,
}
#[derive(Debug)]
struct ParseWorkflowError;
impl TryFrom<&str> for Workflow {
type Error = ParseWorkflowError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(Self {
workflows: value.split(',').filter_map(|x| x.try_into().ok()).collect(),
})
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
enum WorkflowInner {
Resolver(Resolver),
Rule((Gear, Comparator, usize, Resolver)),
}
#[derive(Debug)]
struct ParseWorkflowInnerError;
impl TryFrom<&str> for WorkflowInner {
type Error = ParseWorkflowInnerError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
if !value.contains(':') {
return Ok(WorkflowInner::Resolver(value.into()));
}
let (rest, resolver) = value.split_once(':').unwrap();
let resolver = resolver.into();
let (gear, number) = rest.split_once(|x| x == '<' || x == '>').unwrap();
let gear = gear.try_into().map_err(|_| ParseWorkflowInnerError)?;
let number = number.parse().map_err(|_| ParseWorkflowInnerError)?;
let comparator = if value.contains('<') { '<' } else { '>' };
let comparator = comparator.try_into().map_err(|_| ParseWorkflowInnerError)?;
Ok(WorkflowInner::Rule((gear, comparator, number, resolver)))
}
}
#[derive(Debug)]
struct Xmas {
x: usize,
m: usize,
a: usize,
s: usize,
}
#[derive(Debug)]
struct ParseXmasError;
impl FromStr for Xmas {
type Err = ParseXmasError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.strip_prefix('{').ok_or(ParseXmasError)?;
let s = s.strip_suffix('}').ok_or(ParseXmasError)?;
let xmas: Vec<_> = s
.split(',')
.filter_map(|x| x.split_once('='))
.map(|x| x.1)
.filter_map(|x| x.parse().ok())
.collect();
Ok(Self {
x: xmas[0],
m: xmas[1],
a: xmas[2],
s: xmas[3],
})
}
}
impl Index<Gear> for Xmas {
type Output = usize;
fn index(&self, index: Gear) -> &Self::Output {
use Gear::*;
match index {
X => &self.x,
M => &self.m,
A => &self.a,
S => &self.s,
}
}
}
impl IndexMut<Gear> for Xmas {
fn index_mut(&mut self, index: Gear) -> &mut Self::Output {
use Gear::*;
match index {
X => &mut self.x,
M => &mut self.m,
A => &mut self.a,
S => &mut self.s,
}
}
}
impl Xmas {
fn sum(&self) -> usize {
self.x + self.m + self.a + self.s
}
fn apply(&self, workflow: &Workflow) -> Resolver {
for w in workflow.workflows.iter() {
match w {
WorkflowInner::Resolver(x) => {
return x.clone();
}
WorkflowInner::Rule((g, c, n, r)) => {
let is_match = match c {
Comparator::Gt => self[*g] > *n,
Comparator::Lt => self[*g] < *n,
};
if is_match {
return r.clone();
}
}
}
}
unreachable!()
}
}
#[derive(Debug, Clone, Copy)]
struct XmasRange {
x: (usize, usize),
m: (usize, usize),
a: (usize, usize),
s: (usize, usize),
}
impl Index<Gear> for XmasRange {
type Output = (usize, usize);
fn index(&self, index: Gear) -> &Self::Output {
use Gear::*;
match index {
X => &self.x,
M => &self.m,
A => &self.a,
S => &self.s,
}
}
}
impl IndexMut<Gear> for XmasRange {
fn index_mut(&mut self, index: Gear) -> &mut Self::Output {
use Gear::*;
match index {
X => &mut self.x,
M => &mut self.m,
A => &mut self.a,
S => &mut self.s,
}
}
}
impl XmasRange {
fn size(&self) -> usize {
(self.x.1 - self.x.0 + 1)
* (self.m.1 - self.m.0 + 1)
* (self.a.1 - self.a.0 + 1)
* (self.s.1 - self.s.0 + 1)
}
fn divide(self, workflow: &Workflow) -> Vec<(Self, Resolver)> {
let mut processed = Vec::new();
let mut rest = vec![self];
for w in workflow.workflows.iter() {
let mut new_rest = Vec::new();
while let Some(xmas_range) = rest.pop() {
match w {
WorkflowInner::Resolver(r) => processed.push((xmas_range, r.clone())),
WorkflowInner::Rule((g, c, n, r)) => {
let compare = |x: usize, y: usize| match c {
Comparator::Gt => x > y,
Comparator::Lt => x < y,
};
let mut min_ok = usize::MAX;
let mut max_ok = usize::MIN;
let mut min_e = usize::MAX;
let mut max_e = usize::MIN;
for i in xmas_range[*g].0..=xmas_range[*g].1 {
if compare(i, *n) {
max_ok = if max_ok < i { i } else { max_ok };
min_ok = if min_ok > i { i } else { min_ok };
} else {
max_e = if max_e < i { i } else { max_e };
min_e = if min_e > i { i } else { min_e };
}
}
if min_e <= max_e {
let mut r = xmas_range;
r[*g] = (min_e, max_e);
new_rest.push(r);
}
if min_ok <= max_ok {
let mut p = xmas_range;
p[*g] = (min_ok, max_ok);
processed.push((p, r.clone()));
}
}
}
}
rest = new_rest;
}
processed
}
}
fn build_map(input: &str) -> HashMap<&str, Workflow> {
let mut map = HashMap::new();
for (s, w) in input
.split("\n\n")
.next()
.unwrap()
.lines()
.filter_map(|x| x.strip_suffix('}'))
.filter_map(|x| x.split_once('{'))
.filter_map(|(s, w)| w.try_into().ok().map(|x| (s, x)))
{
map.insert(s, w);
}
map
}
pub fn part_one(input: &str) -> Option<usize> {
let map = build_map(input);
let xmas_vec: Vec<Xmas> = input
.split("\n\n")
.nth(1)?
.lines()
.filter_map(|x| x.parse().ok())
.collect();
let mut total = 0;
'outer: for xmas in xmas_vec.into_iter() {
let mut workflow = &map["in"];
'apply: loop {
match xmas.apply(workflow) {
Resolver::Accepted => break 'apply,
Resolver::Rejected => continue 'outer,
Resolver::Delegated(x) => workflow = &map[x.as_str()],
}
}
total += xmas.sum();
}
Some(total)
}
pub fn part_two(input: &str) -> Option<usize> {
let map = build_map(input);
let mut finished = Vec::new();
let mut stack = vec![(
XmasRange {
x: (1, 4000),
m: (1, 4000),
a: (1, 4000),
s: (1, 4000),
},
Resolver::Delegated("in".to_string()),
)];
while let Some((xmas_range, resolver)) = stack.pop() {
match resolver {
Resolver::Accepted => finished.push(xmas_range),
Resolver::Rejected => {
continue;
}
Resolver::Delegated(x) => {
let workflow = &map[x.as_str()];
stack.append(&mut xmas_range.divide(workflow))
}
}
}
Some(finished.iter().map(XmasRange::size).sum())
}
aoc::solution!(19);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(
part_one(&aoc::template::read_file("examples", 19)),
Some(19114)
);
}
#[test]
fn test_part_two() {
assert_eq!(
part_two(&aoc::template::read_file("examples", 19)),
Some(167409079868000)
);
}
}

187
src/bin/20.rs Normal file
View File

@@ -0,0 +1,187 @@
use std::{
collections::{HashMap, VecDeque},
mem::swap,
str::FromStr,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Module {
Broadcaster,
FlipFlop(bool),
Conjuction,
}
#[derive(Debug)]
struct ParseModuleError;
impl FromStr for Module {
type Err = ParseModuleError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"%" => Module::FlipFlop(false),
"&" => Module::Conjuction,
_ => Module::Broadcaster,
})
}
}
#[derive(Debug)]
struct Node {
index: usize,
module: Module,
inputs: Vec<usize>,
outputs: Vec<usize>,
}
fn parse_input(input: &str) -> Vec<Node> {
let mut nodes = Vec::new();
let mut mapper: HashMap<String, usize> = HashMap::new();
for line in input.lines() {
let (from, _) = line.split_once(" -> ").unwrap();
let (module, mut name) = from.split_at(1);
let module: Module = module.parse().unwrap();
if module == Module::Broadcaster {
name = from;
}
*mapper.entry(name.to_string()).or_default() = nodes.len();
let l = Node {
module,
index: nodes.len(),
inputs: Vec::new(),
outputs: Vec::new(),
};
nodes.push(l);
}
for line in input.lines() {
let (from, to) = line.split_once(" -> ").unwrap();
let (module, mut name) = from.split_at(1);
let module: Module = module.parse().unwrap();
if module == Module::Broadcaster {
name = from;
}
let index = mapper[name];
for destination in to.split(", ") {
let to_index = mapper[destination];
nodes[index].outputs.push(to_index);
nodes[to_index].inputs.push(index);
}
}
nodes
}
pub fn part_one(input: &str) -> Option<usize> {
let mut nodes = parse_input(input);
let mut graph = vec![vec![None; nodes.len()]; nodes.len()];
let mut highs = 0;
let mut lows = 0;
let broadcaster = nodes
.iter()
.find(|x| x.module == Module::Broadcaster)?
.index;
for _ in 0..1000 {
let mut stack = VecDeque::from([broadcaster]);
while !stack.is_empty() {
let mut new_stack = VecDeque::new();
let mut new_graph = graph.clone();
while let Some(index) = stack.pop_front() {
let node = &nodes[index];
let mut new_module = None;
match node.module {
Module::Broadcaster => {
for dest_index in node.outputs.iter() {
new_graph[index][*dest_index] = Some(false);
new_stack.push_back(*dest_index);
lows += 1;
}
}
Module::FlipFlop(high) => {
let mut swapper = None;
for in_index in nodes[index].inputs.iter() {
let value = &mut graph[*in_index][index];
if value.is_some() {
debug_assert!(swapper.is_none());
swap(&mut swapper, value);
}
}
if !swapper.unwrap() {
let signal = !high;
for dest_index in nodes[index].outputs.iter() {
new_graph[index][*dest_index] = Some(signal);
new_stack.push_back(*dest_index);
if signal {
highs += 1;
} else {
lows += 1;
}
}
new_module = Some(Module::FlipFlop(signal));
}
}
Module::Conjuction => {
let mut all = true;
for in_index in nodes[index].inputs.iter() {
let value = graph[*in_index][index];
all &= value.unwrap_or(false);
}
for dest_index in nodes[index].outputs.iter() {
new_graph[index][*dest_index] = Some(!all);
new_stack.push_back(*dest_index);
if !all {
highs += 1;
} else {
lows += 1;
}
}
}
}
let node = &mut nodes[index];
if let Some(module) = new_module {
node.module = module;
}
}
stack = new_stack;
graph = new_graph;
}
}
Some(highs * lows)
}
pub fn part_two(input: &str) -> Option<u32> {
None
}
aoc::solution!(20);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_one() {
assert_eq!(part_one(&aoc::template::read_file("examples", 20)), None);
}
#[test]
fn test_part_two() {
assert_eq!(part_two(&aoc::template::read_file("examples", 20)), None);
}
}

View File

@@ -2,3 +2,27 @@
pub mod parsers;
pub mod template;
use std::mem::swap;
pub fn lcm(first: usize, second: usize) -> usize {
first * second / gcd(first, second)
}
pub fn gcd(first: usize, second: usize) -> usize {
let mut max = first;
let mut min = second;
if min > max {
swap(&mut min, &mut max)
}
loop {
let res = max % min;
if res == 0 {
return min;
}
max = min;
min = res;
}
}

View File

@@ -54,3 +54,14 @@ pub fn read_file(folder: &str, day: u8) -> String {
let f = fs::read_to_string(filepath);
f.expect("could not open input file").trim().to_string()
}
#[must_use]
pub fn read_file_part(folder: &str, day: u8, part: u8) -> String {
let cwd = env::current_dir().unwrap();
let filepath = cwd
.join("data")
.join(folder)
.join(format!("{day:02}-{part}.txt"));
let f = fs::read_to_string(filepath);
f.expect("could not open input file").trim().to_string()
}