mirror of
https://github.com/bertptrs/adventofcode.git
synced 2025-12-25 21:00:31 +01:00
Compare commits
27 Commits
2021-day23
...
2022/day-2
| Author | SHA1 | Date | |
|---|---|---|---|
| 561fd2f07c | |||
| 256d351f8e | |||
| 48594a75e6 | |||
| 85a51b13c1 | |||
| 2ae2d6baa8 | |||
| 4a55e53182 | |||
| af0897300d | |||
| cabae7b1fd | |||
| 0635141ac6 | |||
| d9d5947c3b | |||
| cc8b4ce353 | |||
| 0b91da04b3 | |||
| dba146b299 | |||
| 33111615be | |||
| 04e8a41d98 | |||
| 36d76018ba | |||
| 4172fd0463 | |||
| ad0b4a4659 | |||
| 2dab7342f8 | |||
| edd14a0e3d | |||
| 4d7188e1ff | |||
| 255edaca79 | |||
| 8ea716cba8 | |||
| 601de2c565 | |||
| 894524bc81 | |||
| f19bf28f34 | |||
| de3a24a87c |
@@ -1,7 +1,7 @@
|
|||||||
on:
|
on:
|
||||||
- push
|
- push
|
||||||
|
|
||||||
name: Advent of Code 2021
|
name: Advent of Code 2022
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci:
|
ci:
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
continue-on-error: ${{ matrix.experimental }}
|
continue-on-error: ${{ matrix.experimental }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install toolchain
|
- name: Install toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
@@ -30,17 +30,23 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Set up caching
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: >
|
||||||
|
2022 -> target
|
||||||
|
|
||||||
- name: Build binaries
|
- name: Build binaries
|
||||||
working-directory: 2021
|
working-directory: 2022
|
||||||
run: >
|
run: >
|
||||||
cargo build --all-targets
|
cargo build --all-targets
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
working-directory: 2021
|
working-directory: 2022
|
||||||
run: >
|
run: >
|
||||||
cargo test
|
cargo test
|
||||||
|
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
working-directory: 2021
|
working-directory: 2022
|
||||||
run: >
|
run: >
|
||||||
cargo clippy -- --deny warnings
|
cargo clippy -- --deny warnings
|
||||||
@@ -4,4 +4,4 @@ version = "0.1.0"
|
|||||||
authors = ["Bert Peters <bert.ljpeters@gmail.com>"]
|
authors = ["Bert Peters <bert.ljpeters@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
regex = "0.1"
|
regex = "1"
|
||||||
|
|||||||
@@ -73,10 +73,10 @@ fn main() {
|
|||||||
let door_label = line.unwrap();
|
let door_label = line.unwrap();
|
||||||
let caps = room_pattern.captures(&door_label).unwrap();
|
let caps = room_pattern.captures(&door_label).unwrap();
|
||||||
|
|
||||||
let name = caps.at(1).unwrap();
|
let name = caps.get(1).unwrap().as_str();
|
||||||
let checksum = caps.at(4).unwrap();
|
let checksum = caps.get(4).unwrap().as_str();
|
||||||
if is_valid(name, checksum) {
|
if is_valid(name, checksum) {
|
||||||
let sector_id = caps.at(3).unwrap().parse().unwrap();
|
let sector_id = caps.get(3).unwrap().as_str().parse().unwrap();
|
||||||
cur_sum += sector_id;
|
cur_sum += sector_id;
|
||||||
|
|
||||||
let decoded: String = name.chars()
|
let decoded: String = name.chars()
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ version = "0.1.0"
|
|||||||
authors = ["Bert Peters <bert.ljpeters@gmail.com>"]
|
authors = ["Bert Peters <bert.ljpeters@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
regex = "^0.1"
|
regex = "1"
|
||||||
lazy_static = "^0.2"
|
lazy_static = "1"
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ version = "0.1.0"
|
|||||||
authors = ["Bert Peters <bert.ljpeters@gmail.com>"]
|
authors = ["Bert Peters <bert.ljpeters@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
regex="^0.1"
|
regex="1"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "3.0.0-rc.0", features = ["derive"] }
|
clap = { version = "3", features = ["derive"] }
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
nom = "7"
|
nom = "7"
|
||||||
|
|
||||||
|
|||||||
@@ -20,3 +20,15 @@ OPTIONS:
|
|||||||
-i, --input <INPUT> Read input from the given file instead of stdin
|
-i, --input <INPUT> Read input from the given file instead of stdin
|
||||||
-t, --time Print time taken
|
-t, --time Print time taken
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## That goal was achieved
|
||||||
|
|
||||||
|
Runtime benchmarked with [Criterion], reading input directly from memory to avoid disk IO
|
||||||
|
inconsistencies.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
[Criterion]: https://github.com/bheisler/criterion.rs
|
||||||
|
|||||||
97
2021/create_timing_plots.py
Executable file
97
2021/create_timing_plots.py
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
def read_timings() -> Dict[int, Dict]:
|
||||||
|
timings = {}
|
||||||
|
|
||||||
|
for day in Path('target/criterion/part1').iterdir():
|
||||||
|
with open(day / 'new' / 'estimates.json', mode='rb') as f:
|
||||||
|
timings[int(day.parts[-1])] = {
|
||||||
|
1: json.load(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
for day in Path('target/criterion/part2').iterdir():
|
||||||
|
with open(day / 'new' / 'estimates.json', mode='rb') as f:
|
||||||
|
timings[int(day.parts[-1])][2] = json.load(f)
|
||||||
|
|
||||||
|
return timings
|
||||||
|
|
||||||
|
|
||||||
|
def plot_cumulative_time(timings: Dict[int, Dict]):
|
||||||
|
plt.clf()
|
||||||
|
|
||||||
|
times = [0]
|
||||||
|
|
||||||
|
for day in range(min(timings.keys()), max(timings.keys()) + 1):
|
||||||
|
times.append(timings[day][1]['mean']['point_estimate'])
|
||||||
|
if day < 25:
|
||||||
|
times.append(timings[day][2]['mean']['point_estimate'])
|
||||||
|
else:
|
||||||
|
times.append(0)
|
||||||
|
|
||||||
|
cumulative = np.cumsum(times)
|
||||||
|
# Convert from nanoseconds to seconds
|
||||||
|
cumulative /= 1e9
|
||||||
|
|
||||||
|
x = np.arange(0.0, 25.5, 0.5)
|
||||||
|
|
||||||
|
plt.plot(x, cumulative, label="Cumulative time", drawstyle='steps-post')
|
||||||
|
plt.plot([0, 25], [0, 0.5], label="Target time")
|
||||||
|
plt.ylabel('Cumulative time (s)')
|
||||||
|
plt.xlabel('Days completed')
|
||||||
|
|
||||||
|
plt.legend()
|
||||||
|
plt.tight_layout()
|
||||||
|
|
||||||
|
plt.xlim(0, 25)
|
||||||
|
plt.ylim(0, 0.5)
|
||||||
|
|
||||||
|
plt.savefig('cumulative-time.svg')
|
||||||
|
|
||||||
|
|
||||||
|
def plot_individual_times(timings: Dict[int, Dict]):
|
||||||
|
plt.clf()
|
||||||
|
|
||||||
|
def plot(parts, **kwargs):
|
||||||
|
x = np.arange(1, len(parts) + 1)
|
||||||
|
|
||||||
|
values = np.array(list(part['mean']['point_estimate'] for part in parts))
|
||||||
|
upper = np.array(list(part['mean']['confidence_interval']['upper_bound'] for part in parts))
|
||||||
|
lower = np.array(list(part['mean']['confidence_interval']['lower_bound'] for part in parts))
|
||||||
|
|
||||||
|
# Convert from ns to s
|
||||||
|
yerr = np.array([upper - values, lower - values]) / 1e9
|
||||||
|
values = values / 1e9
|
||||||
|
|
||||||
|
plt.bar(x, values, yerr=yerr, align='edge', log=True, **kwargs)
|
||||||
|
pass
|
||||||
|
|
||||||
|
plot(list(timings[day][1] for day in range(1, 26)), label="Part 1", width=-0.4)
|
||||||
|
plot(list(timings[day][2] for day in range(1, 25)), label="Part 2", width=0.4)
|
||||||
|
|
||||||
|
plt.ylabel('Runtime (s)')
|
||||||
|
plt.xlabel('Day')
|
||||||
|
|
||||||
|
plt.xlim(0, 26)
|
||||||
|
plt.xticks(np.arange(1, 26))
|
||||||
|
|
||||||
|
plt.legend()
|
||||||
|
plt.tight_layout()
|
||||||
|
|
||||||
|
plt.savefig('individual-time.svg')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
timings = read_timings()
|
||||||
|
plot_cumulative_time(timings)
|
||||||
|
plot_individual_times(timings)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
1
2021/cumulative-time.svg
Normal file
1
2021/cumulative-time.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 16 KiB |
1
2021/individual-time.svg
Normal file
1
2021/individual-time.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 32 KiB |
@@ -90,7 +90,65 @@ where
|
|||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
input.read_to_end(&mut buffer).unwrap();
|
input.read_to_end(&mut buffer).unwrap();
|
||||||
|
|
||||||
let (_, output) = parser(&buffer).finish().unwrap();
|
match parser(&buffer).finish() {
|
||||||
|
Ok((_, output)) => output,
|
||||||
output
|
Err(err) => {
|
||||||
|
panic!(
|
||||||
|
"Failed to parse input with error {:?} at \"{}\"",
|
||||||
|
err.code,
|
||||||
|
String::from_utf8_lossy(err.input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct BitSet {
|
||||||
|
buffer: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitSet {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_capacity(capacity: usize) -> Self {
|
||||||
|
let buffer = vec![0; capacity / 32];
|
||||||
|
|
||||||
|
Self { buffer }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_value(value: usize) -> (usize, u32) {
|
||||||
|
let chunk = value / 32;
|
||||||
|
let bit = 1 << (31 - (value % 32));
|
||||||
|
|
||||||
|
(chunk, bit)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, value: usize) -> bool {
|
||||||
|
let (chunk, bit) = Self::convert_value(value);
|
||||||
|
|
||||||
|
if self.buffer.len() <= chunk + 1 {
|
||||||
|
self.buffer.resize(chunk + 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let not_present = self.buffer[chunk] & bit;
|
||||||
|
|
||||||
|
self.buffer[chunk] |= bit;
|
||||||
|
|
||||||
|
not_present == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.buffer.iter().map(|c| c.count_ones() as usize).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, value: usize) -> bool {
|
||||||
|
let (chunk, bit) = Self::convert_value(value);
|
||||||
|
|
||||||
|
self.buffer
|
||||||
|
.get(chunk)
|
||||||
|
.map(|&c| c & bit != 0)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +1,95 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::iter::repeat;
|
use std::iter::repeat;
|
||||||
|
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::sequence::tuple;
|
use nom::character::complete::newline;
|
||||||
use nom::Finish;
|
use nom::combinator::map;
|
||||||
|
use nom::multi::separated_list1;
|
||||||
|
use nom::sequence::separated_pair;
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
|
|
||||||
use crate::common::ordered;
|
use crate::common::ordered;
|
||||||
use crate::common::LineIter;
|
use crate::common::read_input;
|
||||||
|
use crate::common::BitSet;
|
||||||
|
|
||||||
type Coord = (u16, u16);
|
type Coord = (u16, u16);
|
||||||
|
|
||||||
fn coordinates(input: &str) -> IResult<&str, Coord> {
|
fn coordinates(input: &[u8]) -> IResult<&[u8], Coord> {
|
||||||
use nom::character::complete;
|
use nom::character::complete::char;
|
||||||
|
use nom::character::complete::u16;
|
||||||
|
|
||||||
let (input, (x, _, y)) = tuple((complete::u16, complete::char(','), complete::u16))(input)?;
|
separated_pair(u16, char(','), u16)(input)
|
||||||
|
|
||||||
Ok((input, (x, y)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_definition(input: &str) -> IResult<&str, (Coord, Coord)> {
|
fn parse_input(input: &[u8]) -> IResult<&[u8], Vec<(Coord, Coord)>> {
|
||||||
let (input, (begin, _, end)) = tuple((coordinates, tag(" -> "), coordinates))(input)?;
|
let read_line = map(
|
||||||
|
separated_pair(coordinates, tag(" -> "), coordinates),
|
||||||
|
|(begin, end)| ordered(begin, end),
|
||||||
|
);
|
||||||
|
|
||||||
// Sorting the coordinates saves trouble later
|
separated_list1(newline, read_line)(input)
|
||||||
Ok((input, ordered(begin, end)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stripe(
|
fn stripe(
|
||||||
map: &mut HashMap<Coord, u16>,
|
once: &mut BitSet,
|
||||||
|
twice: &mut BitSet,
|
||||||
|
width: usize,
|
||||||
xs: impl Iterator<Item = u16>,
|
xs: impl Iterator<Item = u16>,
|
||||||
ys: impl Iterator<Item = u16>,
|
ys: impl Iterator<Item = u16>,
|
||||||
) {
|
) {
|
||||||
for (x, y) in xs.zip(ys) {
|
for (x, y) in xs.zip(ys) {
|
||||||
*map.entry((x, y)).or_default() += 1;
|
let index = x as usize + y as usize * width;
|
||||||
|
if !once.insert(index) {
|
||||||
|
twice.insert(index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn part_common(input: &mut dyn Read, diagonals: bool) -> String {
|
fn part_common(input: &mut dyn Read, diagonals: bool) -> String {
|
||||||
let mut reader = LineIter::new(input);
|
let lines = read_input(input, parse_input);
|
||||||
let mut map = HashMap::new();
|
|
||||||
|
|
||||||
while let Some(line) = reader.next() {
|
let width = lines
|
||||||
let (begin, end) = line_definition(line).finish().unwrap().1;
|
.iter()
|
||||||
|
.map(|&(_, (x, _))| x as usize + 1)
|
||||||
|
.max()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut once_map = BitSet::new();
|
||||||
|
let mut twice_map = BitSet::new();
|
||||||
|
|
||||||
|
for (begin, end) in lines {
|
||||||
if begin.0 == end.0 {
|
if begin.0 == end.0 {
|
||||||
let y_range = begin.1..=end.1;
|
let y_range = begin.1..=end.1;
|
||||||
stripe(&mut map, repeat(begin.0), y_range);
|
stripe(
|
||||||
|
&mut once_map,
|
||||||
|
&mut twice_map,
|
||||||
|
width,
|
||||||
|
repeat(begin.0),
|
||||||
|
y_range,
|
||||||
|
);
|
||||||
} else if begin.1 == end.1 {
|
} else if begin.1 == end.1 {
|
||||||
let x_range = begin.0..=end.0;
|
let x_range = begin.0..=end.0;
|
||||||
stripe(&mut map, x_range, repeat(begin.1));
|
stripe(
|
||||||
|
&mut once_map,
|
||||||
|
&mut twice_map,
|
||||||
|
width,
|
||||||
|
x_range,
|
||||||
|
repeat(begin.1),
|
||||||
|
);
|
||||||
} else if diagonals {
|
} else if diagonals {
|
||||||
let x_range = begin.0..=end.0;
|
let x_range = begin.0..=end.0;
|
||||||
let y_range = (begin.1.min(end.1))..=(begin.1.max(end.1));
|
let y_range = (begin.1.min(end.1))..=(begin.1.max(end.1));
|
||||||
|
|
||||||
if begin.1 > end.1 {
|
if begin.1 > end.1 {
|
||||||
// For a downward slope we need to reverse Y
|
// For a downward slope we need to reverse Y
|
||||||
stripe(&mut map, x_range, y_range.rev());
|
stripe(&mut once_map, &mut twice_map, width, x_range, y_range.rev());
|
||||||
} else {
|
} else {
|
||||||
stripe(&mut map, x_range, y_range);
|
stripe(&mut once_map, &mut twice_map, width, x_range, y_range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
map.values().filter(|&&v| v > 1).count().to_string()
|
twice_map.len().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn part1(input: &mut dyn Read) -> String {
|
pub fn part1(input: &mut dyn Read) -> String {
|
||||||
@@ -82,11 +108,6 @@ mod tests {
|
|||||||
|
|
||||||
const SAMPLE: &[u8] = include_bytes!("samples/05.txt");
|
const SAMPLE: &[u8] = include_bytes!("samples/05.txt");
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parser() {
|
|
||||||
assert_eq!(line_definition("6,4 -> 2,0"), Ok(("", ((2, 0), (6, 4)))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sample_part1() {
|
fn sample_part1() {
|
||||||
test_implementation(part1, SAMPLE, 5)
|
test_implementation(part1, SAMPLE, 5)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ fn parse_fold(input: &[u8]) -> IResult<&[u8], Fold> {
|
|||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_fold(dots: &mut Vec<Coords>, fold: Fold) {
|
fn apply_fold(dots: &mut [Coords], fold: Fold) {
|
||||||
match fold {
|
match fold {
|
||||||
Fold::X(coord) => dots.iter_mut().for_each(|(x, _)| {
|
Fold::X(coord) => dots.iter_mut().for_each(|(x, _)| {
|
||||||
if *x >= coord {
|
if *x >= coord {
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ mod tests {
|
|||||||
fn sample_part1() {
|
fn sample_part1() {
|
||||||
let answers = [16, 12, 23, 31];
|
let answers = [16, 12, 23, 31];
|
||||||
|
|
||||||
for (&sample, answer) in SAMPLE.into_iter().zip(answers) {
|
for (&sample, answer) in SAMPLE.iter().zip(answers) {
|
||||||
test_implementation(part1, sample, answer);
|
test_implementation(part1, sample, answer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
use std::ops::Deref;
|
||||||
use std::ops::Sub;
|
use std::ops::Sub;
|
||||||
|
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
@@ -23,6 +25,10 @@ impl Point3 {
|
|||||||
pub fn manhattan(&self) -> i32 {
|
pub fn manhattan(&self) -> i32 {
|
||||||
self.0.into_iter().map(i32::abs).sum()
|
self.0.into_iter().map(i32::abs).sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn euler_square(&self) -> i32 {
|
||||||
|
self.0.into_iter().map(|c| c * c).sum()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sub for Point3 {
|
impl Sub for Point3 {
|
||||||
@@ -49,6 +55,44 @@ impl Add for Point3 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Scanner {
|
||||||
|
visible: Vec<Point3>,
|
||||||
|
distances: HashMap<i32, (Point3, Point3)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scanner {
|
||||||
|
pub fn new(visible: Vec<Point3>) -> Self {
|
||||||
|
let distances = visible
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(skip, &a)| {
|
||||||
|
visible[(skip + 1)..]
|
||||||
|
.iter()
|
||||||
|
.map(move |&b| ((a - b).euler_square(), (a, b)))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self { visible, distances }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn can_overlap(&self, other: &Self) -> bool {
|
||||||
|
other
|
||||||
|
.distances
|
||||||
|
.keys()
|
||||||
|
.filter(|&k| self.distances.contains_key(k))
|
||||||
|
.count()
|
||||||
|
>= 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Scanner {
|
||||||
|
type Target = [Point3];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Rotations<'a> {
|
struct Rotations<'a> {
|
||||||
points: &'a [Point3],
|
points: &'a [Point3],
|
||||||
axes: [usize; 3],
|
axes: [usize; 3],
|
||||||
@@ -119,32 +163,56 @@ fn parse_point(input: &[u8]) -> IResult<&[u8], Point3> {
|
|||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_input(input: &[u8]) -> IResult<&[u8], Vec<Vec<Point3>>> {
|
fn parse_input(input: &[u8]) -> IResult<&[u8], Vec<Scanner>> {
|
||||||
use nom::character::complete::i32;
|
use nom::character::complete::i32;
|
||||||
let parse_header = delimited(tag("--- scanner "), i32, tag(" ---\n"));
|
let parse_header = delimited(tag("--- scanner "), i32, tag(" ---\n"));
|
||||||
|
|
||||||
let parse_scanner = preceded(parse_header, many1(terminated(parse_point, newline)));
|
let parse_scanner = map(
|
||||||
|
preceded(parse_header, many1(terminated(parse_point, newline))),
|
||||||
|
Scanner::new,
|
||||||
|
);
|
||||||
separated_list1(newline, parse_scanner)(input)
|
separated_list1(newline, parse_scanner)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_overlap(
|
fn find_pivot(group: &Scanner, related: &Scanner) -> Option<Point3> {
|
||||||
correct: &[(Point3, HashSet<Point3>)],
|
let mut counter = HashMap::new();
|
||||||
candidate: &[Point3],
|
|
||||||
) -> Option<(Point3, Vec<Point3>)> {
|
for (distance, &(a, b)) in &group.distances {
|
||||||
let mut relative = HashSet::new();
|
if related.distances.contains_key(distance) {
|
||||||
|
*counter.entry(a).or_insert(0) += 1;
|
||||||
|
*counter.entry(b).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
counter
|
||||||
|
.into_iter()
|
||||||
|
.max_by_key(|(_, count)| *count)
|
||||||
|
.map(|t| t.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_overlap(matched: &Scanner, candidate: &Scanner) -> Option<(Point3, Scanner)> {
|
||||||
|
if !matched.can_overlap(candidate) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let matched_pivot = find_pivot(matched, candidate)?;
|
||||||
|
|
||||||
|
let correct: HashSet<_> = matched.iter().map(|&base| base - matched_pivot).collect();
|
||||||
|
|
||||||
for rot in Rotations::new(candidate) {
|
for rot in Rotations::new(candidate) {
|
||||||
for &start in &rot {
|
for &start in &rot {
|
||||||
relative.clear();
|
let translated_iter = rot.iter().map(|&other| other - start);
|
||||||
|
|
||||||
relative.extend(rot.iter().map(|&other| other - start));
|
if translated_iter
|
||||||
|
.clone()
|
||||||
if let Some((base, _)) = correct.iter().find(|(_, correct_relative)| {
|
.filter(|p| correct.contains(p))
|
||||||
correct_relative.intersection(&relative).count() >= 12
|
.count()
|
||||||
}) {
|
>= 12
|
||||||
|
{
|
||||||
// Found a solution, build the correct output
|
// Found a solution, build the correct output
|
||||||
let translated = relative.drain().map(|point| point + *base).collect();
|
let translated = translated_iter.map(|point| point + matched_pivot).collect();
|
||||||
|
|
||||||
return Some((start - *base, translated));
|
return Some((start - matched_pivot, Scanner::new(translated)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,33 +225,30 @@ fn parts_common(input: &mut dyn Read) -> (HashSet<Point3>, Vec<Point3>) {
|
|||||||
|
|
||||||
let mut points: HashSet<_> = scanners[0].iter().copied().collect();
|
let mut points: HashSet<_> = scanners[0].iter().copied().collect();
|
||||||
|
|
||||||
let mut todo = vec![std::mem::take(&mut scanners[0])];
|
let mut todo = vec![scanners.remove(0)];
|
||||||
let mut scanners_found = vec![Point3::default()];
|
let mut scanners_found = vec![Point3::default()];
|
||||||
|
|
||||||
while let Some(matched) = todo.pop() {
|
while let Some(matched) = todo.pop() {
|
||||||
if scanners.iter().all(Vec::is_empty) {
|
if scanners.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let relative: Vec<(Point3, HashSet<Point3>)> = matched
|
let mut i = 0;
|
||||||
.iter()
|
|
||||||
.map(|&base| (base, matched.iter().map(|&other| (other - base)).collect()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for candidate in &mut scanners {
|
while i < scanners.len() {
|
||||||
if candidate.is_empty() {
|
if let Some((scanner, result)) = try_overlap(&matched, &scanners[i]) {
|
||||||
continue;
|
scanners.remove(i);
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((scanner, result)) = try_overlap(&relative, candidate) {
|
|
||||||
scanners_found.push(scanner);
|
scanners_found.push(scanner);
|
||||||
points.extend(result.iter().copied());
|
points.extend(result.iter().copied());
|
||||||
todo.push(result);
|
todo.push(result);
|
||||||
candidate.clear();
|
} else {
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert!(scanners.is_empty());
|
||||||
|
|
||||||
(points, scanners_found)
|
(points, scanners_found)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,124 @@
|
|||||||
use std::collections::HashSet;
|
use std::fmt::Display;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::mem::swap;
|
use std::ops::Index;
|
||||||
|
|
||||||
|
use crate::common::BitSet;
|
||||||
|
|
||||||
type Translation = [bool; 512];
|
type Translation = [bool; 512];
|
||||||
type Point = (i32, i32);
|
|
||||||
type Field = HashSet<Point>;
|
struct Field {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
infinity: bool,
|
||||||
|
finite: BitSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Field {
|
||||||
|
pub fn from_input<'a>(input: impl Iterator<Item = &'a [u8]>) -> Self {
|
||||||
|
let mut input = input.peekable();
|
||||||
|
|
||||||
|
let width = input.peek().unwrap().len();
|
||||||
|
|
||||||
|
let mut finite = BitSet::new();
|
||||||
|
|
||||||
|
let len = input
|
||||||
|
.flatten()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, &c)| {
|
||||||
|
if c == b'#' {
|
||||||
|
finite.insert(index);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
debug_assert_eq!(len % width, 0);
|
||||||
|
let height = len / width;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
finite,
|
||||||
|
infinity: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, translation: &[bool; 512]) {
|
||||||
|
const INDEX_MASK: usize = (1 << 9) - 1;
|
||||||
|
|
||||||
|
let new_width = self.width + 2;
|
||||||
|
let new_height = self.height + 2;
|
||||||
|
|
||||||
|
let mut new_finite = BitSet::with_capacity(new_width * new_height);
|
||||||
|
|
||||||
|
for y in 0..new_height {
|
||||||
|
let mut mask = if self.infinity { INDEX_MASK } else { 0 };
|
||||||
|
|
||||||
|
for x in 0..new_width {
|
||||||
|
const COLUMN_MASK: usize = 0b001001001;
|
||||||
|
let mut submask = if self.infinity { COLUMN_MASK } else { 0 };
|
||||||
|
|
||||||
|
for y in y.saturating_sub(2)..=y {
|
||||||
|
submask = (submask << 3) | (self[(x, y)] as usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
mask <<= 1;
|
||||||
|
mask &= !COLUMN_MASK;
|
||||||
|
mask |= submask;
|
||||||
|
mask &= INDEX_MASK;
|
||||||
|
|
||||||
|
if translation[mask] {
|
||||||
|
let index = x + y * new_width;
|
||||||
|
new_finite.insert(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.width += 2;
|
||||||
|
self.height += 2;
|
||||||
|
self.finite = new_finite;
|
||||||
|
self.infinity = translation[if self.infinity { INDEX_MASK } else { 0 }];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
assert!(!self.infinity);
|
||||||
|
self.finite.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<(usize, usize)> for Field {
|
||||||
|
type Output = bool;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn index(&self, (x, y): (usize, usize)) -> &Self::Output {
|
||||||
|
if x >= self.width || y >= self.height {
|
||||||
|
return &self.infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = x + y * self.width;
|
||||||
|
|
||||||
|
if self.finite.contains(index) {
|
||||||
|
&true
|
||||||
|
} else {
|
||||||
|
&false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Field {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for y in 0..self.height {
|
||||||
|
for x in 0..self.width {
|
||||||
|
if self[(x, y)] {
|
||||||
|
write!(f, "#")?
|
||||||
|
} else {
|
||||||
|
write!(f, ".")?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn read_input(input: &mut dyn Read) -> (Translation, Field) {
|
fn read_input(input: &mut dyn Read) -> (Translation, Field) {
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
@@ -19,67 +133,16 @@ fn read_input(input: &mut dyn Read) -> (Translation, Field) {
|
|||||||
.zip(it.next().unwrap())
|
.zip(it.next().unwrap())
|
||||||
.for_each(|(t, &c)| *t = c == b'#');
|
.for_each(|(t, &c)| *t = c == b'#');
|
||||||
|
|
||||||
let mut field = Field::default();
|
let field = Field::from_input(it.skip(1));
|
||||||
|
|
||||||
for (y, line) in it.skip(1).enumerate() {
|
|
||||||
for (x, _) in line.iter().enumerate().filter(|(_, &c)| c == b'#') {
|
|
||||||
field.insert((x as i32, y as i32));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(translation, field)
|
(translation, field)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_dimensions(field: &Field) -> ((i32, i32), (i32, i32)) {
|
|
||||||
field
|
|
||||||
.iter()
|
|
||||||
.fold(((0, 0), (0, 0)), |((xmin, xmax), (ymin, ymax)), &(x, y)| {
|
|
||||||
((xmin.min(x), xmax.max(x)), (ymin.min(y), ymax.max(y)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advance(translation: &Translation, field: &Field, new_field: &mut Field, infinity: &mut bool) {
|
|
||||||
const INDEX_MASK: usize = (1 << 9) - 1;
|
|
||||||
new_field.clear();
|
|
||||||
|
|
||||||
let ((xmin, xmax), (ymin, ymax)) = find_dimensions(field);
|
|
||||||
|
|
||||||
for x in (xmin - 1)..=(xmax + 1) {
|
|
||||||
let mut index = if *infinity { INDEX_MASK } else { 0 };
|
|
||||||
|
|
||||||
for y in (ymin - 1)..=(ymax + 1) {
|
|
||||||
for dx in -1..=1 {
|
|
||||||
index <<= 1;
|
|
||||||
|
|
||||||
let nx = x + dx;
|
|
||||||
let ny = y + 1;
|
|
||||||
|
|
||||||
if nx < xmin || nx > xmax || ny < ymin || ny > ymax {
|
|
||||||
index |= *infinity as usize;
|
|
||||||
} else if field.contains(&(nx, ny)) {
|
|
||||||
index |= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
index &= INDEX_MASK;
|
|
||||||
|
|
||||||
if translation[index] {
|
|
||||||
new_field.insert((x, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*infinity = translation[if *infinity { 511 } else { 0 }]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parts_common(input: &mut dyn Read, count: usize) -> String {
|
fn parts_common(input: &mut dyn Read, count: usize) -> String {
|
||||||
let (translation, mut field) = read_input(input);
|
let (translation, mut field) = read_input(input);
|
||||||
let mut new_field = Field::new();
|
|
||||||
let mut infinity = false;
|
|
||||||
|
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
advance(&translation, &field, &mut new_field, &mut infinity);
|
field.advance(&translation);
|
||||||
swap(&mut field, &mut new_field);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
field.len().to_string()
|
field.len().to_string()
|
||||||
|
|||||||
@@ -1,9 +1,451 @@
|
|||||||
|
use std::cmp::Reverse;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
use std::collections::BinaryHeap;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::mem::swap;
|
||||||
|
|
||||||
pub fn part1(_input: &mut dyn Read) -> String {
|
use crate::common::LineIter;
|
||||||
todo!()
|
|
||||||
|
type Item<const S: usize> = (u32, State<S>);
|
||||||
|
type Todo<const S: usize> = BinaryHeap<Reverse<Item<S>>>;
|
||||||
|
type Visited<const S: usize> = HashMap<State<S>, u32>;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
|
||||||
|
enum Pod {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
D,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn part2(_input: &mut dyn Read) -> String {
|
impl Pod {
|
||||||
todo!()
|
pub fn cost(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Pod::A => 1,
|
||||||
|
Pod::B => 10,
|
||||||
|
Pod::C => 100,
|
||||||
|
Pod::D => 1000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dest(self) -> usize {
|
||||||
|
self as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<usize> for Pod {
|
||||||
|
type Error = usize;
|
||||||
|
|
||||||
|
fn try_from(index: usize) -> Result<Self, Self::Error> {
|
||||||
|
match index {
|
||||||
|
0 => Ok(Pod::A),
|
||||||
|
1 => Ok(Pod::B),
|
||||||
|
2 => Ok(Pod::C),
|
||||||
|
3 => Ok(Pod::D),
|
||||||
|
_ => Err(index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<char> for Pod {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(c: char) -> Result<Self, Self::Error> {
|
||||||
|
match c {
|
||||||
|
'A' => Ok(Pod::A),
|
||||||
|
'B' => Ok(Pod::B),
|
||||||
|
'C' => Ok(Pod::C),
|
||||||
|
'D' => Ok(Pod::D),
|
||||||
|
_ => Err("Invalid pod"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
|
||||||
|
struct State<const S: usize> {
|
||||||
|
hallway: [Option<Pod>; 11],
|
||||||
|
rooms: [[Option<Pod>; S]; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
fn room_hallway_pos(room: usize) -> usize {
|
||||||
|
room * 2 + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn abs_delta(a: usize, b: usize) -> usize {
|
||||||
|
if a < b {
|
||||||
|
b - a
|
||||||
|
} else {
|
||||||
|
a - b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const S: usize> State<S> {
|
||||||
|
const VALID_HALLWAY_POS: [usize; 7] = [0, 1, 3, 5, 7, 9, 10];
|
||||||
|
|
||||||
|
pub fn is_done(&self) -> bool {
|
||||||
|
self == &State {
|
||||||
|
hallway: Default::default(),
|
||||||
|
rooms: [
|
||||||
|
[Some(Pod::A); S],
|
||||||
|
[Some(Pod::B); S],
|
||||||
|
[Some(Pod::C); S],
|
||||||
|
[Some(Pod::D); S],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_to_queue(self, cost: u32, todo: &mut Todo<S>, visited: &mut Visited<S>) {
|
||||||
|
let entry = visited.entry(self.clone());
|
||||||
|
|
||||||
|
if matches!(&entry, Entry::Occupied(entry) if *entry.get() <= cost) {
|
||||||
|
// Already got a better one
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nightly only :'(
|
||||||
|
// entry.insert(cost);
|
||||||
|
*entry.or_default() = cost;
|
||||||
|
|
||||||
|
todo.push(Reverse((cost + self.estimate(), self)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate(&self) -> u32 {
|
||||||
|
// A* estimate. For every entry that is not already "at rest", the cost is the cost
|
||||||
|
// required to get it to the top of its intended room.
|
||||||
|
|
||||||
|
// Cost to enter the hole for all pods that still need to
|
||||||
|
let enter_estimate: u32 = self
|
||||||
|
.rooms
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, room)| {
|
||||||
|
let pod = Pod::try_from(index).unwrap();
|
||||||
|
|
||||||
|
room.iter()
|
||||||
|
.enumerate()
|
||||||
|
.rev()
|
||||||
|
.skip_while(|&(_, &entry)| entry == Some(pod))
|
||||||
|
.map(|(index, _)| index as u32 + 1)
|
||||||
|
.sum::<u32>()
|
||||||
|
* pod.cost()
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
// Cost for all of the hallway to move to above their intended rooms
|
||||||
|
let hallway_estimate: u32 = self
|
||||||
|
.hallway
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(pos, &pod)| {
|
||||||
|
let pod = pod?;
|
||||||
|
|
||||||
|
let destination_pos = room_hallway_pos(pod.dest());
|
||||||
|
|
||||||
|
Some(abs_delta(pos, destination_pos) as u32 * pod.cost())
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
// Cost to move out of the room and above the correct rooms
|
||||||
|
let rooms_estimate: u32 = self
|
||||||
|
.rooms
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(room_index, room)| {
|
||||||
|
let hallway_pos = room_hallway_pos(room_index);
|
||||||
|
|
||||||
|
room.iter()
|
||||||
|
.enumerate()
|
||||||
|
.rev()
|
||||||
|
.skip_while(|&(_, &entry)| {
|
||||||
|
entry.map(|pod| pod.dest() == room_index).unwrap_or(false)
|
||||||
|
})
|
||||||
|
.filter_map(|(room_pos, &pod)| {
|
||||||
|
let pod = pod?;
|
||||||
|
|
||||||
|
let destination_pos = room_hallway_pos(pod.dest());
|
||||||
|
|
||||||
|
let steps = 1 + room_pos + abs_delta(hallway_pos, destination_pos).max(2);
|
||||||
|
|
||||||
|
Some(steps as u32 * pod.cost())
|
||||||
|
})
|
||||||
|
.sum::<u32>()
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
enter_estimate + hallway_estimate + rooms_estimate
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_next(&self, cost: u32, todo: &mut Todo<S>, visited: &mut Visited<S>) {
|
||||||
|
self.room_to_hallway(cost, todo, visited);
|
||||||
|
self.hallway_to_room(cost, todo, visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn room_to_hallway(&self, cost: u32, todo: &mut Todo<S>, visited: &mut Visited<S>) {
|
||||||
|
for (index, room) in self.rooms.iter().enumerate() {
|
||||||
|
// Check if we even want to move anything out of this room
|
||||||
|
if room
|
||||||
|
.iter()
|
||||||
|
.all(|entry| entry.map(|pod| pod.dest() == index).unwrap_or(true))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (pos, pod) = room
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find_map(|(pos, entry)| entry.map(|pod| (pos, pod)))
|
||||||
|
.unwrap(); // Safe unwrap, we know it exists from above.
|
||||||
|
|
||||||
|
let base_cost = 1 + pos;
|
||||||
|
let hallway_pos = room_hallway_pos(index);
|
||||||
|
|
||||||
|
let mut queue_new = |new_pos, new_cost| {
|
||||||
|
let mut new_state = self.clone();
|
||||||
|
swap(
|
||||||
|
&mut new_state.hallway[new_pos],
|
||||||
|
&mut new_state.rooms[index][pos],
|
||||||
|
);
|
||||||
|
|
||||||
|
new_state.add_to_queue(new_cost + cost, todo, visited)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check positions to the left
|
||||||
|
for new_pos in (0..hallway_pos).rev() {
|
||||||
|
if self.hallway[new_pos].is_some() {
|
||||||
|
// Hit an occupied room
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Self::VALID_HALLWAY_POS.contains(&new_pos) {
|
||||||
|
// Not allowed to stop here
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_cost = (base_cost + hallway_pos - new_pos) as u32 * pod.cost();
|
||||||
|
queue_new(new_pos, new_cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And to the right
|
||||||
|
for new_pos in hallway_pos..self.hallway.len() {
|
||||||
|
if self.hallway[new_pos].is_some() {
|
||||||
|
// Hit an occupied room
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Self::VALID_HALLWAY_POS.contains(&new_pos) {
|
||||||
|
// Not allowed to stop here
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_cost = (base_cost + new_pos - hallway_pos) as u32 * pod.cost();
|
||||||
|
queue_new(new_pos, new_cost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hallway_to_room(&self, cost: u32, todo: &mut Todo<S>, visited: &mut Visited<S>) {
|
||||||
|
for (pos, pod) in self
|
||||||
|
.hallway
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(pos, pod)| pod.map(|pod| (pos, pod)))
|
||||||
|
{
|
||||||
|
let room = pod.dest();
|
||||||
|
let new_hallway_pos = room_hallway_pos(room);
|
||||||
|
|
||||||
|
// Check if the path is free
|
||||||
|
let in_between = if new_hallway_pos < pos {
|
||||||
|
&self.hallway[(new_hallway_pos + 1)..pos]
|
||||||
|
} else {
|
||||||
|
&self.hallway[(pos + 1)..new_hallway_pos]
|
||||||
|
};
|
||||||
|
|
||||||
|
if in_between.iter().any(Option::is_some) {
|
||||||
|
// Something's in the way
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we can move into the room
|
||||||
|
if self.rooms[room]
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.flatten()
|
||||||
|
.any(|other| other != pod)
|
||||||
|
{
|
||||||
|
// Scared of other pods
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let room_pos = if let Some(pos) = self.rooms[room].iter().rposition(Option::is_none) {
|
||||||
|
pos
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_cost = (abs_delta(pos, new_hallway_pos) + room_pos + 1) as u32 * pod.cost();
|
||||||
|
let mut new_state = self.clone();
|
||||||
|
swap(
|
||||||
|
&mut new_state.hallway[pos],
|
||||||
|
&mut new_state.rooms[room][room_pos],
|
||||||
|
);
|
||||||
|
new_state.add_to_queue(cost + new_cost, todo, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn solve(&self) -> u32 {
|
||||||
|
let mut todo = Todo::new();
|
||||||
|
|
||||||
|
let mut visited = HashMap::new();
|
||||||
|
visited.insert(self.clone(), 0);
|
||||||
|
|
||||||
|
todo.push(Reverse((self.estimate(), self.clone())));
|
||||||
|
|
||||||
|
while let Some(Reverse((_, state))) = todo.pop() {
|
||||||
|
let cost = *visited.get(&state).unwrap_or(&0);
|
||||||
|
|
||||||
|
if state.is_done() {
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.generate_next(cost, &mut todo, &mut visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("No route found!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const S: usize> Display for State<S> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let helper = |opt_pod| match opt_pod {
|
||||||
|
Some(Pod::A) => 'A',
|
||||||
|
Some(Pod::B) => 'B',
|
||||||
|
Some(Pod::C) => 'C',
|
||||||
|
Some(Pod::D) => 'D',
|
||||||
|
None => '.',
|
||||||
|
};
|
||||||
|
writeln!(f, "#############")?;
|
||||||
|
write!(f, "#")?;
|
||||||
|
|
||||||
|
for entry in self.hallway {
|
||||||
|
write!(f, "{}", helper(entry))?;
|
||||||
|
}
|
||||||
|
writeln!(f, "#")?;
|
||||||
|
|
||||||
|
for i in 0..S {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
" #{}#{}#{}#{}#",
|
||||||
|
helper(self.rooms[0][i]),
|
||||||
|
helper(self.rooms[1][i]),
|
||||||
|
helper(self.rooms[2][i]),
|
||||||
|
helper(self.rooms[3][i])
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, " #########")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_input(input: &mut dyn Read) -> State<2> {
|
||||||
|
let mut reader = LineIter::new(input);
|
||||||
|
let mut state = State {
|
||||||
|
hallway: Default::default(),
|
||||||
|
rooms: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = reader.next();
|
||||||
|
let _ = reader.next();
|
||||||
|
|
||||||
|
let mut helper = |idx: usize| {
|
||||||
|
reader
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.chars()
|
||||||
|
.filter_map(|c| Pod::try_from(c).ok())
|
||||||
|
.zip(&mut state.rooms)
|
||||||
|
.for_each(|(pod, room)| room[idx] = Some(pod))
|
||||||
|
};
|
||||||
|
|
||||||
|
helper(0);
|
||||||
|
helper(1);
|
||||||
|
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part1(input: &mut dyn Read) -> String {
|
||||||
|
let state = read_input(input);
|
||||||
|
|
||||||
|
state.solve().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(input: &mut dyn Read) -> String {
|
||||||
|
let state2 = read_input(input);
|
||||||
|
|
||||||
|
let state4 = State {
|
||||||
|
hallway: Default::default(),
|
||||||
|
rooms: [
|
||||||
|
[
|
||||||
|
state2.rooms[0][0],
|
||||||
|
Some(Pod::D),
|
||||||
|
Some(Pod::D),
|
||||||
|
state2.rooms[0][1],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
state2.rooms[1][0],
|
||||||
|
Some(Pod::C),
|
||||||
|
Some(Pod::B),
|
||||||
|
state2.rooms[1][1],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
state2.rooms[2][0],
|
||||||
|
Some(Pod::B),
|
||||||
|
Some(Pod::A),
|
||||||
|
state2.rooms[2][1],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
state2.rooms[3][0],
|
||||||
|
Some(Pod::A),
|
||||||
|
Some(Pod::C),
|
||||||
|
state2.rooms[3][1],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
state4.solve().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::test_implementation;
|
||||||
|
|
||||||
|
const SAMPLE: &[u8] = include_bytes!("samples/23.txt");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_done() {
|
||||||
|
let state = State {
|
||||||
|
hallway: Default::default(),
|
||||||
|
rooms: [
|
||||||
|
[Some(Pod::A); 2],
|
||||||
|
[Some(Pod::B); 2],
|
||||||
|
[Some(Pod::C); 2],
|
||||||
|
[Some(Pod::D); 2],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(state.is_done());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sample_part1() {
|
||||||
|
test_implementation(part1, SAMPLE, 12521);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sample_part2() {
|
||||||
|
test_implementation(part2, SAMPLE, 44169);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,159 @@
|
|||||||
|
//! Very input-specific reverse-engineered solution
|
||||||
|
//!
|
||||||
|
//! # General implementation
|
||||||
|
//!
|
||||||
|
//! The code in the examples is a series of 14 times this:
|
||||||
|
//!
|
||||||
|
//! ```txt
|
||||||
|
//! inp w -> read digit
|
||||||
|
//! mul x 0
|
||||||
|
//! add x z
|
||||||
|
//! mod x 26 -> x = z % 26
|
||||||
|
//! div z $A -> pop Z (see below)
|
||||||
|
//! add x $B
|
||||||
|
//! eql x w -> x = ((z + $B) == w)
|
||||||
|
//! eql x 0 -> x = ((z + $B) != w)
|
||||||
|
//! mul y 0
|
||||||
|
//! add y 25
|
||||||
|
//! mul y x
|
||||||
|
//! add y 1 -> if x { 26 } else { 1 }
|
||||||
|
//! mul z y -> if x { z *= 26 } (push z, see below)
|
||||||
|
//! mul y 0
|
||||||
|
//! add y w
|
||||||
|
//! add y $C -> y = w + $C
|
||||||
|
//! mul y x
|
||||||
|
//! add z y -> if x { z += w + $C }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! `$A` is either `1` or `26` which we can translate to a bool `$A == 26` for convenience. This
|
||||||
|
//! simplifies to the following rust.
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! fn validate<const A: bool, const B: i32, const C: i32>(mut z: i32, digit: i32) -> i32 {
|
||||||
|
//! let x = (z % 26 + B) != digit;
|
||||||
|
//! if A {
|
||||||
|
//! z /= 26;
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! if x {
|
||||||
|
//! z = 26 * z + digit + C;
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! z
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! In human terms, `z` is used to hold a base 26 number. When `$A` is `true`, we pop off the least
|
||||||
|
//! significant digit by dividing by 26. Then, depending on whether `(z + $B) % 26` is equal to our
|
||||||
|
//! digit, we push `digit + $C`. Ideally, we should pop as often as we push in order to arrive at `z
|
||||||
|
//! == 0` in the end. The input contains 7 pops, so we want each of those to not push.
|
||||||
|
//!
|
||||||
|
//! To solve this problem, we use a backtracking memoizing algorithm, where we cancel every sequence
|
||||||
|
//! that fails to pop at some point. A pop is failed whenever we execute a validation pop where we
|
||||||
|
//! can pop if `x` happened to be set to `0` at the time of the check. We can memoize this over the
|
||||||
|
//! running value of `z`.
|
||||||
|
//!
|
||||||
|
//! This implementation probably doesn't work on other people's input; to fix it, you'll want to
|
||||||
|
//! update the `parse_step` function. Good luck with that.
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
pub fn part1(_input: &mut dyn Read) -> String {
|
use nom::branch::alt;
|
||||||
todo!()
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::newline;
|
||||||
|
use nom::combinator::map;
|
||||||
|
use nom::multi::separated_list1;
|
||||||
|
use nom::sequence::delimited;
|
||||||
|
use nom::sequence::preceded;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
use nom::IResult;
|
||||||
|
|
||||||
|
use crate::common::read_input;
|
||||||
|
|
||||||
|
type Cache = HashMap<(usize, i32), Option<i64>>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Step(bool, i32, i32);
|
||||||
|
|
||||||
|
impl Step {
|
||||||
|
fn evaluate(&self, digit: i32, z: i32) -> Option<i32> {
|
||||||
|
if self.0 {
|
||||||
|
(z % 26 + self.1 == digit).then(|| z / 26)
|
||||||
|
} else {
|
||||||
|
Some(z * 26 + digit + self.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn part2(_input: &mut dyn Read) -> String {
|
fn parse_step(input: &[u8]) -> IResult<&[u8], Step> {
|
||||||
todo!()
|
use nom::character::complete::i32;
|
||||||
|
|
||||||
|
let parse_pop = preceded(
|
||||||
|
tag("inp w\nmul x 0\nadd x z\nmod x 26\ndiv z "),
|
||||||
|
alt((map(tag("1"), |_| false), map(tag("26"), |_| true))),
|
||||||
|
);
|
||||||
|
|
||||||
|
let parse_a = preceded(tag("\nadd x "), i32);
|
||||||
|
|
||||||
|
let parse_b = delimited(
|
||||||
|
tag("\neql x w\neql x 0\nmul y 0\nadd y 25\nmul y x\nadd y 1\nmul z y\nmul y 0\nadd y w\nadd y "),
|
||||||
|
i32,
|
||||||
|
tag("\nmul y x\nadd z y"),
|
||||||
|
);
|
||||||
|
|
||||||
|
map(tuple((parse_pop, parse_a, parse_b)), |(pop, a, b)| {
|
||||||
|
Step(pop, a, b)
|
||||||
|
})(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_input(input: &[u8]) -> IResult<&[u8], Vec<Step>> {
|
||||||
|
separated_list1(newline, parse_step)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn optimize(
|
||||||
|
current: usize,
|
||||||
|
steps: &[Step],
|
||||||
|
digits: &[i32],
|
||||||
|
z: i32,
|
||||||
|
cache: &mut Cache,
|
||||||
|
) -> Option<i64> {
|
||||||
|
if current >= steps.len() {
|
||||||
|
return (z == 0).then(|| 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(&memoized) = cache.get(&(current, z)) {
|
||||||
|
return memoized;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = digits.iter().find_map(|&digit| {
|
||||||
|
let z = steps[current].evaluate(digit, z)?;
|
||||||
|
let result = optimize(current + 1, steps, digits, z, cache)?;
|
||||||
|
|
||||||
|
Some(result + digit as i64 * 10i64.pow(13 - current as u32))
|
||||||
|
});
|
||||||
|
cache.insert((current, z), result);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parts_common(input: &mut dyn Read, digits: &[i32]) -> String {
|
||||||
|
let steps = read_input(input, parse_input);
|
||||||
|
|
||||||
|
let mut cache = Cache::new();
|
||||||
|
|
||||||
|
optimize(0, &steps, digits, 0, &mut cache)
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part1(input: &mut dyn Read) -> String {
|
||||||
|
let digits = [9, 8, 7, 6, 5, 4, 3, 2, 1];
|
||||||
|
|
||||||
|
parts_common(input, &digits)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(input: &mut dyn Read) -> String {
|
||||||
|
let digits = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||||
|
|
||||||
|
parts_common(input, &digits)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ pub fn get_implementation(day: usize, part2: bool) -> Solution {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn test_implementation(solution: Solution, data: &[u8], answer: impl ToString) {
|
fn test_implementation(solution: Solution, data: &[u8], answer: impl ToString) {
|
||||||
let result = solution(&mut &data[..]);
|
let result = solution(&mut &*data);
|
||||||
|
|
||||||
assert_eq!(answer.to_string(), result);
|
assert_eq!(answer.to_string(), result);
|
||||||
}
|
}
|
||||||
|
|||||||
5
2021/src/samples/23.txt
Normal file
5
2021/src/samples/23.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#############
|
||||||
|
#...........#
|
||||||
|
###B#C#B#D###
|
||||||
|
#A#D#C#A#
|
||||||
|
#########
|
||||||
26
2022/Cargo.toml
Normal file
26
2022/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "aoc_2022"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.66"
|
||||||
|
clap = { version = "4.0.19", features = ["derive"] }
|
||||||
|
nom = "7.1.1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = "0.4.0"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
# Keep debug information in release for better flamegraphs
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[profile.bench]
|
||||||
|
# And same for benchmarking
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "days"
|
||||||
|
harness = false
|
||||||
20
2022/README.md
Normal file
20
2022/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Advent of Code 2022
|
||||||
|
|
||||||
|
Another year and another Advent of Code in Rust. Because last year went so well, this time we'll be
|
||||||
|
aiming for a total time of under 250ms.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ target/release/aoc_2022 --help
|
||||||
|
Advent of Code 2022 runner
|
||||||
|
|
||||||
|
Usage: aoc_2022 [OPTIONS] <DAY>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<DAY> Which day to run
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-t, --time Print time taken
|
||||||
|
-2, --part2 Run part 2 instead of part 1
|
||||||
|
-i, --input <INPUT> Read input from the given file instead of stdin
|
||||||
|
-h, --help Print help information
|
||||||
|
```
|
||||||
46
2022/benches/days.rs
Normal file
46
2022/benches/days.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use aoc_2022::get_implementation;
|
||||||
|
use criterion::criterion_group;
|
||||||
|
use criterion::criterion_main;
|
||||||
|
use criterion::BenchmarkId;
|
||||||
|
use criterion::Criterion;
|
||||||
|
|
||||||
|
/// Number of days we have an implementation to benchmark
|
||||||
|
const DAYS_IMPLEMENTED: u8 = 2;
|
||||||
|
|
||||||
|
fn read_input(day: u8) -> Vec<u8> {
|
||||||
|
let input_path = format!("inputs/{:02}.txt", day);
|
||||||
|
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
File::open(input_path)
|
||||||
|
.expect("Failed to open input file")
|
||||||
|
.read_to_end(&mut buffer)
|
||||||
|
.expect("Failed to read input file");
|
||||||
|
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn benchmark_days(c: &mut Criterion) {
|
||||||
|
for day in 1..=DAYS_IMPLEMENTED {
|
||||||
|
let input = read_input(day);
|
||||||
|
|
||||||
|
let part1 = get_implementation(day, false).unwrap();
|
||||||
|
|
||||||
|
c.bench_with_input(BenchmarkId::new("part1", day), &input, |b, i| {
|
||||||
|
b.iter(|| part1(i));
|
||||||
|
});
|
||||||
|
|
||||||
|
if day < 25 {
|
||||||
|
let part2 = get_implementation(day, true).unwrap();
|
||||||
|
|
||||||
|
c.bench_with_input(BenchmarkId::new("part2", day), &input, |b, i| {
|
||||||
|
b.iter(|| part2(i));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, benchmark_days);
|
||||||
|
criterion_main!(benches);
|
||||||
0
2022/inputs/.gitkeep
Normal file
0
2022/inputs/.gitkeep
Normal file
2259
2022/inputs/01.txt
Normal file
2259
2022/inputs/01.txt
Normal file
File diff suppressed because it is too large
Load Diff
2500
2022/inputs/02.txt
Normal file
2500
2022/inputs/02.txt
Normal file
File diff suppressed because it is too large
Load Diff
94
2022/src/common.rs
Normal file
94
2022/src/common.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
//! Common helper utilities to all days
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use nom::error::ErrorKind;
|
||||||
|
use nom::error::ParseError;
|
||||||
|
use nom::Finish;
|
||||||
|
use nom::IResult;
|
||||||
|
use nom::InputLength;
|
||||||
|
use nom::Parser;
|
||||||
|
|
||||||
|
/// Parse input from some nom parser and return as an anyhow result
|
||||||
|
///
|
||||||
|
/// This method exists as a convenience because nom's errors cannot otherwise be easily converted to
|
||||||
|
/// an anyhow error, and I don't want to keep track of custom error implementations here.
|
||||||
|
pub fn parse_input<'a, O>(
|
||||||
|
input: &'a [u8],
|
||||||
|
mut parser: impl Parser<&'a [u8], O, nom::error::Error<&'a [u8]>>,
|
||||||
|
) -> Result<O> {
|
||||||
|
let (unparsed, output) = parser.parse(input).finish().map_err(|e| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Parser error {:?} to parse at {}",
|
||||||
|
e.code,
|
||||||
|
String::from_utf8_lossy(e.input)
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !unparsed.is_empty() {
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"Not all input consumed: {}",
|
||||||
|
String::from_utf8_lossy(unparsed)
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies a parser iteratively and reduces the results using the given function. Fails if the
|
||||||
|
/// embedded parser doesn't return at least one result.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// - `f`: the function to apply
|
||||||
|
/// - `g`: the function that combines the result o `f` with previous results
|
||||||
|
///
|
||||||
|
/// This implementation is based on [`nom::multi::fold_many1`] with minor differences. If
|
||||||
|
/// successful, this should probably be upstreamed.
|
||||||
|
pub fn reduce_many1<I, O, E, F>(
|
||||||
|
mut f: F,
|
||||||
|
mut g: impl FnMut(O, O) -> O,
|
||||||
|
) -> impl FnMut(I) -> IResult<I, O, E>
|
||||||
|
where
|
||||||
|
I: Clone + InputLength,
|
||||||
|
E: ParseError<I>,
|
||||||
|
F: Parser<I, O, E>,
|
||||||
|
{
|
||||||
|
// Cannot delegate to fold_many0 because that would make the function FnOnce rather than FnMut,
|
||||||
|
// since it would transfer ownership of the embedded parser to fold_many0.
|
||||||
|
move |i: I| {
|
||||||
|
let _i = i.clone();
|
||||||
|
match f.parse(_i) {
|
||||||
|
Err(nom::Err::Error(_)) => {
|
||||||
|
Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Many1)))
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
Ok((i1, mut acc)) => {
|
||||||
|
let mut input = i1;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let _input = input.clone();
|
||||||
|
let len = input.input_len();
|
||||||
|
match f.parse(_input) {
|
||||||
|
Err(nom::Err::Error(_)) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
Ok((i, o)) => {
|
||||||
|
// infinite loop check: the parser must always consume
|
||||||
|
if i.input_len() == len {
|
||||||
|
return Err(nom::Err::Failure(E::from_error_kind(
|
||||||
|
i,
|
||||||
|
ErrorKind::Many1,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
acc = g(acc, o);
|
||||||
|
input = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((input, acc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
2022/src/day01.rs
Normal file
55
2022/src/day01.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use nom::character::complete::newline;
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::multi::separated_list0;
|
||||||
|
use nom::sequence::terminated;
|
||||||
|
use nom::IResult;
|
||||||
|
|
||||||
|
use crate::common::parse_input;
|
||||||
|
use crate::common::reduce_many1;
|
||||||
|
|
||||||
|
fn parse_elf(input: &[u8]) -> IResult<&[u8], i32> {
|
||||||
|
reduce_many1(terminated(nom::character::complete::i32, newline), Add::add)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_max(input: &[u8]) -> IResult<&[u8], i32> {
|
||||||
|
reduce_many1(terminated(parse_elf, opt(newline)), Ord::max)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part1(input: &[u8]) -> Result<String> {
|
||||||
|
let result = parse_input(input, parse_max)?.to_string();
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_elf_list(input: &[u8]) -> IResult<&[u8], Vec<i32>> {
|
||||||
|
separated_list0(newline, parse_elf)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(input: &[u8]) -> Result<String> {
|
||||||
|
let mut elves = parse_input(input, parse_elf_list)?;
|
||||||
|
|
||||||
|
let (first, third, _) = elves.select_nth_unstable_by(2, |a, b| Ord::cmp(b, a));
|
||||||
|
|
||||||
|
let result = first[1] + first[0] + *third;
|
||||||
|
|
||||||
|
Ok(result.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const SAMPLE: &[u8] = include_bytes!("samples/01.txt");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sample_part1() {
|
||||||
|
assert_eq!(part1(SAMPLE).unwrap(), "24000");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sample_part2() {
|
||||||
|
assert_eq!(part2(SAMPLE).unwrap(), "45000");
|
||||||
|
}
|
||||||
|
}
|
||||||
129
2022/src/day02.rs
Normal file
129
2022/src/day02.rs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use nom::character::complete::newline;
|
||||||
|
use nom::combinator::map;
|
||||||
|
use nom::combinator::map_res;
|
||||||
|
use nom::sequence::separated_pair;
|
||||||
|
use nom::sequence::terminated;
|
||||||
|
use nom::IResult;
|
||||||
|
|
||||||
|
use crate::common::parse_input;
|
||||||
|
use crate::common::reduce_many1;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
enum Rps {
|
||||||
|
Rock,
|
||||||
|
Paper,
|
||||||
|
Scissors,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rps {
|
||||||
|
/// Score we get by playing this move
|
||||||
|
fn score(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Rps::Rock => 1,
|
||||||
|
Rps::Paper => 2,
|
||||||
|
Rps::Scissors => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Score we get from the result from playing given other
|
||||||
|
fn score_against(self, other: Self) -> u32 {
|
||||||
|
match (self, other) {
|
||||||
|
(a, b) if a == b => 3,
|
||||||
|
(Rps::Rock, Rps::Paper) | (Rps::Paper, Rps::Scissors) | (Rps::Scissors, Rps::Rock) => 0,
|
||||||
|
_ => 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Score if the result is according to the instruction
|
||||||
|
fn score_result(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Rps::Rock => 0, // Rock is lose
|
||||||
|
Rps::Paper => 3, // Paper is draw
|
||||||
|
Rps::Scissors => 6, // Scissors is win
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move we need to achieve the result indicated by self
|
||||||
|
fn needed(self, other: Self) -> Self {
|
||||||
|
match (self, other) {
|
||||||
|
(Rps::Paper, other) => other,
|
||||||
|
(Rps::Rock, Rps::Rock) => Rps::Scissors,
|
||||||
|
(Rps::Rock, Rps::Paper) => Rps::Rock,
|
||||||
|
(Rps::Rock, Rps::Scissors) => Rps::Paper,
|
||||||
|
(Rps::Scissors, Rps::Rock) => Rps::Paper,
|
||||||
|
(Rps::Scissors, Rps::Paper) => Rps::Scissors,
|
||||||
|
(Rps::Scissors, Rps::Scissors) => Rps::Rock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Rps {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
b'A' | b'X' => Ok(Rps::Rock),
|
||||||
|
b'B' | b'Y' => Ok(Rps::Paper),
|
||||||
|
b'C' | b'Z' => Ok(Rps::Scissors),
|
||||||
|
_ => Err(anyhow::anyhow!("Invalid RPS: {value}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_line(input: &[u8]) -> IResult<&[u8], (Rps, Rps)> {
|
||||||
|
fn parse_rps(input: &[u8]) -> IResult<&[u8], Rps> {
|
||||||
|
// Note: alpha1 also sort of works but is significantly slower
|
||||||
|
map_res(nom::bytes::complete::take(1usize), |v: &[u8]| {
|
||||||
|
Rps::try_from(v[0])
|
||||||
|
})(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
terminated(
|
||||||
|
separated_pair(parse_rps, nom::character::complete::char(' '), parse_rps),
|
||||||
|
newline,
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part1(input: &[u8]) -> Result<String> {
|
||||||
|
parse_input(
|
||||||
|
input,
|
||||||
|
reduce_many1(
|
||||||
|
map(parse_line, |(them, us)| us.score() + us.score_against(them)),
|
||||||
|
Add::add,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map(|sum| sum.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(input: &[u8]) -> Result<String> {
|
||||||
|
parse_input(
|
||||||
|
input,
|
||||||
|
reduce_many1(
|
||||||
|
map(parse_line, |(them, us)| {
|
||||||
|
us.score_result() + us.needed(them).score()
|
||||||
|
}),
|
||||||
|
Add::add,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map(|sum| sum.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const SAMPLE: &[u8] = include_bytes!("samples/02.txt");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sample_part1() {
|
||||||
|
assert_eq!(part1(SAMPLE).unwrap(), "15")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sample_part2() {
|
||||||
|
assert_eq!(part2(SAMPLE).unwrap(), "12")
|
||||||
|
}
|
||||||
|
}
|
||||||
9
2022/src/day03.rs
Normal file
9
2022/src/day03.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day04.rs
Normal file
9
2022/src/day04.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day05.rs
Normal file
9
2022/src/day05.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day06.rs
Normal file
9
2022/src/day06.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day07.rs
Normal file
9
2022/src/day07.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day08.rs
Normal file
9
2022/src/day08.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day09.rs
Normal file
9
2022/src/day09.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day10.rs
Normal file
9
2022/src/day10.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day11.rs
Normal file
9
2022/src/day11.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day12.rs
Normal file
9
2022/src/day12.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day13.rs
Normal file
9
2022/src/day13.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day14.rs
Normal file
9
2022/src/day14.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day15.rs
Normal file
9
2022/src/day15.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day16.rs
Normal file
9
2022/src/day16.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day17.rs
Normal file
9
2022/src/day17.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day18.rs
Normal file
9
2022/src/day18.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day19.rs
Normal file
9
2022/src/day19.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day20.rs
Normal file
9
2022/src/day20.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day21.rs
Normal file
9
2022/src/day21.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day22.rs
Normal file
9
2022/src/day22.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day23.rs
Normal file
9
2022/src/day23.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
9
2022/src/day24.rs
Normal file
9
2022/src/day24.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
5
2022/src/day25.rs
Normal file
5
2022/src/day25.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn part1(_input: &[u8]) -> Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
91
2022/src/lib.rs
Normal file
91
2022/src/lib.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
mod day01;
|
||||||
|
mod day02;
|
||||||
|
mod day03;
|
||||||
|
mod day04;
|
||||||
|
mod day05;
|
||||||
|
mod day06;
|
||||||
|
mod day07;
|
||||||
|
mod day08;
|
||||||
|
mod day09;
|
||||||
|
mod day10;
|
||||||
|
mod day11;
|
||||||
|
mod day12;
|
||||||
|
mod day13;
|
||||||
|
mod day14;
|
||||||
|
mod day15;
|
||||||
|
mod day16;
|
||||||
|
mod day17;
|
||||||
|
mod day18;
|
||||||
|
mod day19;
|
||||||
|
mod day20;
|
||||||
|
mod day21;
|
||||||
|
mod day22;
|
||||||
|
mod day23;
|
||||||
|
mod day24;
|
||||||
|
mod day25;
|
||||||
|
|
||||||
|
type Solution = fn(&[u8]) -> Result<String>;
|
||||||
|
|
||||||
|
pub fn get_implementation(day: u8, part2: bool) -> Result<Solution> {
|
||||||
|
if !part2 {
|
||||||
|
match day {
|
||||||
|
1 => Ok(day01::part1),
|
||||||
|
2 => Ok(day02::part1),
|
||||||
|
3 => Ok(day03::part1),
|
||||||
|
4 => Ok(day04::part1),
|
||||||
|
5 => Ok(day05::part1),
|
||||||
|
6 => Ok(day06::part1),
|
||||||
|
7 => Ok(day07::part1),
|
||||||
|
8 => Ok(day08::part1),
|
||||||
|
9 => Ok(day09::part1),
|
||||||
|
10 => Ok(day10::part1),
|
||||||
|
11 => Ok(day11::part1),
|
||||||
|
12 => Ok(day12::part1),
|
||||||
|
13 => Ok(day13::part1),
|
||||||
|
14 => Ok(day14::part1),
|
||||||
|
15 => Ok(day15::part1),
|
||||||
|
16 => Ok(day16::part1),
|
||||||
|
17 => Ok(day17::part1),
|
||||||
|
18 => Ok(day18::part1),
|
||||||
|
19 => Ok(day19::part1),
|
||||||
|
20 => Ok(day20::part1),
|
||||||
|
21 => Ok(day21::part1),
|
||||||
|
22 => Ok(day22::part1),
|
||||||
|
23 => Ok(day23::part1),
|
||||||
|
24 => Ok(day24::part1),
|
||||||
|
25 => Ok(day25::part1),
|
||||||
|
_ => anyhow::bail!("Invalid day for part 1: {day}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match day {
|
||||||
|
1 => Ok(day01::part2),
|
||||||
|
2 => Ok(day02::part2),
|
||||||
|
3 => Ok(day03::part2),
|
||||||
|
4 => Ok(day04::part2),
|
||||||
|
5 => Ok(day05::part2),
|
||||||
|
6 => Ok(day06::part2),
|
||||||
|
7 => Ok(day07::part2),
|
||||||
|
8 => Ok(day08::part2),
|
||||||
|
9 => Ok(day09::part2),
|
||||||
|
10 => Ok(day10::part2),
|
||||||
|
11 => Ok(day11::part2),
|
||||||
|
12 => Ok(day12::part2),
|
||||||
|
13 => Ok(day13::part2),
|
||||||
|
14 => Ok(day14::part2),
|
||||||
|
15 => Ok(day15::part2),
|
||||||
|
16 => Ok(day16::part2),
|
||||||
|
17 => Ok(day17::part2),
|
||||||
|
18 => Ok(day18::part2),
|
||||||
|
19 => Ok(day19::part2),
|
||||||
|
20 => Ok(day20::part2),
|
||||||
|
21 => Ok(day21::part2),
|
||||||
|
22 => Ok(day22::part2),
|
||||||
|
23 => Ok(day23::part2),
|
||||||
|
24 => Ok(day24::part2),
|
||||||
|
_ => anyhow::bail!("Invalid day for part 2: {day}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
2022/src/main.rs
Normal file
61
2022/src/main.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::num::NonZeroU8;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use aoc_2022::get_implementation;
|
||||||
|
|
||||||
|
/// Advent of Code 2022 runner
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Opts {
|
||||||
|
/// Which day to run
|
||||||
|
day: NonZeroU8,
|
||||||
|
|
||||||
|
/// Print time taken
|
||||||
|
#[clap(short, long)]
|
||||||
|
time: bool,
|
||||||
|
|
||||||
|
/// Run part 2 instead of part 1
|
||||||
|
#[clap(short = '2', long)]
|
||||||
|
part2: bool,
|
||||||
|
|
||||||
|
/// Read input from the given file instead of stdin
|
||||||
|
#[clap(short, long)]
|
||||||
|
input: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Opts {
|
||||||
|
fn input_data(&self) -> Result<Vec<u8>> {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
|
if let Some(input) = &self.input {
|
||||||
|
File::open(input)?.read_to_end(&mut buffer)?;
|
||||||
|
} else {
|
||||||
|
std::io::stdin().read_to_end(&mut buffer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let opts: Opts = Opts::parse();
|
||||||
|
|
||||||
|
let input = opts.input_data()?;
|
||||||
|
|
||||||
|
let implementation = get_implementation(opts.day.get(), opts.part2)?;
|
||||||
|
|
||||||
|
let begin = Instant::now();
|
||||||
|
let result = implementation(&input)?;
|
||||||
|
|
||||||
|
if opts.time {
|
||||||
|
eprintln!("Execution time: {:?}", Instant::now().duration_since(begin));
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
0
2022/src/samples/.gitkeep
Normal file
0
2022/src/samples/.gitkeep
Normal file
14
2022/src/samples/01.txt
Normal file
14
2022/src/samples/01.txt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
1000
|
||||||
|
2000
|
||||||
|
3000
|
||||||
|
|
||||||
|
4000
|
||||||
|
|
||||||
|
5000
|
||||||
|
6000
|
||||||
|
|
||||||
|
7000
|
||||||
|
8000
|
||||||
|
9000
|
||||||
|
|
||||||
|
10000
|
||||||
3
2022/src/samples/02.txt
Normal file
3
2022/src/samples/02.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
A Y
|
||||||
|
B X
|
||||||
|
C Z
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Advent of Code
|
# Advent of Code
|
||||||
|
|
||||||
[](https://github.com/bertptrs/adventofcode/actions/workflows/2021.yml)
|
[](https://github.com/bertptrs/adventofcode/actions/workflows/2022.yml)
|
||||||
|
|
||||||
This repository contains my solutions for Advent of Code. See:
|
This repository contains my solutions for Advent of Code. See:
|
||||||
|
|
||||||
@@ -11,3 +11,4 @@ This repository contains my solutions for Advent of Code. See:
|
|||||||
- [2019 edition](./2019)
|
- [2019 edition](./2019)
|
||||||
- [2020 edition](./2020)
|
- [2020 edition](./2020)
|
||||||
- [2021 edition](./2021)
|
- [2021 edition](./2021)
|
||||||
|
- [2022 edition](./2022)
|
||||||
|
|||||||
Reference in New Issue
Block a user