Compare commits

...

6 Commits

Author SHA1 Message Date
50472363cf cleanup 2023-12-27 12:33:32 -05:00
2c4288de42 day25 2023-12-27 12:28:30 -05:00
14c6da72d9 day24 2023-12-27 10:20:20 -05:00
bdc150d0ed day23 2023-12-27 09:04:34 -05:00
b802cf1d16 day22 2023-12-22 10:31:00 -05:00
9db0e3f054 day21 2023-12-21 15:21:44 -05:00
10 changed files with 798 additions and 5 deletions

62
Cargo.lock generated
View File

@@ -12,6 +12,7 @@ dependencies = [
"itertools",
"num",
"prev-iter",
"rand 0.8.5",
"regex",
"rust-crypto",
"strum",
@@ -68,6 +69,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "either"
version = "1.9.0"
@@ -86,6 +93,17 @@ version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
[[package]]
name = "getrandom"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "heck"
version = "0.4.1"
@@ -195,6 +213,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "prev-iter"
version = "0.1.2"
@@ -242,6 +266,27 @@ dependencies = [
"winapi",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.3.1"
@@ -257,6 +302,15 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rdrand"
version = "0.4.0"
@@ -405,7 +459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
@@ -421,6 +475,12 @@ version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"

View File

@@ -16,4 +16,5 @@ strum = "0.25.0"
strum_macros = "0.25"
prev-iter = "0.1.2"
regex = "1.10.2"
num = "0.4.1"
num = "0.4.1"
rand = "0.8.5"

View File

@@ -47,10 +47,10 @@ fn part2(input: &Vec<String>) -> usize {
if let Some(position) = boxes[box_number].iter().position(|(l, _)| *l == *label) {
let _ = std::mem::replace(
&mut boxes[box_number][position],
(label.clone().to_string(), *focal_length as usize),
(label.to_string(), *focal_length as usize),
);
} else {
boxes[box_number].push((label.clone().to_string(), *focal_length as usize));
boxes[box_number].push((label.to_string(), *focal_length as usize));
}
} else {
panic!("invalid step AHHH {}", step)

View File

@@ -1,5 +1,4 @@
use std::collections::{HashMap, VecDeque};
use aoc_runner_derive::{aoc, aoc_generator};
use num::Integer;

186
src/day21.rs Normal file
View File

@@ -0,0 +1,186 @@
use std::collections::HashSet;
use aoc_runner_derive::{aoc, aoc_generator};
const STEPS: i32 = 64;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
struct Position(i32, i32);
#[derive(Debug, PartialEq)]
struct Garden {
rocks: HashSet<Position>,
start: Position,
extents: (Position, Position),
}
#[aoc_generator(day21, part1)]
fn parse(input: &str) -> Garden {
let mut rocks: HashSet<Position> = HashSet::new();
let mut start: Position = Position(0, 0);
for (row, line) in input.lines().enumerate() {
for (col, ch) in line.chars().enumerate() {
match ch {
'#' => {
rocks.insert(Position(row as i32, col as i32));
}
'S' => {
start = Position(row as i32, col as i32);
}
_ => {}
};
}
}
let mut trans_rocks: HashSet<Position> = HashSet::new();
for rock in rocks.iter() {
let trans_rock = Position(rock.0 - start.0, start.1 - rock.1);
trans_rocks.insert(trans_rock);
}
rocks = trans_rocks;
let num_rows = input.lines().count() as i32;
let num_cols = input.lines().next().unwrap().chars().count() as i32;
let extents = (
Position(-num_rows / 2, -num_cols / 2),
Position(num_rows / 2, num_cols / 2),
);
Garden {
rocks,
start: Position(0, 0),
extents,
}
}
#[aoc(day21, part1)]
fn part1(map: &Garden) -> u32 {
let mut visited: HashSet<Position> = HashSet::new();
visited.insert(map.start);
for _ in 0..STEPS {
let mut new_visited = HashSet::new();
for pos in visited.iter().clone() {
let neighbors = [
Position(pos.0 - 1, pos.1),
Position(pos.0 + 1, pos.1),
Position(pos.0, pos.1 - 1),
Position(pos.0, pos.1 + 1),
]
.into_iter()
.filter(|p| in_bounds(*p, map.extents) && !map.rocks.contains(p));
for neighbor in neighbors {
new_visited.insert(neighbor);
}
}
visited = new_visited;
}
visited.len() as u32
}
fn in_bounds(pos: Position, extents: (Position, Position)) -> bool {
pos.0 >= extents.0 .0 && pos.0 <= extents.1 .0 && pos.1 >= extents.0 .1 && pos.1 < extents.1 .1
}
fn in_bounds_part2(pos: Position, extents: Position) -> bool {
pos.0 >= 0 && pos.0 <= extents.0 && pos.1 >= 0 && pos.1 < extents.1
}
struct GardenPart2 {
rocks: HashSet<Position>,
start: Position,
extents: Position,
}
#[aoc_generator(day21, part2)]
fn parse_part2(input: &str) -> GardenPart2 {
let mut rocks: HashSet<Position> = HashSet::new();
for (row, line) in input.lines().enumerate() {
for (col, ch) in line.chars().enumerate() {
match ch {
'#' => {
rocks.insert(Position(row as i32, col as i32));
}
_ => {}
};
}
}
let num_rows = input.lines().count() as i32;
let num_cols = input.lines().next().unwrap().chars().count() as i32;
let mut expanded_rocks: HashSet<Position> = HashSet::new();
for rock in rocks.iter() {
for row_mul in 0..5 {
for col_mul in 0..5 {
let expanded_rock = Position(
rock.0 + (num_rows * row_mul as i32),
rock.1 + (num_cols * col_mul as i32),
);
expanded_rocks.insert(expanded_rock);
}
}
}
GardenPart2 {
rocks: expanded_rocks,
start: Position(num_rows * 5 / 2, num_cols * 5 / 2),
extents: Position(num_rows * 5, num_cols * 5),
}
}
#[aoc(day21, part2)]
fn part2(map: &GardenPart2) -> i64 {
let b0: i64 = walk(&map, 65) as i64;
let b1: i64 = walk(&map, 65 + 131) as i64;
let b2: i64 = walk(&map, 65 + 2 * 131) as i64;
let n: i64 = 202300;
// below uses Cramer's Rule to solve for x0, x1, x2
let det_a: f64 = -2.0;
let det_a0: f64 = -b0 as f64 + 2.0 * b1 as f64 - b2 as f64;
let det_a1: f64 = 3.0 * b0 as f64 - 4.0 * b1 as f64 + b2 as f64;
let det_a2: f64 = -2.0 * b0 as f64;
let x0: i64 = (det_a0 / det_a) as i64;
let x1: i64 = (det_a1 / det_a) as i64;
let x2: i64 = (det_a2 / det_a) as i64;
x0 * n * n + x1 * n + x2
}
fn walk(garden: &GardenPart2, steps: u32) -> u32 {
let mut visited: HashSet<Position> = HashSet::new();
visited.insert(garden.start);
for _ in 0..steps {
let mut new_visited = HashSet::new();
for pos in visited.iter().clone() {
let neighbors = [
Position(pos.0 - 1, pos.1),
Position(pos.0 + 1, pos.1),
Position(pos.0, pos.1 - 1),
Position(pos.0, pos.1 + 1),
]
.into_iter()
.filter(|p| in_bounds_part2(*p, garden.extents) && !garden.rocks.contains(p));
for neighbor in neighbors {
new_visited.insert(neighbor);
}
}
visited = new_visited;
}
visited.len() as u32
}
#[cfg(test)]
mod tests {
use super::*;
const EX: &str = r"...........
.....###.#.
.###.##..#.
..#.#...#..
....#.#....
.##..S####.
.##..#...#.
.......##..
.##.#.####.
.##..##.##.
...........";
#[test]
fn part1_example() {
assert_eq!(part1(&parse(EX)), 16);
}
}

133
src/day22.rs Normal file
View File

@@ -0,0 +1,133 @@
use std::collections::VecDeque;
use aoc_runner_derive::{aoc, aoc_generator};
use regex::Regex;
pub struct Brick {
up: Vec<Vec<usize>>,
down: Vec<Vec<usize>>,
}
#[aoc_generator(day22)]
fn parse(input: &str) -> Brick {
let re: Regex = Regex::new(r"\d+").unwrap();
let mut bricks: Vec<[usize; 6]> = input.lines().map(|line| {
re.captures_iter(line)
.map(|c| c.extract::<0>().0.parse::<usize>().unwrap())
.collect::<Vec<_>>().try_into().unwrap()
}).collect::<Vec<_>>();
let mut heights = [[0; 10]; 10];
let mut indices = [[usize::MAX; 10]; 10];
let mut up = vec![Vec::new(); bricks.len()];
let mut down = vec![Vec::new(); bricks.len()];
bricks.sort_unstable_by_key(|b| b[2]);
for (i, &[x1, y1, z1, x2, y2, z2]) in bricks.iter().enumerate() {
let height = z2 - z1 + 1;
let mut top = 0;
let mut previous = usize::MAX;
for x in x1..=x2 {
for y in y1..=y2 {
top = top.max(heights[x][y]);
}
}
for x in x1..=x2 {
for y in y1..=y2 {
if heights[x][y] == top {
let index = indices[x][y];
if index != previous {
up[index].push(i);
down[i].push(index);
previous = index;
}
}
heights[x][y] = top + height;
indices[x][y] = i;
}
}
}
Brick { up , down }
}
#[aoc(day22, part1)]
fn part1(input: &Brick) -> usize {
let Brick { down, .. } = input;
let mut safe = vec![true; down.len()];
for underneath in down {
if underneath.len() == 1 {
safe[underneath[0]] = false;
}
}
safe.iter().filter(|&&b| b).count()
}
#[aoc(day22, part2)]
fn part2(input: &Brick) -> usize {
let Brick { up, down } = input;
let mut safe = vec![true; down.len()];
for underneath in down {
if underneath.len() == 1 {
safe[underneath[0]] = false;
}
}
let mut result = 0;
let mut todo = VecDeque::new();
let mut removed = vec![usize::MAX; down.len()];
for (start, &safe) in safe.iter().enumerate() {
if safe {
continue;
}
todo.push_back(start);
removed[start] = start;
while let Some(current) = todo.pop_front() {
for &next in &up[current] {
if removed[next] != start && down[next].iter().all(|&i| removed[i] == start) {
result += 1;
removed[next] = start;
todo.push_back(next);
}
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
const EX: &str = r"1,0,1~1,2,1
0,0,2~2,0,2
0,2,3~2,2,3
0,0,4~0,2,4
2,0,5~2,2,5
0,1,6~2,1,6
1,1,8~1,1,9";
#[test]
fn part1_example() {
assert_eq!(part1(&parse(EX)), 5);
}
#[test]
fn part2_example() {
assert_eq!(part2(&parse(EX)), 7);
}
}

156
src/day23.rs Normal file
View File

@@ -0,0 +1,156 @@
use std::collections::HashMap;
use aoc_runner_derive::{aoc, aoc_generator};
const NEIGHBORS: &[(i64,i64)] = &[(-1,0),(0,1),(1,0),(0,-1)];
use itertools::Itertools;
#[aoc_generator(day23)]
fn parse(input: &str) -> Vec<Vec<char>> {
input.lines().map(|x| x.chars().collect::<Vec<_>>()).collect::<Vec<_>>()
}
fn dfs(graph: &HashMap<(usize,usize), Vec<(usize,usize,usize)>>, seen: &mut Vec<Vec<bool>>, (r,c): (usize, usize)) -> Option<usize> {
if r == seen.len() - 1 {
return Some(0);
}
let mut max_dist = None;
for &(rr, cc, d) in &graph[&(r,c)] {
if !seen[rr][cc] {
seen[rr][cc] = true;
if let Some(dist) = dfs(graph, seen, (rr,cc)) {
max_dist = Some(max_dist.unwrap_or(0).max(d+dist))
}
seen[rr][cc] = false;
}
}
max_dist
}
#[aoc(day23, part1)]
fn part1(grid: &Vec<Vec<char>>) -> usize {
let mut graph = HashMap::<_,Vec<_>>::new();
for (r, c) in (0..grid.len()).cartesian_product(0..grid[0].len()) {
let neighbors = match grid[r][c] {
'#' => continue,
'.' => NEIGHBORS,
'^' => &NEIGHBORS[0..][..1],
'>' => &NEIGHBORS[1..][..1],
'v' => &NEIGHBORS[2..][..1],
'<' => &NEIGHBORS[3..][..1],
_ => unreachable!(),
};
let e = graph.entry((r,c)).or_default();
for (dr, dc) in neighbors {
let rr = (r as i64 + dr) as usize;
let cc = (c as i64 + dc) as usize;
if grid.get(rr).and_then(|row| row.get(cc)).is_some_and(|&t| t != '#') {
e.push((rr,cc,1));
}
}
}
let corridors = graph.iter()
.filter(|(_, n)| n.len() == 2)
.map(|(&node, _)| node)
.collect::<Vec<_>>();
for (r,c) in corridors {
let neighbors = graph.remove(&(r,c)).unwrap();
let (r1, c1, d1) = neighbors[0];
let (r2, c2, d2) = neighbors[1];
let n1 = graph.get_mut(&(r1,c1)).unwrap();
if let Some(i) = n1.iter().position(|&(rr,cc,_)| (rr,cc) == (r,c)) {
n1[i] = (r2,c2,d1+d2);
}
let n2 = graph.get_mut(&(r2,c2)).unwrap();
if let Some(i) = n2.iter().position(|&(rr,cc,_)| (rr,cc) == (r,c)) {
n2[i] = (r1,c1,d1+d2);
}
}
dfs(&graph, &mut vec![vec![false; grid[0].len()]; grid.len()], (0,1)).unwrap()
}
#[aoc(day23, part2)]
fn part2(grid: &Vec<Vec<char>>) -> usize {
let mut graph = HashMap::<_,Vec<_>>::new();
for (r, c) in (0..grid.len()).cartesian_product(0..grid[0].len()) {
let neighbors = match grid[r][c] {
'#' => continue,
_ => NEIGHBORS,
};
let e = graph.entry((r,c)).or_default();
for (dr, dc) in neighbors {
let rr = (r as i64 + dr) as usize;
let cc = (c as i64 + dc) as usize;
if grid.get(rr).and_then(|row| row.get(cc)).is_some_and(|&t| t != '#') {
e.push((rr,cc,1));
}
}
}
let corridors = graph.iter()
.filter(|(_, n)| n.len() == 2)
.map(|(&node, _)| node)
.collect::<Vec<_>>();
for (r,c) in corridors {
let neighbors = graph.remove(&(r,c)).unwrap();
let (r1, c1, d1) = neighbors[0];
let (r2, c2, d2) = neighbors[1];
let n1 = graph.get_mut(&(r1,c1)).unwrap();
if let Some(i) = n1.iter().position(|&(rr,cc,_)| (rr,cc) == (r,c)) {
n1[i] = (r2,c2,d1+d2);
}
let n2 = graph.get_mut(&(r2,c2)).unwrap();
if let Some(i) = n2.iter().position(|&(rr,cc,_)| (rr,cc) == (r,c)) {
n2[i] = (r1,c1,d1+d2);
}
}
dfs(&graph, &mut vec![vec![false; grid[0].len()]; grid.len()], (0,1)).unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
const EX: &str = r"#.#####################
#.......#########...###
#######.#########.#.###
###.....#.>.>.###.#.###
###v#####.#v#.###.#.###
###.>...#.#.#.....#...#
###v###.#.#.#########.#
###...#.#.#.......#...#
#####.#.#.#######.#.###
#.....#.#.#.......#...#
#.#####.#.#.#########v#
#.#...#...#...###...>.#
#.#.#v#######v###.###v#
#...#.>.#...>.>.#.###.#
#####v#.#.###v#.#.###.#
#.....#...#...#.#.#...#
#.#########.###.#.#.###
#...###...#...#...#.###
###.###.#.###v#####v###
#...#...#.#.>.>.#.>.###
#.###.###.#.###.#.#v###
#.....###...###...#...#
#####################.#";
#[test]
fn part1_example() {
assert_eq!(part1(&parse(EX)), 94);
}
#[test]
fn part2_example() {
assert_eq!(part2(&parse(EX)), 154);
}
}

144
src/day24.rs Normal file
View File

@@ -0,0 +1,144 @@
use std::ops::RangeInclusive;
use aoc_runner_derive::{aoc, aoc_generator};
//const RANGE: RangeInclusive<f64> = 7.0..=27.0;
const RANGE: RangeInclusive<f64> = 200_000_000_000_000.0..=400_000_000_000_000.0;
#[aoc_generator(day24)]
fn parse(input: &str) -> Vec<[f64; 6]> {
let input: Vec<[f64; 6]> = input.lines()
.map(|line| {
line.split("@")
.flat_map(|components| {
components.split(",")
.map(str::trim)
.map(|num| num.parse::<f64>().unwrap())
})
.collect::<Vec<_>>().try_into().unwrap()
}).collect::<Vec<_>>();
input
}
#[aoc(day24, part1)]
fn part1(input: &[[f64; 6]]) -> u32 {
let mut total = 0;
// It's just solve the linear equations. I'm using a matrix.
for i in 1..input.len() {
for j in 0..i {
let [a, b, _, d, e, _] = input[i];
let [g, h, _, j, k, _] = input[j];
let determinant = e * j - d * k;
if determinant == 0.0 {
continue;
}
let t = (j * (h - b) - k * (g - a)) / determinant;
let u = (d * (h - b) - e * (g - a)) / determinant;
let x = a + t * d;
let y = b + t * e;
if t >= 0.0 && u >= 0.0 && RANGE.contains(&x) && RANGE.contains(&y) {
total += 1;
}
}
}
total
}
#[aoc(day24, part2)]
fn part2(input: &[[f64; 6]]) -> f64 {
let [a, b, c, d, e, f] = input[0];
let [g, h, i, j, k, l] = input[1];
let [m, n, o, p, q, r] = input[2];
let mut matrix = [
[0.0, l - f, e - k, 0.0, c - i, h - b, e * c - b * f + h * l - k * i],
[0.0, r - f, e - q, 0.0, c - o, n - b, e * c - b * f + n * r - q * o],
[f - l, 0.0, j - d, i - c, 0.0, a - g, a * f - d * c + j * i - g * l],
[f - r, 0.0, p - d, o - c, 0.0, a - m, a * f - d * c + p * o - m * r],
[k - e, d - j, 0.0, b - h, g - a, 0.0, d * b - a * e + g * k - j * h],
[q - e, d - p, 0.0, b - n, m - a, 0.0, d * b - a * e + m * q - p * n],
];
// Use Gaussian elimination to solve for the 6 unknowns.
// Forward elimination, processing columns from left to right.
// This will leave a matrix in row echelon form.
// That's right, I got a D in applied linear algebra.
for pivot in 0..6 {
// Make leading coefficient of each row positive to make subsequent calculations easier.
for row in &mut matrix[pivot..] {
if row[pivot] < 0.0 {
row.iter_mut().for_each(|num| *num = -*num);
}
}
loop {
let column = matrix.map(|row| row[pivot]);
// If only one non-zero coefficient remaining in the column then we're done.
if column[pivot..].iter().filter(|&&c| c > 0.0).count() == 1 {
// Move this row into the pivot location
let index = column.iter().rposition(|&c| c > 0.0).unwrap();
matrix.swap(pivot, index);
break;
}
// Find the row with the lowest non-zero leading coefficient.
let min = *column[pivot..].iter().filter(|&&c| c > 0.0).min_by(|a, b| a.total_cmp(b)).unwrap();
let index = column.iter().rposition(|&c| c == min).unwrap();
// Subtract as many multiples of this minimum row from each other row as possible
// to shrink the coefficients of our column towards zero.
for row in pivot..6 {
if row != index && column[row] != 0.0 {
let factor = column[row] / min;
for col in pivot..7 {
matrix[row][col] -= factor * matrix[index][col];
}
}
}
}
}
// Back substitution, processing columns from right to left.
// This will leave the matrix in reduced row echelon form.
// The solved unknowns are then in the 7th column.
for pivot in (0..6).rev() {
matrix[pivot][6] /= matrix[pivot][pivot];
for row in 0..pivot {
matrix[row][6] -= matrix[pivot][6] * matrix[row][pivot];
}
}
(matrix[0][6] + matrix[1][6] + matrix[2][6]).floor()
}
#[cfg(test)]
mod tests {
use super::*;
const EX: &str = r"19, 13, 30 @ -2, 1, -2
18, 19, 22 @ -1, -1, -2
20, 25, 34 @ -2, -2, -4
12, 31, 28 @ -1, -2, -1
20, 19, 15 @ 1, -5, -3";
#[test]
fn part1_example() {
assert_eq!(part1(&parse(EX)), 2);
}
#[test]
fn part2_example() {
assert_eq!(part2(&parse(EX)), 47.0);
}
}

109
src/day25.rs Normal file
View File

@@ -0,0 +1,109 @@
use std::collections::{HashSet, HashMap};
use rand::Rng;
use aoc_runner_derive::{aoc, aoc_generator};
#[derive(Debug, Clone)]
struct Graph {
edges: Vec<(String, String)>,
vertex_count: usize
}
impl Graph {
fn cut(&mut self) -> (usize, usize, usize) {
let mut contracted_edges = self.edges.clone();
let mut contracted_vertex_count = self.vertex_count;
let mut contracted: HashMap<String, Vec<String>> = HashMap::new();
while contracted_vertex_count > 2 {
let random = rand::thread_rng().gen_range(0..contracted_edges.len());
let edge_to_contract = contracted_edges[random].clone();
if contracted.contains_key(&edge_to_contract.0) {
contracted.get_mut(&edge_to_contract.0.clone()).unwrap().push(edge_to_contract.1.clone());
} else {
contracted.insert(edge_to_contract.0.clone(), vec![edge_to_contract.1.clone()]);
}
if contracted.contains_key(&edge_to_contract.1) {
let mut to_append = contracted.clone().get_mut(&edge_to_contract.1).unwrap().clone();
contracted.get_mut(&edge_to_contract.0).unwrap().append(&mut to_append);
contracted.remove(&edge_to_contract.1);
}
let mut new_edges: Vec<(String, String)> = vec![];
for edge in contracted_edges {
if edge.1 == edge_to_contract.1 {
new_edges.push((edge.0.clone(), edge_to_contract.0.clone()));
} else if edge.0 == edge_to_contract.1 {
new_edges.push((edge_to_contract.0.clone(), edge.1.clone()));
} else {
new_edges.push(edge.clone());
}
}
let tmp = new_edges.iter().cloned().filter(|x| x.0 != x.1).collect::<Vec<_>>();
contracted_edges = tmp;
contracted_vertex_count -= 1;
}
let counts = contracted.iter().map(|x| x.1.len() + 1).collect::<Vec<_>>();
return (contracted_edges.len(), counts[0], *counts.last().unwrap());
}
}
#[aoc_generator(day25)]
fn parse(input: &str) -> Graph {
let input = input.lines().flat_map(|line| {
let line = line.split(' ').map(|x| x.trim_end_matches(':').to_string()).collect::<Vec<_>>();
line[1..].iter().cloned().map(|x| (line[0].clone(), x)).collect::<Vec<_>>()
}).collect::<Vec<_>>();
let vertex_count = input.clone().into_iter().flat_map(|x| vec![x.0, x.1]).collect::<HashSet<_>>().len();
Graph {
edges: input,
vertex_count
}
}
#[aoc(day25, part1)]
fn part1(graph: &Graph) -> usize {
let graph = &mut graph.clone();
let mut min_cut = usize::MAX;
let mut count1 = 0;
let mut count2 = 0;
while min_cut != 3 {
(min_cut, count1, count2) = graph.cut();
}
count1 * count2
}
#[cfg(test)]
mod tests {
use super::*;
const EX: &str = r"jqt: rhn xhk nvd
rsh: frs pzl lsr
xhk: hfx
cmg: qnr nvd lhk bvb
rhn: xhk bvb hfx
bvb: xhk hfx
pzl: lsr hfx nvd
qnr: nvd
ntq: jqt hfx bvb xhk
nvd: lhk
lsr: lhk
rzs: qnr cmg lsr rsh
frs: qnr lhk lsr";
#[test]
fn part1_example() {
assert_eq!(part1(&parse(EX)), 54);
}
}

View File

@@ -1,3 +1,8 @@
mod day25;
mod day24;
mod day23;
mod day22;
mod day21;
mod day20;
mod day19;
mod day18;