Improve perf for day 20
This commit is contained in:
Родитель
bfe2a15dd1
Коммит
468d43c129
|
@ -39,6 +39,14 @@ impl<T> Field2D<T> {
|
|||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub fn stride_at(&self, idx: usize) -> &[T] {
|
||||
&self.data[self.stride * idx..self.stride * (idx + 1)]
|
||||
}
|
||||
|
||||
pub fn stride_at_mut(&mut self, idx: usize) -> &mut [T] {
|
||||
&mut self.data[self.stride * idx..self.stride * (idx + 1)]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Field2D<T>
|
||||
|
|
|
@ -11,6 +11,6 @@ aoc-lib = { path = "../aoc-lib" }
|
|||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
|
||||
# [[bench]]
|
||||
# name = "bench"
|
||||
# harness = false
|
||||
[[bench]]
|
||||
name = "bench"
|
||||
harness = false
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use aoc_lib::AdventOfCode;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
#[path = "../src/main.rs"]
|
||||
mod main;
|
||||
|
||||
fn bench_main(c: &mut Criterion) {
|
||||
c.bench_function("parse input", |b| {
|
||||
let input = include_str!("../input.txt");
|
||||
b.iter(|| main::Day20::parse_input(black_box(input)))
|
||||
});
|
||||
|
||||
c.bench_function("solve 1", |b| {
|
||||
let input = main::Day20::parse_input(include_str!("../input.txt"));
|
||||
b.iter(|| main::Day20::solve_1(black_box(&input)))
|
||||
});
|
||||
|
||||
c.bench_function("solve 2", |b| {
|
||||
let input = main::Day20::parse_input(include_str!("../input.txt"));
|
||||
b.iter(|| main::Day20::solve_2(black_box(&input)))
|
||||
});
|
||||
|
||||
c.bench_function("parse sample input", |b| {
|
||||
let input = include_str!("../sample.txt");
|
||||
b.iter(|| main::Day20::parse_input(black_box(input)))
|
||||
});
|
||||
|
||||
c.bench_function("solve 1 (sample input)", |b| {
|
||||
let input = main::Day20::parse_input(include_str!("../sample.txt"));
|
||||
b.iter(|| main::Day20::solve_1(black_box(&input)))
|
||||
});
|
||||
|
||||
c.bench_function("solve 2 (sample input)", |b| {
|
||||
let input = main::Day20::parse_input(include_str!("../sample.txt"));
|
||||
b.iter(|| main::Day20::solve_2(black_box(&input)))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_main);
|
||||
criterion_main!(benches);
|
|
@ -1,21 +1,21 @@
|
|||
use std::{
|
||||
collections::HashSet,
|
||||
fmt::{Display, Formatter},
|
||||
ops::{Add, Sub},
|
||||
};
|
||||
|
||||
use aoc_lib::*;
|
||||
use aoc_lib::{utils::Field2D, *};
|
||||
|
||||
aoc_setup!(Day20, sample 1: 35, sample 2: 3351, part 1: 5489, part 2: 19066);
|
||||
|
||||
// TODO: consider other datatypes
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Vector2 {
|
||||
x: i16,
|
||||
y: i16,
|
||||
x: isize,
|
||||
y: isize,
|
||||
}
|
||||
|
||||
impl From<(i16, i16)> for Vector2 {
|
||||
fn from(data: (i16, i16)) -> Self {
|
||||
impl From<(isize, isize)> for Vector2 {
|
||||
fn from(data: (isize, isize)) -> Self {
|
||||
Self {
|
||||
x: data.0,
|
||||
y: data.1,
|
||||
|
@ -23,66 +23,103 @@ impl From<(i16, i16)> for Vector2 {
|
|||
}
|
||||
}
|
||||
|
||||
impl Vector2 {
|
||||
pub fn move_x(self, x: i16) -> Self {
|
||||
impl Add for Vector2 {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
x: self.x + x as i16,
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vector2 {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Vector2 {
|
||||
pub fn move_x(self, x: isize) -> Self {
|
||||
Self {
|
||||
x: self.x + x as isize,
|
||||
y: self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_y(self, y: i16) -> Self {
|
||||
pub fn move_y(self, y: isize) -> Self {
|
||||
Self {
|
||||
x: self.x,
|
||||
y: self.y + y as i16,
|
||||
y: self.y + y as isize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct InfiniteField2D {
|
||||
data: HashSet<Vector2>,
|
||||
min_x: i16,
|
||||
min_y: i16,
|
||||
max_x: i16,
|
||||
max_y: i16,
|
||||
pub struct Field2DWithMovableOrigin<T> {
|
||||
data: Field2D<T>,
|
||||
origin: Vector2,
|
||||
min_x: isize,
|
||||
min_y: isize,
|
||||
max_x: isize,
|
||||
max_y: isize,
|
||||
padded_with_ones: bool,
|
||||
}
|
||||
|
||||
impl InfiniteField2D {
|
||||
pub fn from_lines<'a>(lines: impl Iterator<Item = &'a str>) -> Self {
|
||||
lines
|
||||
.enumerate()
|
||||
.flat_map(|(y, line)| {
|
||||
line.bytes().enumerate().map(move |(x, c)| {
|
||||
(
|
||||
Vector2 {
|
||||
x: x as i16,
|
||||
y: y as i16,
|
||||
},
|
||||
c == b'#',
|
||||
)
|
||||
})
|
||||
})
|
||||
.fold(Self::default(), |mut acc, (pos, bit)| {
|
||||
acc.insert(pos, bit);
|
||||
acc
|
||||
})
|
||||
impl Field2DWithMovableOrigin<bool> {
|
||||
pub fn with_capacity(width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
data: Field2D::new(width, height, false),
|
||||
origin: Vector2::from((0, 0)),
|
||||
min_x: 0,
|
||||
min_y: 0,
|
||||
max_x: 0,
|
||||
max_y: 0,
|
||||
padded_with_ones: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, pos: Vector2, bit: bool) -> bool {
|
||||
pub fn init_with(&mut self, source: &Field2D<bool>) {
|
||||
self.max_x = self.max_x.max((source.width() - 1) as isize);
|
||||
self.max_y = self.max_y.max((source.height() - 1) as isize);
|
||||
|
||||
debug_assert!(self.data.width() >= source.width());
|
||||
debug_assert!(self.data.height() >= source.height());
|
||||
|
||||
// copy over data
|
||||
for y in 0..source.height() {
|
||||
let source_stride = source.stride_at(y as usize);
|
||||
let stride = &mut self.data.stride_at_mut(self.origin.y as usize + y)
|
||||
[(self.origin.x as usize)..(self.origin.x as usize + source.width())];
|
||||
stride.copy_from_slice(source_stride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Field2DWithMovableOrigin<bool> {
|
||||
pub fn get(&self, pos: Vector2) -> bool {
|
||||
let real_position = pos + self.origin;
|
||||
self.data[(real_position.x as usize, real_position.y as usize)]
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, pos: Vector2, bit: bool) {
|
||||
if bit {
|
||||
self.min_x = self.min_x.min(pos.x);
|
||||
self.min_y = self.min_y.min(pos.y);
|
||||
self.max_x = self.max_x.max(pos.x);
|
||||
self.max_y = self.max_y.max(pos.y);
|
||||
self.data.insert(pos)
|
||||
} else {
|
||||
self.data.remove(&pos)
|
||||
}
|
||||
|
||||
let real_position = pos + self.origin;
|
||||
self.data[(real_position.x as usize, real_position.y as usize)] = bit;
|
||||
}
|
||||
|
||||
pub fn read_square_at(&self, pos: Vector2) -> i16 {
|
||||
pub fn read_square_at(&self, pos: Vector2) -> isize {
|
||||
self.bit(pos.move_y(-1).move_x(-1)) << 8
|
||||
| self.bit(pos.move_y(-1).move_x(0)) << 7
|
||||
| self.bit(pos.move_y(-1).move_x(1)) << 6
|
||||
|
@ -91,37 +128,33 @@ impl InfiniteField2D {
|
|||
| self.bit(pos.move_y(0).move_x(1)) << 3
|
||||
| self.bit(pos.move_y(1).move_x(-1)) << 2
|
||||
| self.bit(pos.move_y(1).move_x(0)) << 1
|
||||
| self.bit(pos.move_y(1).move_x(1)) << 0
|
||||
| self.bit(pos.move_y(1).move_x(1))
|
||||
}
|
||||
|
||||
fn bit(&self, pos: Vector2) -> i16 {
|
||||
if (self.is_out_of_range(pos)) && self.padded_with_ones {
|
||||
1
|
||||
fn bit(&self, pos: Vector2) -> isize {
|
||||
if self.is_out_of_range(pos) {
|
||||
self.padded_with_ones as isize
|
||||
} else {
|
||||
self.data.contains(&pos) as i16
|
||||
self.get(pos) as isize
|
||||
}
|
||||
}
|
||||
|
||||
fn is_out_of_range(&self, pos: Vector2) -> bool {
|
||||
!(self.min_x..=self.max_x).contains(&pos.x) || !(self.min_y..=self.max_y).contains(&pos.y)
|
||||
}
|
||||
|
||||
pub fn count_ones(&self) -> usize {
|
||||
self.data.data.iter().cloned().filter(|&x| x).count()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InfiniteField2D {
|
||||
impl Display for Field2DWithMovableOrigin<bool> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for y in self.min_y..=self.max_y {
|
||||
for x in self.min_x..=self.max_x {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
if self.data.contains(&Vector2 { x, y }) {
|
||||
'#'
|
||||
} else {
|
||||
'.'
|
||||
}
|
||||
)?;
|
||||
write!(f, "{}", if self.get(Vector2 { x, y }) { '#' } else { '.' })?;
|
||||
}
|
||||
write!(f, "\n")?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -130,7 +163,7 @@ impl Display for InfiniteField2D {
|
|||
pub struct Day20;
|
||||
|
||||
impl AdventOfCode for Day20 {
|
||||
type Input = (Vec<bool>, InfiniteField2D); // TODO: first arg is exactly 512 characters long
|
||||
type Input = (Vec<bool>, Field2D<bool>); // TODO: first arg is exactly 512 characters long
|
||||
type Output = usize;
|
||||
|
||||
fn parse_input(s: &str) -> Self::Input {
|
||||
|
@ -144,7 +177,15 @@ impl AdventOfCode for Day20 {
|
|||
.collect();
|
||||
lines.next();
|
||||
|
||||
(v, InfiniteField2D::from_lines(lines))
|
||||
let field = Field2D {
|
||||
stride: lines.clone().next().unwrap().len(),
|
||||
data: lines
|
||||
.map(|l| l.bytes().map(|c| c == b'#'))
|
||||
.flatten()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
(v, field)
|
||||
}
|
||||
|
||||
fn solve_1(input: &Self::Input) -> Self::Output {
|
||||
|
@ -156,43 +197,69 @@ impl AdventOfCode for Day20 {
|
|||
}
|
||||
}
|
||||
|
||||
fn do_stuff(input: &(Vec<bool>, InfiniteField2D), count: usize) -> usize {
|
||||
fn do_stuff(input: &(Vec<bool>, Field2D<bool>), count: usize) -> usize {
|
||||
debug_assert_eq!(input.0.len(), 512);
|
||||
let should_pad_with_ones = input.0[0];
|
||||
if should_pad_with_ones {
|
||||
debug_assert!(!input.0[511]);
|
||||
}
|
||||
|
||||
let mut field = input.1.clone();
|
||||
const EXTEND: isize = 1;
|
||||
let extra_space_needed = (count * EXTEND as usize * 2) as usize;
|
||||
|
||||
println!("{}", field);
|
||||
let (mut front_buffer, mut back_buffer) = (
|
||||
Field2DWithMovableOrigin::with_capacity(
|
||||
input.1.width() + extra_space_needed,
|
||||
input.1.height() + extra_space_needed,
|
||||
),
|
||||
Field2DWithMovableOrigin::with_capacity(
|
||||
input.1.width() + extra_space_needed,
|
||||
input.1.height() + extra_space_needed,
|
||||
),
|
||||
);
|
||||
|
||||
front_buffer.origin = Vector2 {
|
||||
x: (count as isize) * EXTEND as isize,
|
||||
y: (count as isize) * EXTEND as isize,
|
||||
};
|
||||
back_buffer.origin = front_buffer.origin;
|
||||
|
||||
front_buffer.init_with(&input.1);
|
||||
|
||||
const EXTEND: i16 = 1;
|
||||
for _ in 0..count {
|
||||
let mut new_field = field.clone();
|
||||
for x in (field.min_x - EXTEND)..=(field.max_x + EXTEND) {
|
||||
for y in (field.min_y - EXTEND)..=(field.max_y + EXTEND) {
|
||||
let index = field.read_square_at((x, y).into());
|
||||
for x in (front_buffer.min_x - EXTEND)..=(front_buffer.max_x + EXTEND) {
|
||||
for y in (front_buffer.min_y - EXTEND)..=(front_buffer.max_y + EXTEND) {
|
||||
let index = front_buffer.read_square_at((x, y).into());
|
||||
let new_bit = input.0[index as usize];
|
||||
new_field.insert((x, y).into(), new_bit);
|
||||
back_buffer.insert((x, y).into(), new_bit);
|
||||
}
|
||||
}
|
||||
|
||||
if should_pad_with_ones {
|
||||
new_field.padded_with_ones = !field.padded_with_ones;
|
||||
back_buffer.padded_with_ones = !front_buffer.padded_with_ones;
|
||||
}
|
||||
|
||||
field = new_field;
|
||||
// swap
|
||||
std::mem::swap(&mut front_buffer, &mut back_buffer);
|
||||
}
|
||||
|
||||
debug_assert!(!field.padded_with_ones);
|
||||
field.data.len()
|
||||
debug_assert!(!front_buffer.padded_with_ones);
|
||||
front_buffer.count_ones()
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_read_index() {
|
||||
const INPUT: &str = "#..#.\n#....\n##..#\n..#..\n..###";
|
||||
let field = InfiniteField2D::from_lines(INPUT.lines());
|
||||
println!("{:?}", field);
|
||||
assert_eq!(field.read_square_at(Vector2 { x: 2, y: 2 }), 0b000100010);
|
||||
let lines = INPUT.lines();
|
||||
let field = Field2D {
|
||||
stride: lines.clone().next().unwrap().len(),
|
||||
data: lines
|
||||
.map(|l| l.bytes().map(|c| c == b'#'))
|
||||
.flatten()
|
||||
.collect(),
|
||||
};
|
||||
let mut field2 = Field2DWithMovableOrigin::with_capacity(field.width(), field.height());
|
||||
field2.init_with(&field);
|
||||
println!("{}", field2);
|
||||
assert_eq!(field2.read_square_at(Vector2 { x: 2, y: 2 }), 0b000100010);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче