├── .gitignore ├── aoc2023 ├── .gitignore ├── inputs │ └── .gitignore ├── .env ├── src │ ├── day07 │ │ ├── mod.rs │ │ ├── part1.rs │ │ └── part2.rs │ ├── lib.rs │ ├── day01 │ │ └── mod.rs │ ├── day09 │ │ └── mod.rs │ ├── day04 │ │ └── mod.rs │ ├── day05 │ │ └── mod.rs │ ├── day02 │ │ └── mod.rs │ ├── grid.rs │ ├── day15 │ │ └── mod.rs │ ├── day03 │ │ └── mod.rs │ ├── inputs.rs │ ├── day08 │ │ └── mod.rs │ ├── day11 │ │ └── mod.rs │ ├── day06 │ │ └── mod.rs │ ├── day17 │ │ └── mod.rs │ ├── main.rs │ ├── day18 │ │ └── mod.rs │ ├── day14 │ │ └── mod.rs │ ├── day10 │ │ └── mod.rs │ ├── day16 │ │ └── mod.rs │ └── day19 │ │ └── mod.rs ├── benches │ ├── day05.rs │ ├── day01.rs │ ├── day02.rs │ ├── day03.rs │ ├── day04.rs │ ├── day06.rs │ ├── day07.rs │ ├── day08.rs │ ├── day09.rs │ ├── day10.rs │ └── day11.rs ├── Cargo.toml └── Cargo.lock ├── README.md ├── .build.yml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /aoc2023/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | -------------------------------------------------------------------------------- /aoc2023/inputs/.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | -------------------------------------------------------------------------------- /aoc2023/.env: -------------------------------------------------------------------------------- 1 | AOC_SESSION=53616c7465645f5fc81a60be8349eacacdde3194dfc517dddfeb582e7c969349d06f0cfcd9b58fc2ace34d2f15011895ed5de7c62f91e1036134d3b7937f2194 2 | -------------------------------------------------------------------------------- /aoc2023/src/day07/mod.rs: -------------------------------------------------------------------------------- 1 | mod part1; 2 | mod part2; 3 | 4 | pub use part1::part1; 5 | pub use part2::part2; 6 | 7 | pub fn run(input: &str) -> (Option, Option) { 8 | (Some(part1(input)), Some(part2(input))) 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AoC 2 | 3 | [![builds.sr.ht status](https://builds.sr.ht/~hamaluik/aoc/commits/main/.build.yml.svg)](https://builds.sr.ht/~hamaluik/aoc/commits/main/.build.yml?) 4 | 5 | My [advent of code](https://adventofcode.com/) adventures. 6 | 7 | Main repo: https://git.sr.ht/~hamaluik/aoc (GitHub is a mirror) 8 | 9 | -------------------------------------------------------------------------------- /aoc2023/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod grid; 2 | 3 | pub mod day01; 4 | pub mod day02; 5 | pub mod day03; 6 | pub mod day04; 7 | pub mod day05; 8 | pub mod day06; 9 | pub mod day07; 10 | pub mod day08; 11 | pub mod day09; 12 | pub mod day10; 13 | pub mod day11; 14 | pub mod day14; 15 | pub mod day15; 16 | pub mod day16; 17 | pub mod day17; 18 | pub mod day18; 19 | pub mod day19; 20 | -------------------------------------------------------------------------------- /.build.yml: -------------------------------------------------------------------------------- 1 | # sr.ht tests 2 | image: archlinux 3 | packages: 4 | - rustup 5 | sources: 6 | - https://git.sr.ht/~hamaluik/aoc 7 | tasks: 8 | - setup: | 9 | rustup set profile minimal 10 | rustup default stable 11 | - version: | 12 | rustc -V 13 | - test: | 14 | cd aoc/aoc2023 15 | cargo test 16 | triggers: 17 | - action: email 18 | condition: failure 19 | to: kenton@hamaluik.ca 20 | -------------------------------------------------------------------------------- /aoc2023/benches/day05.rs: -------------------------------------------------------------------------------- 1 | use aoc2023::day05::part1; 2 | use criterion::BenchmarkId; 3 | use criterion::Criterion; 4 | use criterion::{criterion_group, criterion_main}; 5 | 6 | fn from_elem(c: &mut Criterion) { 7 | let path = std::path::PathBuf::from("inputs").join("day05.txt"); 8 | let input = std::fs::read_to_string(&path).expect("can read input"); 9 | let input = input.as_str(); 10 | 11 | c.bench_with_input( 12 | BenchmarkId::new("Part 1", path.display()), 13 | &input, 14 | |b, &s| { 15 | b.iter(|| part1(s)); 16 | }, 17 | ); 18 | } 19 | 20 | criterion_group!(benches, from_elem); 21 | criterion_main!(benches); 22 | -------------------------------------------------------------------------------- /aoc2023/benches/day01.rs: -------------------------------------------------------------------------------- 1 | use aoc2023::day01::{part1, part2}; 2 | use criterion::BenchmarkId; 3 | use criterion::Criterion; 4 | use criterion::{criterion_group, criterion_main}; 5 | 6 | fn from_elem(c: &mut Criterion) { 7 | let path = std::path::PathBuf::from("inputs").join("day01.txt"); 8 | let input = std::fs::read_to_string(&path).expect("can read input"); 9 | let input = input.as_str(); 10 | 11 | c.bench_with_input( 12 | BenchmarkId::new("Part 1", path.display()), 13 | &input, 14 | |b, &s| { 15 | b.iter(|| part1(s)); 16 | }, 17 | ); 18 | c.bench_with_input( 19 | BenchmarkId::new("Part 2", path.display()), 20 | &input, 21 | |b, &s| { 22 | b.iter(|| part2(s)); 23 | }, 24 | ); 25 | } 26 | 27 | criterion_group!(benches, from_elem); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /aoc2023/benches/day02.rs: -------------------------------------------------------------------------------- 1 | use aoc2023::day02::{part1, part2}; 2 | use criterion::BenchmarkId; 3 | use criterion::Criterion; 4 | use criterion::{criterion_group, criterion_main}; 5 | 6 | fn from_elem(c: &mut Criterion) { 7 | let path = std::path::PathBuf::from("inputs").join("day02.txt"); 8 | let input = std::fs::read_to_string(&path).expect("can read input"); 9 | let input = input.as_str(); 10 | 11 | c.bench_with_input( 12 | BenchmarkId::new("Part 1", path.display()), 13 | &input, 14 | |b, &s| { 15 | b.iter(|| part1(s)); 16 | }, 17 | ); 18 | c.bench_with_input( 19 | BenchmarkId::new("Part 2", path.display()), 20 | &input, 21 | |b, &s| { 22 | b.iter(|| part2(s)); 23 | }, 24 | ); 25 | } 26 | 27 | criterion_group!(benches, from_elem); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /aoc2023/benches/day03.rs: -------------------------------------------------------------------------------- 1 | use aoc2023::day03::{part1, part2}; 2 | use criterion::BenchmarkId; 3 | use criterion::Criterion; 4 | use criterion::{criterion_group, criterion_main}; 5 | 6 | fn from_elem(c: &mut Criterion) { 7 | let path = std::path::PathBuf::from("inputs").join("day03.txt"); 8 | let input = std::fs::read_to_string(&path).expect("can read input"); 9 | let input = input.as_str(); 10 | 11 | c.bench_with_input( 12 | BenchmarkId::new("Part 1", path.display()), 13 | &input, 14 | |b, &s| { 15 | b.iter(|| part1(s)); 16 | }, 17 | ); 18 | c.bench_with_input( 19 | BenchmarkId::new("Part 2", path.display()), 20 | &input, 21 | |b, &s| { 22 | b.iter(|| part2(s)); 23 | }, 24 | ); 25 | } 26 | 27 | criterion_group!(benches, from_elem); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /aoc2023/benches/day04.rs: -------------------------------------------------------------------------------- 1 | use aoc2023::day04::{part1, part2}; 2 | use criterion::BenchmarkId; 3 | use criterion::Criterion; 4 | use criterion::{criterion_group, criterion_main}; 5 | 6 | fn from_elem(c: &mut Criterion) { 7 | let path = std::path::PathBuf::from("inputs").join("day04.txt"); 8 | let input = std::fs::read_to_string(&path).expect("can read input"); 9 | let input = input.as_str(); 10 | 11 | c.bench_with_input( 12 | BenchmarkId::new("Part 1", path.display()), 13 | &input, 14 | |b, &s| { 15 | b.iter(|| part1(s)); 16 | }, 17 | ); 18 | c.bench_with_input( 19 | BenchmarkId::new("Part 2", path.display()), 20 | &input, 21 | |b, &s| { 22 | b.iter(|| part2(s)); 23 | }, 24 | ); 25 | } 26 | 27 | criterion_group!(benches, from_elem); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /aoc2023/benches/day06.rs: -------------------------------------------------------------------------------- 1 | use aoc2023::day06::{part1, part2}; 2 | use criterion::BenchmarkId; 3 | use criterion::Criterion; 4 | use criterion::{criterion_group, criterion_main}; 5 | 6 | fn from_elem(c: &mut Criterion) { 7 | let path = std::path::PathBuf::from("inputs").join("day06.txt"); 8 | let input = std::fs::read_to_string(&path).expect("can read input"); 9 | let input = input.as_str(); 10 | 11 | c.bench_with_input( 12 | BenchmarkId::new("Part 1", path.display()), 13 | &input, 14 | |b, &s| { 15 | b.iter(|| part1(s)); 16 | }, 17 | ); 18 | c.bench_with_input( 19 | BenchmarkId::new("Part 2", path.display()), 20 | &input, 21 | |b, &s| { 22 | b.iter(|| part2(s)); 23 | }, 24 | ); 25 | } 26 | 27 | criterion_group!(benches, from_elem); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /aoc2023/benches/day07.rs: -------------------------------------------------------------------------------- 1 | use aoc2023::day07::{part1, part2}; 2 | use criterion::BenchmarkId; 3 | use criterion::Criterion; 4 | use criterion::{criterion_group, criterion_main}; 5 | 6 | fn from_elem(c: &mut Criterion) { 7 | let path = std::path::PathBuf::from("inputs").join("day07.txt"); 8 | let input = std::fs::read_to_string(&path).expect("can read input"); 9 | let input = input.as_str(); 10 | 11 | c.bench_with_input( 12 | BenchmarkId::new("Part 1", path.display()), 13 | &input, 14 | |b, &s| { 15 | b.iter(|| part1(s)); 16 | }, 17 | ); 18 | c.bench_with_input( 19 | BenchmarkId::new("Part 2", path.display()), 20 | &input, 21 | |b, &s| { 22 | b.iter(|| part2(s)); 23 | }, 24 | ); 25 | } 26 | 27 | criterion_group!(benches, from_elem); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /aoc2023/benches/day08.rs: -------------------------------------------------------------------------------- 1 | use aoc2023::day08::{part1, part2}; 2 | use criterion::BenchmarkId; 3 | use criterion::Criterion; 4 | use criterion::{criterion_group, criterion_main}; 5 | 6 | fn from_elem(c: &mut Criterion) { 7 | let path = std::path::PathBuf::from("inputs").join("day08.txt"); 8 | let input = std::fs::read_to_string(&path).expect("can read input"); 9 | let input = input.as_str(); 10 | 11 | c.bench_with_input( 12 | BenchmarkId::new("Part 1", path.display()), 13 | &input, 14 | |b, &s| { 15 | b.iter(|| part1(s)); 16 | }, 17 | ); 18 | c.bench_with_input( 19 | BenchmarkId::new("Part 2", path.display()), 20 | &input, 21 | |b, &s| { 22 | b.iter(|| part2(s)); 23 | }, 24 | ); 25 | } 26 | 27 | criterion_group!(benches, from_elem); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /aoc2023/benches/day09.rs: -------------------------------------------------------------------------------- 1 | use aoc2023::day09::{part1, part2}; 2 | use criterion::BenchmarkId; 3 | use criterion::Criterion; 4 | use criterion::{criterion_group, criterion_main}; 5 | 6 | fn from_elem(c: &mut Criterion) { 7 | let path = std::path::PathBuf::from("inputs").join("day09.txt"); 8 | let input = std::fs::read_to_string(&path).expect("can read input"); 9 | let input = input.as_str(); 10 | 11 | c.bench_with_input( 12 | BenchmarkId::new("Part 1", path.display()), 13 | &input, 14 | |b, &s| { 15 | b.iter(|| part1(s)); 16 | }, 17 | ); 18 | c.bench_with_input( 19 | BenchmarkId::new("Part 2", path.display()), 20 | &input, 21 | |b, &s| { 22 | b.iter(|| part2(s)); 23 | }, 24 | ); 25 | } 26 | 27 | criterion_group!(benches, from_elem); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /aoc2023/benches/day10.rs: -------------------------------------------------------------------------------- 1 | use aoc2023::day10::{part1, part2}; 2 | use criterion::BenchmarkId; 3 | use criterion::Criterion; 4 | use criterion::{criterion_group, criterion_main}; 5 | 6 | fn from_elem(c: &mut Criterion) { 7 | let path = std::path::PathBuf::from("inputs").join("day10.txt"); 8 | let input = std::fs::read_to_string(&path).expect("can read input"); 9 | let input = input.as_str(); 10 | 11 | c.bench_with_input( 12 | BenchmarkId::new("Part 1", path.display()), 13 | &input, 14 | |b, &s| { 15 | b.iter(|| part1(s)); 16 | }, 17 | ); 18 | c.bench_with_input( 19 | BenchmarkId::new("Part 2", path.display()), 20 | &input, 21 | |b, &s| { 22 | b.iter(|| part2(s)); 23 | }, 24 | ); 25 | } 26 | 27 | criterion_group!(benches, from_elem); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /aoc2023/benches/day11.rs: -------------------------------------------------------------------------------- 1 | use aoc2023::day11::{part1, part2}; 2 | use criterion::BenchmarkId; 3 | use criterion::Criterion; 4 | use criterion::{criterion_group, criterion_main}; 5 | 6 | fn from_elem(c: &mut Criterion) { 7 | let path = std::path::PathBuf::from("inputs").join("day11.txt"); 8 | let input = std::fs::read_to_string(&path).expect("can read input"); 9 | let input = input.as_str(); 10 | 11 | c.bench_with_input( 12 | BenchmarkId::new("Part 1", path.display()), 13 | &input, 14 | |b, &s| { 15 | b.iter(|| part1(s)); 16 | }, 17 | ); 18 | c.bench_with_input( 19 | BenchmarkId::new("Part 2", path.display()), 20 | &input, 21 | |b, &s| { 22 | b.iter(|| part2(s, 1000000)); 23 | }, 24 | ); 25 | } 26 | 27 | criterion_group!(benches, from_elem); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /aoc2023/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc2023" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "aoc2023" 8 | 9 | [[bin]] 10 | name = "aoc2023" 11 | 12 | [dependencies] 13 | rayon = "1" 14 | regex = "1" 15 | anyhow = "1" 16 | termcolor = "1" 17 | itertools = "0.12.0" 18 | nom = "7.1.3" 19 | chrono = "0.4.31" 20 | dotenv = "0.15.0" 21 | dialoguer = "0.11.0" 22 | ureq = { version = "2.9.1", features = ["cookies", "gzip"] } 23 | 24 | [dev-dependencies] 25 | criterion = "0.4" 26 | 27 | [[bench]] 28 | harness = false 29 | name = "day01" 30 | 31 | [[bench]] 32 | harness = false 33 | name = "day02" 34 | 35 | [[bench]] 36 | harness = false 37 | name = "day03" 38 | 39 | [[bench]] 40 | harness = false 41 | name = "day04" 42 | 43 | [[bench]] 44 | harness = false 45 | name = "day05" 46 | 47 | [[bench]] 48 | harness = false 49 | name = "day06" 50 | 51 | [[bench]] 52 | harness = false 53 | name = "day07" 54 | 55 | [[bench]] 56 | harness = false 57 | name = "day08" 58 | 59 | [[bench]] 60 | harness = false 61 | name = "day09" 62 | 63 | [[bench]] 64 | harness = false 65 | name = "day10" 66 | 67 | [[bench]] 68 | harness = false 69 | name = "day11" 70 | 71 | -------------------------------------------------------------------------------- /aoc2023/src/day01/mod.rs: -------------------------------------------------------------------------------- 1 | fn is_digit(c: &char) -> bool { 2 | *c >= '0' && *c <= '9' 3 | } 4 | 5 | fn parse_digit(c: char) -> usize { 6 | c as usize - '0' as usize 7 | } 8 | 9 | pub fn part1(input: &str) -> usize { 10 | input 11 | .lines() 12 | .filter_map(|l| { 13 | let a = l.chars().find(is_digit); 14 | let b = l.chars().rev().find(is_digit); 15 | match (a, b) { 16 | (Some(a), Some(b)) => Some((parse_digit(a) * 10) + parse_digit(b)), 17 | _ => None, 18 | } 19 | }) 20 | .sum() 21 | } 22 | 23 | const NUMBERS: [(&'static str, usize); 19] = [ 24 | ("0", 0), 25 | ("1", 1), 26 | ("2", 2), 27 | ("3", 3), 28 | ("4", 4), 29 | ("5", 5), 30 | ("6", 6), 31 | ("7", 7), 32 | ("8", 8), 33 | ("9", 9), 34 | ("one", 1), 35 | ("two", 2), 36 | ("three", 3), 37 | ("four", 4), 38 | ("five", 5), 39 | ("six", 6), 40 | ("seven", 7), 41 | ("eight", 8), 42 | ("nine", 9), 43 | ]; 44 | 45 | fn find_number_fwd(line: &str) -> Option { 46 | NUMBERS 47 | .iter() 48 | .filter_map(|&(word, value)| line.find(word).map(|p| (p, word, value))) 49 | .min_by_key(|x| x.0) 50 | .map(|x| x.2) 51 | } 52 | 53 | fn find_number_rev(line: &str) -> Option { 54 | NUMBERS 55 | .iter() 56 | .filter_map(|&(word, value)| line.rfind(word).map(|p| (p, word, value))) 57 | .max_by_key(|x| x.0) 58 | .map(|x| x.2) 59 | } 60 | 61 | pub fn part2(input: &str) -> usize { 62 | input 63 | .lines() 64 | .filter_map(|l| { 65 | let left = find_number_fwd(l); 66 | let right = find_number_rev(l); 67 | match (left, right) { 68 | (Some(left), Some(right)) => Some(left * 10 + right), 69 | _ => None, 70 | } 71 | }) 72 | .sum() 73 | } 74 | 75 | pub fn run(input: &str) -> (Option, Option) { 76 | (Some(part1(input)), Some(part2(input))) 77 | } 78 | 79 | #[cfg(test)] 80 | mod test { 81 | use super::*; 82 | const SAMPLE1: &'static str = r#"1abc2 83 | pqr3stu8vwx 84 | a1b2c3d4e5f 85 | treb7uchet"#; 86 | const SAMPLE2: &'static str = r#"two1nine 87 | eightwothree 88 | abcone2threexyz 89 | xtwone3four 90 | 4nineeightseven2 91 | zoneight234 92 | 7pqrstsixteen"#; 93 | 94 | #[test] 95 | fn day01_sample_part1() { 96 | assert_eq!(part1(SAMPLE1), 142); 97 | } 98 | 99 | #[test] 100 | fn day01_sample_part2() { 101 | assert_eq!(part2(SAMPLE2), 281); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /aoc2023/src/day09/mod.rs: -------------------------------------------------------------------------------- 1 | use rayon::prelude::*; 2 | 3 | fn parse(input: &str) -> Vec> { 4 | input 5 | .par_lines() 6 | .map(|line| { 7 | line.split_whitespace() 8 | .map(|n| { 9 | n.parse::() 10 | .expect(&format!("failed to parse number: {}", n)) 11 | }) 12 | .collect() 13 | }) 14 | .collect() 15 | } 16 | 17 | fn derivative<'i, I: Iterator>(mut iter: I) -> Vec { 18 | let mut prev = iter.next().unwrap(); 19 | iter.map(|n| { 20 | let diff = n - prev; 21 | prev = n; 22 | diff 23 | }) 24 | .collect() 25 | } 26 | 27 | fn derivatives<'i, I: Iterator>(iter: I) -> Vec> { 28 | let mut derivatives: Vec> = Vec::new(); 29 | derivatives.push(derivative(iter)); 30 | loop { 31 | if derivatives.last().unwrap().iter().all(|n| *n == 0) { 32 | break; 33 | } 34 | derivatives.push(derivative(derivatives.last().unwrap().iter())); 35 | } 36 | derivatives 37 | } 38 | 39 | fn predict_forward(items: Vec) -> isize { 40 | // naive implementation.. 41 | let derivatives = derivatives(items.iter()); 42 | 43 | let mut dx = 0; 44 | for deriv in derivatives.into_iter().rev() { 45 | dx += deriv.last().unwrap(); 46 | } 47 | 48 | items.last().unwrap() + dx 49 | } 50 | 51 | fn predict_backward(items: Vec) -> isize { 52 | // naive implementation.. 53 | let derivatives = derivatives(items.iter()); 54 | 55 | let mut dx = 0; 56 | for deriv in derivatives.into_iter().rev() { 57 | dx = deriv.first().unwrap() - dx; 58 | } 59 | 60 | items.first().unwrap() - dx 61 | } 62 | 63 | pub fn part1(input: &str) -> isize { 64 | parse(input).into_par_iter().map(predict_forward).sum() 65 | } 66 | 67 | pub fn part2(input: &str) -> isize { 68 | parse(input).into_par_iter().map(predict_backward).sum() 69 | } 70 | 71 | pub fn run(input: &str) -> (Option, Option) { 72 | let p1 = part1(input); 73 | let p1 = usize::try_from(p1).expect("part1: usize overflow"); 74 | let p2 = part2(input); 75 | let p2 = usize::try_from(p2).expect("part2: usize overflow"); 76 | (Some(p1), Some(p2)) 77 | } 78 | 79 | #[cfg(test)] 80 | mod test { 81 | use super::*; 82 | 83 | const SAMPLE: &'static str = r#"0 3 6 9 12 15 84 | 1 3 6 10 15 21 85 | 10 13 16 21 30 45"#; 86 | 87 | #[test] 88 | fn day09_sample_part1() { 89 | assert_eq!(part1(SAMPLE), 114); 90 | } 91 | 92 | #[test] 93 | fn day09_sample_part2() { 94 | assert_eq!(part2(SAMPLE), 2); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /aoc2023/src/day04/mod.rs: -------------------------------------------------------------------------------- 1 | pub fn part1(input: &str) -> usize { 2 | input 3 | .lines() 4 | .map(|line| { 5 | let mut parts = line.splitn(2, '|'); 6 | let winning = parts.next().expect("winning half"); 7 | let numbers = parts.next().expect("numbers half"); 8 | 9 | let winning_numbers = winning 10 | .splitn(2, ':') 11 | .skip(1) 12 | .next() 13 | .expect("list of winning numbers"); 14 | let winning_numbers = winning_numbers 15 | .trim() 16 | .split_whitespace() 17 | .collect::>(); 18 | 19 | let matching_numbers = numbers 20 | .trim() 21 | .split_whitespace() 22 | .filter(|n| winning_numbers.contains(n)); 23 | 24 | match matching_numbers.count() { 25 | 0 => 0, 26 | n => 2_usize.pow(n as u32 - 1), 27 | } 28 | }) 29 | .sum() 30 | } 31 | 32 | pub fn part2(input: &str) -> usize { 33 | let mut lines: Vec<(&str, usize)> = Vec::default(); 34 | for line in input.lines() { 35 | let mut parts = line.splitn(2, '|'); 36 | let winning = parts.next().expect("winning half"); 37 | let numbers = parts.next().expect("numbers half"); 38 | 39 | let winning_numbers = winning 40 | .splitn(2, ':') 41 | .skip(1) 42 | .next() 43 | .expect("list of winning numbers"); 44 | let winning_numbers = winning_numbers 45 | .trim() 46 | .split_whitespace() 47 | .collect::>(); 48 | 49 | let matching_numbers = numbers 50 | .trim() 51 | .split_whitespace() 52 | .filter(|n| winning_numbers.contains(n)); 53 | lines.push((line, matching_numbers.count())); 54 | } 55 | 56 | let mut card_counts = vec![1usize; lines.len()]; 57 | for i in 0..lines.len() { 58 | for _ in 0..card_counts[i] { 59 | for j in 0..lines[i].1 { 60 | card_counts[i + 1 + j] += 1; 61 | } 62 | } 63 | } 64 | 65 | card_counts.into_iter().sum() 66 | } 67 | 68 | pub fn run(input: &str) -> (Option, Option) { 69 | (Some(part1(input)), Some(part2(input))) 70 | } 71 | 72 | #[cfg(test)] 73 | mod test { 74 | use super::*; 75 | 76 | const SAMPLE: &'static str = r#"Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 77 | Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 78 | Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 79 | Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 80 | Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 81 | Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11"#; 82 | 83 | #[test] 84 | fn day04_sample_part1() { 85 | assert_eq!(part1(SAMPLE), 13); 86 | } 87 | 88 | #[test] 89 | fn day04_sample_part2() { 90 | assert_eq!(part2(SAMPLE), 30); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /aoc2023/src/day05/mod.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | struct Map { 3 | destination: usize, 4 | source: usize, 5 | length: usize, 6 | } 7 | 8 | impl From<&str> for Map { 9 | fn from(s: &str) -> Self { 10 | let mut parts = s.split(" "); 11 | let destination = parts.next().unwrap().parse::().unwrap(); 12 | let source = parts.next().unwrap().parse::().unwrap(); 13 | let length = parts.next().unwrap().parse::().unwrap(); 14 | Self { 15 | destination, 16 | source, 17 | length, 18 | } 19 | } 20 | } 21 | 22 | impl Map { 23 | fn map(&self, x: usize) -> Option { 24 | if x >= self.source && x <= self.source + self.length { 25 | let res = self.destination + (x - self.source); 26 | Some(res) 27 | } else { 28 | None 29 | } 30 | } 31 | } 32 | 33 | fn map(maps: &Vec, x: usize) -> usize { 34 | let map = maps.iter().find_map(|m| m.map(x)); 35 | match map { 36 | Some(mapped) => mapped, 37 | None => x, 38 | } 39 | } 40 | 41 | pub fn part1(input: &str) -> usize { 42 | let mut lines = input.lines(); 43 | let seeds = lines 44 | .next() 45 | .unwrap() 46 | .split(": ") 47 | .nth(1) 48 | .unwrap() 49 | .split(" ") 50 | .map(|s| s.parse::().unwrap()) 51 | .collect::>(); 52 | 53 | let mut maps: Vec> = Vec::default(); 54 | let mut current_map: Vec = Vec::default(); 55 | for line in lines { 56 | if line.trim().is_empty() { 57 | continue; 58 | } 59 | if line.contains("map:") { 60 | maps.push(current_map); 61 | current_map = Vec::default(); 62 | continue; 63 | } 64 | let map = Map::from(line.trim()); 65 | current_map.push(map); 66 | } 67 | maps.push(current_map); 68 | 69 | seeds 70 | .iter() 71 | .map(|seed| { 72 | let mut x: usize = *seed; 73 | for mapping in maps.iter() { 74 | x = map(mapping, x); 75 | } 76 | x 77 | }) 78 | .min() 79 | .expect("min value") 80 | } 81 | 82 | pub fn run(input: &str) -> (Option, Option) { 83 | (Some(part1(input)), None) 84 | } 85 | 86 | #[cfg(test)] 87 | mod test { 88 | use super::*; 89 | 90 | const SAMPLE: &'static str = r#"seeds: 79 14 55 13 91 | 92 | seed-to-soil map: 93 | 50 98 2 94 | 52 50 48 95 | 96 | soil-to-fertilizer map: 97 | 0 15 37 98 | 37 52 2 99 | 39 0 15 100 | 101 | fertilizer-to-water map: 102 | 49 53 8 103 | 0 11 42 104 | 42 0 7 105 | 57 7 4 106 | 107 | water-to-light map: 108 | 88 18 7 109 | 18 25 70 110 | 111 | light-to-temperature map: 112 | 45 77 23 113 | 81 45 19 114 | 68 64 13 115 | 116 | temperature-to-humidity map: 117 | 0 69 1 118 | 1 0 69 119 | 120 | humidity-to-location map: 121 | 60 56 37 122 | 56 93 4 123 | "#; 124 | 125 | #[test] 126 | fn day05_sample_part1() { 127 | assert_eq!(part1(SAMPLE), 35); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /aoc2023/src/day02/mod.rs: -------------------------------------------------------------------------------- 1 | struct Game { 2 | id: usize, 3 | rounds: Vec<(usize, usize, usize)>, 4 | } 5 | 6 | fn is_digit(c: &char) -> bool { 7 | *c >= '0' && *c <= '9' 8 | } 9 | 10 | fn extract_and_parse_numbers(s: &str) -> Option { 11 | s.chars() 12 | .filter(is_digit) 13 | .collect::() 14 | .parse::() 15 | .ok() 16 | } 17 | 18 | fn parse_round(round: &str) -> (usize, usize, usize) { 19 | let parts = round.split(',').collect::>(); 20 | let mut r = 0; 21 | let mut g = 0; 22 | let mut b = 0; 23 | 24 | for part in parts { 25 | let mut pull = part.trim().split_whitespace(); 26 | let count = pull.next(); 27 | let colour = pull.next(); 28 | 29 | match (count, colour) { 30 | (Some(count), Some(colour)) if colour.len() > 0 => { 31 | let count = extract_and_parse_numbers(count); 32 | if let Some(count) = count { 33 | match colour.chars().next().unwrap() { 34 | 'r' => r += count, 35 | 'g' => g += count, 36 | 'b' => b += count, 37 | _ => {} 38 | } 39 | } 40 | } 41 | _ => {} 42 | } 43 | } 44 | 45 | (r, g, b) 46 | } 47 | 48 | fn parse_game(line: &str) -> Option { 49 | // parse the game # 50 | let mut parts = line.splitn(2, ':'); 51 | let title = parts.next()?; 52 | let id = extract_and_parse_numbers(title)?; 53 | 54 | let rounds: Vec<_> = parts.next()?.split(';').map(parse_round).collect(); 55 | 56 | Some(Game { id, rounds }) 57 | } 58 | 59 | fn is_game_possible(game: &Game) -> bool { 60 | game.rounds 61 | .iter() 62 | .all(|g| g.0 <= 12 && g.1 <= 13 && g.2 <= 14) 63 | } 64 | 65 | pub fn part1(input: &str) -> usize { 66 | input 67 | .lines() 68 | .filter_map(parse_game) 69 | .filter(is_game_possible) 70 | .map(|g| g.id) 71 | .sum() 72 | } 73 | 74 | fn min_cubes(game: Game) -> (usize, usize, usize) { 75 | game.rounds.iter().fold((0, 0, 0), |acc, el| { 76 | (acc.0.max(el.0), acc.1.max(el.1), acc.2.max(el.2)) 77 | }) 78 | } 79 | 80 | fn power(game: (usize, usize, usize)) -> usize { 81 | game.0 * game.1 * game.2 82 | } 83 | 84 | pub fn part2(input: &str) -> usize { 85 | input 86 | .lines() 87 | .filter_map(parse_game) 88 | .map(min_cubes) 89 | .map(power) 90 | .sum() 91 | } 92 | 93 | pub fn run(input: &str) -> (Option, Option) { 94 | (Some(part1(input)), Some(part2(input))) 95 | } 96 | 97 | #[cfg(test)] 98 | mod test { 99 | const SAMPLE: &'static str = r#"Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green 100 | Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue 101 | Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red 102 | Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red 103 | Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green"#; 104 | 105 | #[test] 106 | fn day02_sample_part1() { 107 | assert_eq!(super::part1(SAMPLE), 8); 108 | } 109 | 110 | #[test] 111 | fn day02_sample_part2() { 112 | assert_eq!(super::part2(SAMPLE), 2286); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /aoc2023/src/grid.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | use std::fmt::Debug; 3 | use std::fmt::{Display, Formatter}; 4 | 5 | pub trait ParseGridChar { 6 | fn parse_grid_char(c: char) -> T; 7 | } 8 | 9 | pub struct Grid { 10 | pub data: Vec>, 11 | } 12 | 13 | impl Grid { 14 | pub fn new(data: Vec>) -> Self { 15 | Self { data } 16 | } 17 | 18 | pub fn construct(width: usize, height: usize, value: T) -> Self { 19 | let data = vec![vec![value; width]; height]; 20 | Self::new(data) 21 | } 22 | 23 | pub fn _get>(&self, x: S, y: S) -> Option { 24 | let x = x.try_into().ok()?; 25 | let y = y.try_into().ok()?; 26 | self.data.get(y).and_then(|row| row.get(x)).copied() 27 | } 28 | 29 | pub fn get>(&self, x: S, y: S) -> T { 30 | self._get(x, y).expect("grid contains coords") 31 | } 32 | 33 | pub fn set(&mut self, x: usize, y: usize, value: T) { 34 | self.data[y][x] = value; 35 | } 36 | 37 | pub fn width(&self) -> usize { 38 | self.data[0].len() 39 | } 40 | 41 | pub fn height(&self) -> usize { 42 | self.data.len() 43 | } 44 | 45 | pub fn parse>(input: &str) -> Self { 46 | let data = input 47 | .lines() 48 | .map(|line| line.chars().map(P::parse_grid_char).collect()) 49 | .collect(); 50 | Self::new(data) 51 | } 52 | 53 | pub fn neighbour_coords_cardinal(&self, x: usize, y: usize) -> Vec<(usize, usize)> { 54 | let mut neighbours = Vec::with_capacity(4); 55 | if x > 0 { 56 | neighbours.push((x - 1, y)); 57 | } 58 | if y > 0 { 59 | neighbours.push((x, y - 1)); 60 | } 61 | if x < self.width() - 1 { 62 | neighbours.push((x + 1, y)); 63 | } 64 | if y < self.height() - 1 { 65 | neighbours.push((x, y + 1)); 66 | } 67 | neighbours 68 | } 69 | 70 | pub fn neighbours_cardinal(&self, x: usize, y: usize) -> Vec { 71 | let mut neighbours = Vec::with_capacity(4); 72 | if x > 0 { 73 | neighbours.push(self.data[y][x - 1]); 74 | } 75 | if y > 0 { 76 | neighbours.push(self.data[y - 1][x]); 77 | } 78 | if x < self.width() - 1 { 79 | neighbours.push(self.data[y][x + 1]); 80 | } 81 | if y < self.height() - 1 { 82 | neighbours.push(self.data[y + 1][x]); 83 | } 84 | neighbours 85 | } 86 | 87 | pub fn iter_coords_col_major(&self) -> impl Iterator + '_ { 88 | (0..self.width()).flat_map(move |x| (0..self.height()).map(move |y| (x, y))) 89 | } 90 | 91 | pub fn iter_coords_row_major(&self) -> impl Iterator + '_ { 92 | (0..self.height()).flat_map(move |y| (0..self.width()).map(move |x| (x, y))) 93 | } 94 | } 95 | 96 | impl Display for Grid { 97 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 98 | for row in &self.data { 99 | for cell in row { 100 | write!(f, "{}", cell)?; 101 | } 102 | writeln!(f)?; 103 | } 104 | Ok(()) 105 | } 106 | } 107 | 108 | impl Clone for Grid { 109 | fn clone(&self) -> Self { 110 | Self::new(self.data.clone()) 111 | } 112 | } 113 | 114 | impl PartialEq for Grid { 115 | fn eq(&self, other: &Self) -> bool { 116 | self.data == other.data 117 | } 118 | } 119 | 120 | impl Eq for Grid {} 121 | -------------------------------------------------------------------------------- /aoc2023/src/day15/mod.rs: -------------------------------------------------------------------------------- 1 | use rayon::prelude::*; 2 | 3 | fn hash(input: &[u8]) -> usize { 4 | let mut h = 0; 5 | for i in input { 6 | h += *i as usize; 7 | h *= 17; 8 | h = h % 256; 9 | } 10 | h 11 | } 12 | 13 | pub fn part1(input: &str) -> usize { 14 | input 15 | .trim() 16 | .par_split(',') 17 | .map(|s| hash(s.as_bytes())) 18 | .sum() 19 | } 20 | 21 | fn get_label(input: &str) -> &str { 22 | let end = input.chars().take_while(|c| c != &'=' && c != &'-').count(); 23 | &input[..end] 24 | } 25 | 26 | fn box_number(input: &str) -> usize { 27 | hash(input.as_bytes()) 28 | } 29 | 30 | pub fn part2(input: &str) -> usize { 31 | let mut boxes: Vec> = vec![Vec::default(); 256]; 32 | 33 | let steps = input.trim().split(','); 34 | for step in steps { 35 | let label = get_label(step); 36 | let box_number = box_number(label); 37 | 38 | // eprintln!("Step {step} -> {label} -> {box_number}"); 39 | 40 | let boxx = boxes.get_mut(box_number).expect("box exists"); 41 | 42 | if step.contains('-') { 43 | // remove! 44 | let idx = boxx.iter().position(|(l, _)| l == &label); 45 | if let Some(idx) = idx { 46 | boxx.remove(idx); 47 | // eprintln!("Removed {} from box {}", label, box_number); 48 | } 49 | // else { 50 | // eprintln!("Tried to remove {} from box {} but it wasn't there", label, box_number); 51 | // } 52 | } else if step.contains('=') { 53 | // add! 54 | let lens = step.split('=').skip(1).next().expect("lens"); 55 | let len = lens.parse::().expect("lens number"); 56 | match boxx.iter().position(|(l, _)| l == &label) { 57 | Some(idx) => { 58 | boxx[idx] = (label, len); 59 | // eprintln!("Updated {} in box {} to {}", label, box_number, len); 60 | } 61 | None => { 62 | boxx.push((label, len)); 63 | // eprintln!("Added {} to box {} with {}", label, box_number, len); 64 | } 65 | } 66 | } 67 | 68 | // eprintln!(); 69 | } 70 | 71 | // for (i, boxx) in boxes.iter().enumerate() { 72 | // if !boxx.is_empty() { 73 | // eprint!("Box {}: ", i); 74 | // for slot in boxx.iter() { 75 | // eprint!("[{} {}] ", slot.0, slot.1); 76 | // } 77 | // eprintln!(); 78 | // } 79 | // } 80 | // 81 | boxes 82 | .into_par_iter() 83 | .enumerate() 84 | .map(|(i, boxx)| { 85 | let box_num = i + 1; 86 | boxx.into_iter() 87 | .enumerate() 88 | .map(|(j, (_, focal_len))| { 89 | let slot_num = j + 1; 90 | slot_num * focal_len * box_num 91 | }) 92 | .sum::() 93 | }) 94 | .sum() 95 | } 96 | 97 | pub fn run(input: &str) -> (Option, Option) { 98 | (Some(part1(input)), Some(part2(input))) 99 | } 100 | 101 | #[cfg(test)] 102 | mod test { 103 | use super::*; 104 | 105 | const SAMPLE: &'static str = r#"rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7"#; 106 | 107 | #[test] 108 | fn can_hash() { 109 | let input = "HASH"; 110 | assert_eq!(hash(input.as_bytes()), 52); 111 | } 112 | 113 | #[test] 114 | fn day15_sample_part1() { 115 | assert_eq!(part1(SAMPLE), 1320); 116 | } 117 | 118 | #[test] 119 | fn day15_sample_part2() { 120 | assert_eq!(part2(SAMPLE), 145); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /aoc2023/src/day03/mod.rs: -------------------------------------------------------------------------------- 1 | fn is_symbol(c: char) -> bool { 2 | c != '.' && !c.is_ascii_digit() && !c.is_ascii_whitespace() 3 | } 4 | 5 | fn is_adjacent((sx, sy): (usize, usize), (nx1, ny): (usize, usize), xlen: usize) -> bool { 6 | let sx = sx as isize; 7 | let sy = sy as isize; 8 | let nx1 = nx1 as isize; 9 | let ny = ny as isize; 10 | let xlen = xlen as isize; 11 | match ny - sy { 12 | -1 | 0 | 1 => sx >= (nx1 - 1) && sx <= (nx1 + xlen), 13 | _ => false, 14 | } 15 | } 16 | 17 | pub fn part1(input: &str) -> usize { 18 | let symbol_locations = input 19 | .lines() 20 | .enumerate() 21 | .flat_map(|(y, line)| { 22 | line.chars().enumerate().filter_map(move |(x, c)| { 23 | if is_symbol(c) { 24 | Some(((x, y), c)) 25 | } else { 26 | None 27 | } 28 | }) 29 | }) 30 | .collect::>(); 31 | 32 | let re = regex::Regex::new(r"\d+").unwrap(); 33 | let mut numbers: Vec<((usize, usize), &str)> = Vec::default(); 34 | for (y, line) in input.lines().enumerate() { 35 | for number in re.find_iter(line) { 36 | let (x, e) = (number.start(), number.end()); 37 | let number = &line[x..e]; 38 | numbers.push(((x, y), number)); 39 | } 40 | } 41 | 42 | // if its dumb but it works... 43 | let mut sum: usize = 0; 44 | for symbol in symbol_locations.iter() { 45 | for number in numbers.iter() { 46 | if is_adjacent(symbol.0, number.0, number.1.len()) { 47 | sum += number.1.parse::().unwrap(); 48 | } 49 | } 50 | } 51 | 52 | sum 53 | } 54 | 55 | fn is_gear(c: char) -> bool { 56 | c == '*' 57 | } 58 | 59 | pub fn part2(input: &str) -> usize { 60 | let symbol_locations = input 61 | .lines() 62 | .enumerate() 63 | .flat_map(|(y, line)| { 64 | line.chars() 65 | .enumerate() 66 | .filter_map(move |(x, c)| if is_gear(c) { Some(((x, y), c)) } else { None }) 67 | }) 68 | .collect::>(); 69 | 70 | let re = regex::Regex::new(r"\d+").unwrap(); 71 | let mut numbers: Vec<((usize, usize), &str)> = Vec::default(); 72 | for (y, line) in input.lines().enumerate() { 73 | for number in re.find_iter(line) { 74 | let (x, e) = (number.start(), number.end()); 75 | let number = &line[x..e]; 76 | numbers.push(((x, y), number)); 77 | } 78 | } 79 | 80 | let mut sum: usize = 0; 81 | for symbol in symbol_locations.iter() { 82 | let adjacent_numbers = numbers 83 | .iter() 84 | .filter(|number| is_adjacent(symbol.0, number.0, number.1.len())) 85 | .take(3) 86 | .collect::>(); 87 | if adjacent_numbers.len() == 2 { 88 | let gear_ratio = adjacent_numbers[0].1.parse::().unwrap() 89 | * adjacent_numbers[1].1.parse::().unwrap(); 90 | sum += gear_ratio; 91 | } 92 | } 93 | 94 | sum 95 | } 96 | 97 | pub fn run(input: &str) -> (Option, Option) { 98 | (Some(part1(input)), Some(part2(input))) 99 | } 100 | 101 | #[cfg(test)] 102 | mod test { 103 | use super::*; 104 | 105 | const SAMPLE: &'static str = r#"467..114.. 106 | ...*...... 107 | ..35..633. 108 | ......#... 109 | 617*...... 110 | .....+.58. 111 | ..592..... 112 | ......755. 113 | ...$.*.... 114 | .664.598.."#; 115 | 116 | #[test] 117 | fn day03_sample_part1() { 118 | assert_eq!(part1(SAMPLE), 4361); 119 | } 120 | 121 | #[test] 122 | fn day03_sample_part2() { 123 | assert_eq!(part2(SAMPLE), 467835); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /aoc2023/src/inputs.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use chrono::TimeZone; 3 | use dialoguer::{theme::ColorfulTheme, Confirm, Input}; 4 | 5 | pub fn load_inputs() -> Result<()> { 6 | let inputs = check_missing_inputs(); 7 | if inputs.is_empty() { 8 | return Ok(()); 9 | } 10 | 11 | println!("The following inputs are missing:"); 12 | for day in inputs.iter() { 13 | println!(" Day {}", day); 14 | } 15 | println!(); 16 | 17 | let confirm = Confirm::with_theme(&ColorfulTheme::default()) 18 | .with_prompt("Do you want to download these inputs now?") 19 | .interact() 20 | .context("Failed to get confirmation")?; 21 | if !confirm { 22 | return Ok(()); 23 | } 24 | 25 | let session = get_session().with_context(|| "Failed to get AOC session auth")?; 26 | inputs.into_iter().try_for_each(|day| { 27 | download_input(day, &session)?; 28 | println!("Downloaded day {day}!"); 29 | Ok::<(), anyhow::Error>(()) 30 | })?; 31 | 32 | Ok(()) 33 | } 34 | 35 | fn check_missing_inputs() -> Vec { 36 | // timezone where AOC is hosted 37 | let tz = chrono::FixedOffset::west_opt(5 * 3600).expect("Valid timezone"); 38 | 39 | let now = chrono::Utc::now(); 40 | (1..=25) 41 | .filter(|day| { 42 | let date = tz 43 | .with_ymd_and_hms(2023, 12, *day, 0, 0, 1) 44 | .earliest() 45 | .expect("Valid date"); 46 | now >= date 47 | }) 48 | .filter(|day| { 49 | !std::path::Path::new("inputs") 50 | .join(&format!("day{:02}.txt", day)) 51 | .exists() 52 | }) 53 | .collect() 54 | } 55 | 56 | fn get_session() -> Result { 57 | match std::env::var("AOC_SESSION") { 58 | Ok(session) => Ok(session), 59 | Err(_) => { 60 | println!("Inputs are missing and env var AOC_SESSION is not set!"); 61 | println!("Please set AOC_SESSION to your AOC session cookie."); 62 | println!("You can find this by logging into AOC and looking at the value of the 'session' cookie."); 63 | println!( 64 | "It should look something like this: 53616c7465645f5f6a0a0a0a0a0a0a0a0a0a0a0a" 65 | ); 66 | println!("You can set it by running:"); 67 | println!(" export AOC_SESSION=53616c7465645f5f6a0a0a0a0a0a0a0a0a0a0a0a"); 68 | println!("Or by adding it to a .env file in the root of this repo, like this:"); 69 | println!(" echo 'AOC_SESSION=53616c7465645f5f6a0a0a0a0a0a0a0a0a0a0a0a' >> .env"); 70 | println!("Be sure not to check this file into git! Add it to your .gitignore:"); 71 | println!(" echo '.env' >> .gitignore"); 72 | println!("Then run this command again."); 73 | 74 | println!("For now, you can just enter your session cookie here:"); 75 | let session = Input::::with_theme(&ColorfulTheme::default()) 76 | .with_prompt("AOC session cookie") 77 | .interact() 78 | .context("Failed to get session cookie")?; 79 | Ok(session) 80 | } 81 | } 82 | } 83 | 84 | fn download_input(day: u32, session: &str) -> Result<()> { 85 | let url = format!("https://adventofcode.com/2023/day/{day}/input"); 86 | let body = ureq::get(&url) 87 | .set("Cookie", &format!("session={}", session)) 88 | .call() 89 | .with_context(|| format!("Failed to communicate with AOC server for day {day}"))? 90 | .into_string() 91 | .with_context(|| format!("Failed to download input for day {}", day))?; 92 | let path = std::path::Path::new("inputs").join(&format!("day{:02}.txt", day)); 93 | std::fs::write(&path, body).with_context(|| { 94 | format!( 95 | "Failed to write input for day {day} to file: {}", 96 | path.display() 97 | ) 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /aoc2023/src/day08/mod.rs: -------------------------------------------------------------------------------- 1 | use rayon::prelude::*; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | enum Direction { 6 | Left, 7 | Right, 8 | } 9 | 10 | fn parse(input: &str) -> (Vec, HashMap<&str, (&str, &str)>) { 11 | let mut lines = input.lines(); 12 | let directions = lines.next().expect("directions").chars().map(|c| match c { 13 | 'L' => Direction::Left, 14 | 'R' => Direction::Right, 15 | _ => panic!("invalid direction"), 16 | }); 17 | 18 | let nodes = lines 19 | .filter_map(|line| { 20 | if line.trim().is_empty() { 21 | return None; 22 | } 23 | let mut parts = line.split(" = "); 24 | let name = parts.next().expect("node name"); 25 | let nodes = parts 26 | .next() 27 | .expect("nodes") 28 | .trim_matches(|c| c == '(' || c == ')'); 29 | let mut nodes = nodes.splitn(2, ", "); 30 | let left = nodes.next().expect("left node"); 31 | let right = nodes.next().expect("right node"); 32 | 33 | Some((name, (left, right))) 34 | }) 35 | .collect::>(); 36 | 37 | (directions.collect(), nodes) 38 | } 39 | 40 | pub fn part1(input: &str) -> usize { 41 | let (directions, nodes) = parse(input); 42 | let mut current = "AAA"; 43 | let mut i = 0; 44 | loop { 45 | let (left, right) = nodes.get(current).expect("current node"); 46 | current = match directions[i % directions.len()] { 47 | Direction::Left => left, 48 | Direction::Right => right, 49 | }; 50 | i += 1; 51 | if current == "ZZZ" { 52 | return i; 53 | } 54 | } 55 | } 56 | 57 | fn lcm>(iter: I) -> usize { 58 | fn gcd(mut a: usize, mut b: usize) -> usize { 59 | while b != 0 { 60 | let t = b; 61 | b = a % b; 62 | a = t; 63 | } 64 | a 65 | } 66 | 67 | iter.into_iter().fold(1, |a, b| a * b / gcd(a, b)) 68 | } 69 | 70 | pub fn part2(input: &str) -> usize { 71 | let (directions, nodes) = parse(input); 72 | let nodes = nodes 73 | .keys() 74 | .par_bridge() 75 | .filter_map(|n| if n.ends_with("A") { Some(n) } else { None }) 76 | .map(|mut current| { 77 | let mut i = 0; 78 | loop { 79 | let (left, right) = nodes.get(current).expect("current node"); 80 | current = match directions[i % directions.len()] { 81 | Direction::Left => left, 82 | Direction::Right => right, 83 | }; 84 | i += 1; 85 | if current.ends_with("Z") { 86 | return i; 87 | } 88 | } 89 | }) 90 | .collect::>(); 91 | lcm(nodes) 92 | } 93 | 94 | pub fn run(input: &str) -> (Option, Option) { 95 | (Some(part1(input)), Some(part2(input))) 96 | } 97 | 98 | #[cfg(test)] 99 | mod test { 100 | use super::*; 101 | 102 | const SAMPLE1: &'static str = r#"RL 103 | 104 | AAA = (BBB, CCC) 105 | BBB = (DDD, EEE) 106 | CCC = (ZZZ, GGG) 107 | DDD = (DDD, DDD) 108 | EEE = (EEE, EEE) 109 | GGG = (GGG, GGG) 110 | ZZZ = (ZZZ, ZZZ)"#; 111 | const SAMPLE2: &'static str = r#"LLR 112 | 113 | AAA = (BBB, BBB) 114 | BBB = (AAA, ZZZ) 115 | ZZZ = (ZZZ, ZZZ)"#; 116 | const SAMPLE3: &'static str = r#"LR 117 | 118 | 11A = (11B, XXX) 119 | 11B = (XXX, 11Z) 120 | 11Z = (11B, XXX) 121 | 22A = (22B, XXX) 122 | 22B = (22C, 22C) 123 | 22C = (22Z, 22Z) 124 | 22Z = (22B, 22B) 125 | XXX = (XXX, XXX)"#; 126 | 127 | #[test] 128 | fn day08_sample1_part1() { 129 | assert_eq!(part1(SAMPLE1), 2); 130 | } 131 | 132 | #[test] 133 | fn day08_sample2_part1() { 134 | assert_eq!(part1(SAMPLE2), 6); 135 | } 136 | 137 | #[test] 138 | fn day08_sample3_part2() { 139 | assert_eq!(part2(SAMPLE3), 6); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /aoc2023/src/day11/mod.rs: -------------------------------------------------------------------------------- 1 | fn parse(input: &str) -> Vec> { 2 | input 3 | .lines() 4 | .map(|line| line.chars().map(|c| c == '#').collect()) 5 | .collect() 6 | } 7 | 8 | fn empty_rows(map: &Vec>) -> Vec { 9 | map.iter() 10 | .enumerate() 11 | .filter(|(_, row)| row.iter().all(|&c| !c)) 12 | .map(|(i, _)| i) 13 | .collect() 14 | } 15 | 16 | fn empty_columns(map: &Vec>) -> Vec { 17 | let mut empty = vec![]; 18 | for i in 0..map[0].len() { 19 | if map.iter().all(|row| !row[i]) { 20 | empty.push(i); 21 | } 22 | } 23 | empty 24 | } 25 | 26 | fn galaxy_locations(map: &Vec>) -> Vec<(usize, usize)> { 27 | let mut locations = vec![]; 28 | for (y, row) in map.iter().enumerate() { 29 | for (x, &c) in row.iter().enumerate() { 30 | if c { 31 | locations.push((x, y)); 32 | } 33 | } 34 | } 35 | locations 36 | } 37 | 38 | fn galaxy_pairs(galaxies: &Vec<(usize, usize)>) -> Vec<((usize, usize), (usize, usize))> { 39 | let mut pairs = vec![]; 40 | for (i, &galaxy) in galaxies.iter().enumerate() { 41 | for &other in &galaxies[i + 1..] { 42 | pairs.push((galaxy, other)); 43 | } 44 | } 45 | pairs 46 | } 47 | 48 | pub fn part1(input: &str) -> usize { 49 | let map = parse(input); 50 | let empty_rows = empty_rows(&map); 51 | let empty_columns = empty_columns(&map); 52 | let galaxies = galaxy_locations(&map); 53 | let pairs = galaxy_pairs(&galaxies); 54 | 55 | pairs 56 | .into_iter() 57 | .map(|(a, b)| { 58 | let (x1, y1) = a; 59 | let (x2, y2) = b; 60 | 61 | let mut dx = (x2 as isize - x1 as isize).abs() as usize; 62 | let mut dy = (y2 as isize - y1 as isize).abs() as usize; 63 | 64 | for xi in x1.min(x2) + 1..x1.max(x2) { 65 | if empty_columns.contains(&xi) { 66 | dx += 1; 67 | } 68 | } 69 | 70 | for yi in y1.min(y2) + 1..y1.max(y2) { 71 | if empty_rows.contains(&yi) { 72 | dy += 1; 73 | } 74 | } 75 | 76 | dx + dy 77 | }) 78 | .sum() 79 | } 80 | 81 | pub fn part2(input: &str, scale: usize) -> usize { 82 | let map = parse(input); 83 | let empty_rows = empty_rows(&map); 84 | let empty_columns = empty_columns(&map); 85 | let galaxies = galaxy_locations(&map); 86 | let pairs = galaxy_pairs(&galaxies); 87 | 88 | // Reduce scale by 1 since we're already intrinsically counting the row/column in dx/dy 89 | // calculation 90 | let scale = scale - 1; 91 | 92 | pairs 93 | .into_iter() 94 | .map(|(a, b)| { 95 | let (x1, y1) = a; 96 | let (x2, y2) = b; 97 | 98 | let mut dx = (x2 as isize - x1 as isize).abs() as usize; 99 | let mut dy = (y2 as isize - y1 as isize).abs() as usize; 100 | 101 | for xi in x1.min(x2) + 1..x1.max(x2) { 102 | if empty_columns.contains(&xi) { 103 | dx += scale; 104 | } 105 | } 106 | 107 | for yi in y1.min(y2) + 1..y1.max(y2) { 108 | if empty_rows.contains(&yi) { 109 | dy += scale; 110 | } 111 | } 112 | 113 | dx + dy 114 | }) 115 | .sum::() 116 | } 117 | 118 | pub fn run(input: &str) -> (Option, Option) { 119 | (Some(part1(input)), Some(part2(input, 1000000))) 120 | } 121 | 122 | #[cfg(test)] 123 | mod test { 124 | use super::*; 125 | 126 | const SAMPLE: &'static str = r#"...#...... 127 | .......#.. 128 | #......... 129 | .......... 130 | ......#... 131 | .#........ 132 | .........# 133 | .......... 134 | .......#.. 135 | #...#....."#; 136 | 137 | #[test] 138 | fn day11_sample_part1() { 139 | assert_eq!(part1(SAMPLE), 374); 140 | } 141 | 142 | #[test] 143 | fn day11_sample_part2() { 144 | assert_eq!(part2(SAMPLE, 10), 1030); 145 | } 146 | 147 | #[test] 148 | fn day11_sample_part3() { 149 | assert_eq!(part2(SAMPLE, 100), 8410); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /aoc2023/src/day06/mod.rs: -------------------------------------------------------------------------------- 1 | fn roots(a: f64, b: f64, c: f64) -> (f64, f64) { 2 | let d = b * b - 4.0 * a * c; 3 | let d_sqrt = d.sqrt(); 4 | let x1 = (-b + d_sqrt) / (2.0 * a); 5 | let x2 = (-b - d_sqrt) / (2.0 * a); 6 | (x1.min(x2), x1.max(x2)) 7 | } 8 | 9 | pub fn part1(input: &str) -> usize { 10 | let mut lines = input.lines(); 11 | const ACCELERATION: usize = 1; 12 | let race_duration = lines 13 | .next() 14 | .unwrap() 15 | .split_whitespace() 16 | .skip(1) 17 | .map(|s| s.parse::().unwrap()); 18 | let distance_record = lines 19 | .next() 20 | .unwrap() 21 | .split_whitespace() 22 | .skip(1) 23 | .map(|s| s.parse::().unwrap()); 24 | 25 | race_duration 26 | .zip(distance_record) 27 | .map(|(t, record)| { 28 | // let t_hold_max = t / 2; 29 | let t_hold_record = roots( 30 | -(ACCELERATION as f64), 31 | (ACCELERATION * t) as f64, 32 | -(record as f64), 33 | ); 34 | let t_hold_record = ( 35 | t_hold_record.0.ceil() as usize, 36 | t_hold_record.1.ceil() as usize, 37 | ); 38 | 39 | // let d_max = ACCELERATION * t_hold_max * (t - t_hold_max); 40 | let d_record = ( 41 | ACCELERATION * t_hold_record.0 * (t - t_hold_record.0), 42 | ACCELERATION * t_hold_record.1 * (t - t_hold_record.1), 43 | ); 44 | 45 | let mut count = t_hold_record.1 - t_hold_record.0; 46 | if d_record.0 == d_record.1 { 47 | count -= 1; 48 | } 49 | // eprintln!("t: {}, record: {}", t, record); 50 | // eprintln!( 51 | // "t_hold_max: {}, t_hold_record: {:?}, count: {}", 52 | // t_hold_max, t_hold_record, count 53 | // ); 54 | // eprintln!("d_max: {}, d_record: {:?}", d_max, d_record); 55 | // eprintln!(); 56 | count 57 | }) 58 | .fold(1, |acc, count| acc * count) 59 | } 60 | 61 | pub fn part2(input: &str) -> usize { 62 | let mut lines = input.lines(); 63 | const ACCELERATION: usize = 1; 64 | let race_duration = lines 65 | .next() 66 | .unwrap() 67 | .split_whitespace() 68 | .skip(1) 69 | .collect::>() 70 | .join("") 71 | .parse::() 72 | .unwrap(); 73 | let distance_record = lines 74 | .next() 75 | .unwrap() 76 | .split_whitespace() 77 | .skip(1) 78 | .collect::>() 79 | .join("") 80 | .parse::() 81 | .unwrap(); 82 | 83 | let t = race_duration; 84 | let record = distance_record; 85 | 86 | // let t_hold_max = t / 2; 87 | let t_hold_record = roots( 88 | -(ACCELERATION as f64), 89 | (ACCELERATION * t) as f64, 90 | -(record as f64), 91 | ); 92 | let t_hold_record = ( 93 | t_hold_record.0.ceil() as usize, 94 | t_hold_record.1.ceil() as usize, 95 | ); 96 | 97 | // let d_max = ACCELERATION * t_hold_max * (t - t_hold_max); 98 | let d_record = ( 99 | ACCELERATION * t_hold_record.0 * (t - t_hold_record.0), 100 | ACCELERATION * t_hold_record.1 * (t - t_hold_record.1), 101 | ); 102 | 103 | let mut count = t_hold_record.1 - t_hold_record.0; 104 | if d_record.0 == d_record.1 { 105 | count -= 1; 106 | } 107 | // eprintln!("t: {}, record: {}", t, record); 108 | // eprintln!( 109 | // "t_hold_max: {}, t_hold_record: {:?}, count: {}", 110 | // t_hold_max, t_hold_record, count 111 | // ); 112 | // eprintln!("d_max: {}, d_record: {:?}", d_max, d_record); 113 | // eprintln!(); 114 | count 115 | } 116 | 117 | pub fn run(input: &str) -> (Option, Option) { 118 | (Some(part1(input)), Some(part2(input))) 119 | } 120 | 121 | #[cfg(test)] 122 | mod test { 123 | use super::*; 124 | 125 | const SAMPLE: &'static str = r#"Time: 7 15 30 126 | Distance: 9 40 200"#; 127 | 128 | #[test] 129 | fn day06_sample_part1() { 130 | assert_eq!(part1(SAMPLE), 288); 131 | } 132 | 133 | #[test] 134 | fn day06_sample_part2() { 135 | assert_eq!(part2(SAMPLE), 71503); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /aoc2023/src/day17/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::grid::{Grid, ParseGridChar}; 2 | 3 | struct Parse; 4 | impl ParseGridChar for Parse { 5 | fn parse_grid_char(c: char) -> u8 { 6 | (c as u8) - ('0' as u8) 7 | } 8 | } 9 | 10 | pub fn part1(input: &str) -> usize { 11 | let grid = Grid::parse::(input); 12 | 13 | let mut dist: Grid = Grid::construct(grid.width(), grid.height(), usize::MAX); 14 | let mut prev: Grid> = Grid::construct(grid.width(), grid.height(), None); 15 | let mut q: Vec<(usize, usize)> = grid.iter_coords_row_major().collect(); 16 | dist.set(0, 0, 0); 17 | 18 | 'djikstra: while !q.is_empty() { 19 | let (u, _) = q 20 | .iter() 21 | .enumerate() 22 | .min_by_key(|(_, (x, y))| dist.get(*x, *y)) 23 | .expect("q has elements"); 24 | let (x, y) = q.remove(u); 25 | if x == grid.width() - 1 && y == grid.height() - 1 { 26 | break 'djikstra; 27 | } 28 | 29 | let neighbours = grid 30 | .neighbour_coords_cardinal(x, y) 31 | .into_iter() 32 | .filter(|n| q.contains(n)) 33 | .filter(|n| { 34 | // can't go in a straight line for more than 3 steps 35 | let mut s = Vec::new(); 36 | s.push((x, y)); 37 | s.push(*n); 38 | let p1 = prev.get(x, y); 39 | if let Some(p1) = p1 { 40 | let p2 = prev.get(p1.0, p1.1); 41 | s.push(p1); 42 | if let Some(p2) = p2 { 43 | s.push(p2); 44 | 45 | // check if all 4 are in a straight line 46 | // if so, skip this neighbour 47 | if s.iter().all(|(x, _)| *x == s[0].0) 48 | || s.iter().all(|(_, y)| *y == s[0].1) 49 | { 50 | return false; 51 | } 52 | } 53 | } 54 | return true; 55 | }) 56 | .collect::>(); 57 | for v in neighbours.into_iter() { 58 | let cost = grid.get(v.0, v.1) as usize; 59 | let alt = dist.get(x, y) + cost; 60 | if alt < dist.get(v.0, v.1) { 61 | dist.set(v.0, v.1, alt); 62 | prev.set(v.0, v.1, Some((x, y))); 63 | } 64 | } 65 | } 66 | 67 | let mut s = Vec::new(); 68 | s.push((grid.width() - 1, grid.height() - 1)); 69 | let mut u = (grid.width() - 1, grid.height() - 1); 70 | while let Some((x, y)) = prev.get(u.0, u.1) { 71 | if x == 0 && y == 0 { 72 | break; 73 | } 74 | s.push((x, y)); 75 | u = (x, y); 76 | } 77 | 78 | let mut draw_grid = Grid::construct(grid.width(), grid.height(), ' '); 79 | for (x, y) in grid.iter_coords_row_major() { 80 | let v = grid.get(x, y); 81 | draw_grid.set(x, y, (v + '0' as u8) as char); 82 | } 83 | for (s, l) in s.iter().skip(1).zip(s.iter()) { 84 | let dir = if s.0 > l.0 { 85 | '←' 86 | } else if s.0 < l.0 { 87 | '→' 88 | } else if s.1 > l.1 { 89 | '↑' 90 | } else if s.1 < l.1 { 91 | '↓' 92 | } else { 93 | unreachable!() 94 | }; 95 | draw_grid.set(s.0, s.1, dir); 96 | } 97 | // for y in 0..draw_grid.height() { 98 | // for x in 0..draw_grid.width() { 99 | // let (es, ee) = if s.contains(&(x, y)) { 100 | // ("\x1b[93m", "\x1b[0m") 101 | // } 102 | // else { 103 | // ("", "") 104 | // }; 105 | // print!("{es}{}{ee}", draw_grid.get(x, y)); 106 | // } 107 | // println!(); 108 | // } 109 | 110 | s.into_iter().map(|(x, y)| grid.get(x, y) as usize).sum() 111 | } 112 | 113 | pub fn part2(_input: &str) -> usize { 114 | 0 115 | } 116 | 117 | pub fn run(input: &str) -> (Option, Option) { 118 | (Some(part1(input)), None) 119 | } 120 | 121 | #[cfg(test)] 122 | mod test { 123 | use super::*; 124 | 125 | const SAMPLE: &'static str = r#"2413432311323 126 | 3215453535623 127 | 3255245654254 128 | 3446585845452 129 | 4546657867536 130 | 1438598798454 131 | 4457876987766 132 | 3637877979653 133 | 4654967986887 134 | 4564679986453 135 | 1224686865563 136 | 2546548887735 137 | 4322674655533"#; 138 | 139 | #[test] 140 | #[ignore] 141 | fn day17_sample_part1() { 142 | assert_eq!(part1(SAMPLE), 102); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /aoc2023/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use aoc2023::*; 3 | use rayon::prelude::*; 4 | use std::io::Write; 5 | use std::path::PathBuf; 6 | use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; 7 | 8 | mod inputs; 9 | 10 | #[derive(Copy, Clone)] 11 | enum Status { 12 | Done, 13 | Pending, 14 | Future, 15 | } 16 | 17 | impl Status { 18 | fn color(&self) -> Color { 19 | match self { 20 | Status::Done => Color::Green, 21 | Status::Pending => Color::Yellow, 22 | Status::Future => Color::White, 23 | } 24 | } 25 | } 26 | 27 | impl ToString for Status { 28 | fn to_string(&self) -> String { 29 | match self { 30 | Status::Done => "✓".to_string(), 31 | Status::Pending => "…".to_string(), 32 | Status::Future => " ".to_string(), 33 | } 34 | } 35 | } 36 | 37 | fn main() -> Result<()> { 38 | dotenv::dotenv().ok(); 39 | inputs::load_inputs().with_context(|| "Failed to load inputs")?; 40 | 41 | let days: Vec = if let Some(day) = std::env::args().skip(1).next() { 42 | vec![day 43 | .parse() 44 | .with_context(|| format!("Failed to parse day: {}", day))?] 45 | } else { 46 | (1..=25).collect() 47 | }; 48 | 49 | let results: Vec<((Option, Option), (Status, Status), f64)> = days 50 | .par_iter() 51 | .map(|day| { 52 | let input = 53 | std::fs::read_to_string(PathBuf::from("inputs").join(format!("day{:02}.txt", day))) 54 | .ok(); 55 | let has_input = input.is_some(); 56 | let now = std::time::Instant::now(); 57 | let res = match (day, input) { 58 | (1, Some(input)) => (day01::run(&input), true), 59 | (2, Some(input)) => (day02::run(&input), true), 60 | (3, Some(input)) => (day03::run(&input), true), 61 | (4, Some(input)) => (day04::run(&input), true), 62 | (5, Some(input)) => (day05::run(&input), true), 63 | (6, Some(input)) => (day06::run(&input), true), 64 | (7, Some(input)) => (day07::run(&input), true), 65 | (8, Some(input)) => (day08::run(&input), true), 66 | (9, Some(input)) => (day09::run(&input), true), 67 | (10, Some(input)) => (day10::run(&input), true), 68 | (11, Some(input)) => (day11::run(&input), true), 69 | (14, Some(input)) => (day14::run(&input), true), 70 | (15, Some(input)) => (day15::run(&input), true), 71 | (16, Some(input)) => (day16::run(&input), true), 72 | (17, Some(input)) => (day17::run(&input), true), 73 | (18, Some(input)) => (day18::run(&input), true), 74 | (19, Some(input)) => (day19::run(&input), true), 75 | _ => ((None, None), has_input), 76 | }; 77 | (res, now.elapsed().as_secs_f64()) 78 | }) 79 | .map(|day| match day { 80 | (((p1, p2), true), elapsed) => ( 81 | (p1, p2), 82 | match (p1.is_some(), p2.is_some()) { 83 | (true, true) => (Status::Done, Status::Done), 84 | (true, false) => (Status::Done, Status::Pending), 85 | (false, true) => (Status::Pending, Status::Pending), 86 | (false, false) => (Status::Pending, Status::Pending), 87 | }, 88 | elapsed, 89 | ), 90 | (((p1, p2), false), elapsed) => ((p1, p2), (Status::Future, Status::Future), elapsed), 91 | }) 92 | .collect(); 93 | 94 | let results: Vec<((String, Status), (String, Status), String)> = results 95 | .into_iter() 96 | .map(|((p1, p2), (s1, s2), elapsed)| { 97 | let p1 = p1 98 | .map(|p1| p1.to_string()) 99 | .unwrap_or_else(|| s1.to_string()); 100 | let p2 = p2 101 | .map(|p2| p2.to_string()) 102 | .unwrap_or_else(|| s2.to_string()); 103 | ((p1, s1), (p2, s2), format!("{:.6}s", elapsed)) 104 | }) 105 | .collect(); 106 | 107 | let (p1_width, p2_width) = 108 | results 109 | .iter() 110 | .fold((0, 0), |(p1_width, p2_width), ((p1, _), (p2, _), _)| { 111 | (p1_width.max(p1.len()), p2_width.max(p2.len())) 112 | }); 113 | let p1_width = p1_width.max("Part 1".len()); 114 | let p2_width = p2_width.max("Part 2".len()); 115 | 116 | println!( 117 | "╒═════╤═{p1:═3} │ ")?; 140 | stdout.set_color(ColorSpec::new().set_fg(Some(s1.color())))?; 141 | write!(stdout, "{p1:>width1$}", p1 = p1, width1 = p1_width,)?; 142 | stdout.reset()?; 143 | write!(stdout, " │ ",)?; 144 | stdout.set_color(ColorSpec::new().set_fg(Some(s2.color())))?; 145 | write!(stdout, "{p2:>width2$}", p2 = p2, width2 = p2_width,)?; 146 | write!(stdout, " │ ",)?; 147 | if elapsed != "0.000000s" { 148 | stdout.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?; 149 | write!(stdout, "{elapsed}")?; 150 | } else { 151 | stdout.set_color(ColorSpec::new().set_fg(None))?; 152 | write!(stdout, " ")?; 153 | } 154 | stdout.reset()?; 155 | writeln!(stdout, " │")?; 156 | } 157 | println!( 158 | "╘═════╧═{p1:═ IResult<&str, Direction> { 30 | alt(( 31 | map(char('U'), |_| Direction::Up), 32 | map(char('D'), |_| Direction::Down), 33 | map(char('L'), |_| Direction::Left), 34 | map(char('R'), |_| Direction::Right), 35 | ))(input) 36 | } 37 | 38 | fn parse_distance(input: &str) -> IResult<&str, usize> { 39 | map_res(digit1, |s: &str| s.parse::())(input) 40 | } 41 | 42 | fn from_hex(input: &str) -> Result { 43 | usize::from_str_radix(input, 16) 44 | } 45 | 46 | fn is_hex_digit(c: char) -> bool { 47 | c.is_digit(16) 48 | } 49 | 50 | fn parse_part2_distance(input: &str) -> IResult<&str, usize> { 51 | map_res(take_while_m_n(5, 5, is_hex_digit), from_hex)(input) 52 | } 53 | 54 | fn parse_part2_direction(input: &str) -> IResult<&str, Direction> { 55 | alt(( 56 | map(char('3'), |_| Direction::Up), 57 | map(char('1'), |_| Direction::Down), 58 | map(char('2'), |_| Direction::Left), 59 | map(char('0'), |_| Direction::Right), 60 | ))(input) 61 | } 62 | 63 | fn parse_part2_hex(input: &str) -> IResult<&str, Instruction> { 64 | pair( 65 | preceded(char('#'), parse_part2_distance), 66 | parse_part2_direction, 67 | )(input) 68 | .map(|(input, (distance, direction))| { 69 | ( 70 | input, 71 | Instruction { 72 | distance: distance as isize, 73 | direction, 74 | }, 75 | ) 76 | }) 77 | } 78 | 79 | pub fn parse_instruction_part_1(input: &str) -> IResult<&str, Instruction> { 80 | map( 81 | tuple((parse_direction, space1, parse_distance, many0(anychar))), 82 | |(direction, _, distance, _)| Instruction { 83 | direction, 84 | distance: distance as isize, 85 | }, 86 | )(input) 87 | } 88 | 89 | pub fn parse_instruction_part_2(input: &str) -> IResult<&str, Instruction> { 90 | map( 91 | tuple(( 92 | parse_direction, 93 | space1, 94 | parse_distance, 95 | space1, 96 | delimited(char('('), parse_part2_hex, char(')')), 97 | )), 98 | |(_, _, _, _, instruction)| instruction, 99 | )(input) 100 | } 101 | 102 | #[cfg(test)] 103 | mod test { 104 | use super::*; 105 | 106 | #[test] 107 | fn can_parse_direction() { 108 | assert_eq!(parse_direction("U"), Ok(("", Direction::Up))); 109 | assert_eq!(parse_direction("D"), Ok(("", Direction::Down))); 110 | assert_eq!(parse_direction("L"), Ok(("", Direction::Left))); 111 | assert_eq!(parse_direction("R"), Ok(("", Direction::Right))); 112 | } 113 | 114 | #[test] 115 | fn can_parse_distance() { 116 | assert_eq!(parse_distance("1"), Ok(("", 1))); 117 | assert_eq!(parse_distance("12"), Ok(("", 12))); 118 | assert_eq!(parse_distance("123"), Ok(("", 123))); 119 | } 120 | 121 | #[test] 122 | fn can_parse_instruction() { 123 | assert_eq!( 124 | parse_instruction_part_1("U 1 (#000000)"), 125 | Ok(( 126 | "", 127 | Instruction { 128 | direction: Direction::Up, 129 | distance: 1, 130 | } 131 | )) 132 | ); 133 | assert_eq!( 134 | parse_instruction_part_1("D 12 (#ffffff)"), 135 | Ok(( 136 | "", 137 | Instruction { 138 | direction: Direction::Down, 139 | distance: 12, 140 | } 141 | )) 142 | ); 143 | assert_eq!( 144 | parse_instruction_part_1("L 123 (#ff0000)"), 145 | Ok(( 146 | "", 147 | Instruction { 148 | direction: Direction::Left, 149 | distance: 123, 150 | } 151 | )) 152 | ); 153 | assert_eq!( 154 | parse_instruction_part_1("R 1234 (#00ff00)"), 155 | Ok(( 156 | "", 157 | Instruction { 158 | direction: Direction::Right, 159 | distance: 1234, 160 | } 161 | )) 162 | ); 163 | } 164 | 165 | #[test] 166 | fn can_parse_instruction_part_2() { 167 | assert_eq!( 168 | parse_instruction_part_2("R 6 (#70c710)"), 169 | Ok(( 170 | "", 171 | Instruction { 172 | direction: Direction::Right, 173 | distance: 461937, 174 | } 175 | )) 176 | ); 177 | } 178 | } 179 | } 180 | 181 | fn parse_input_part1(input: &str) -> Vec { 182 | use parse::parse_instruction_part_1; 183 | 184 | input 185 | .par_lines() 186 | .map(|line| { 187 | parse_instruction_part_1(line) 188 | .expect("can parse instruction") 189 | .1 190 | }) 191 | .collect() 192 | } 193 | 194 | fn parse_input_part2(input: &str) -> Vec { 195 | use parse::parse_instruction_part_2; 196 | 197 | input 198 | .par_lines() 199 | .map(|line| { 200 | parse_instruction_part_2(line) 201 | .expect("can parse instruction") 202 | .1 203 | }) 204 | .collect() 205 | } 206 | 207 | fn generate_polygon(instructions: Vec) -> Vec<(isize, isize)> { 208 | instructions 209 | .into_iter() 210 | .fold(vec![(0isize, 0isize)], |mut polygon, instruction| { 211 | let (mut x, mut y) = polygon.last().expect("polygon has at least one point"); 212 | match instruction.direction { 213 | Direction::Up => y -= instruction.distance, 214 | Direction::Down => y += instruction.distance, 215 | Direction::Left => x -= instruction.distance, 216 | Direction::Right => x += instruction.distance, 217 | }; 218 | 219 | polygon.push((x, y)); 220 | polygon 221 | }) 222 | } 223 | 224 | fn shoelace(polygon: &Vec<(isize, isize)>) -> usize { 225 | let perimeter = polygon 226 | .iter() 227 | .zip(polygon.iter().skip(1)) 228 | .map(|((x1, y1), (x2, y2))| (y2 - y1).abs() + (x2 - x1).abs()) 229 | .sum::() as usize; 230 | 231 | let double_area = polygon 232 | .iter() 233 | .zip(polygon.iter().skip(1)) 234 | .map(|((x1, y1), (x2, y2))| (x1 * y2) - (x2 * y1)) 235 | .sum::() 236 | .abs() as usize 237 | + perimeter; 238 | (double_area / 2) + 1 239 | } 240 | 241 | pub fn part1(input: &str) -> usize { 242 | let instructions = parse_input_part1(input); 243 | let polygon = generate_polygon(instructions); 244 | shoelace(&polygon) 245 | } 246 | 247 | pub fn part2(input: &str) -> usize { 248 | let instructions = parse_input_part2(input); 249 | let polygon = generate_polygon(instructions); 250 | shoelace(&polygon) 251 | } 252 | 253 | pub fn run(input: &str) -> (Option, Option) { 254 | (Some(part1(input)), Some(part2(input))) 255 | } 256 | 257 | #[cfg(test)] 258 | mod test { 259 | use super::*; 260 | 261 | const SAMPLE: &'static str = r#"R 6 (#70c710) 262 | D 5 (#0dc571) 263 | L 2 (#5713f0) 264 | D 2 (#d2c081) 265 | R 2 (#59c680) 266 | D 2 (#411b91) 267 | L 5 (#8ceee2) 268 | U 2 (#caa173) 269 | L 1 (#1b58a2) 270 | U 2 (#caa171) 271 | R 2 (#7807d2) 272 | U 3 (#a77fa3) 273 | L 2 (#015232) 274 | U 2 (#7a21e3)"#; 275 | 276 | #[test] 277 | fn day17_sample_part1() { 278 | assert_eq!(part1(SAMPLE), 62); 279 | } 280 | 281 | #[test] 282 | fn day17_sample_part2() { 283 | assert_eq!(part2(SAMPLE), 952408144115); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /aoc2023/src/day14/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use rayon::prelude::*; 4 | 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 6 | enum Tile { 7 | Open, 8 | Block, 9 | Rock, 10 | } 11 | 12 | impl From for Tile { 13 | fn from(c: char) -> Self { 14 | match c { 15 | '.' => Tile::Open, 16 | '#' => Tile::Block, 17 | 'O' => Tile::Rock, 18 | _ => panic!("Invalid tile: {}", c), 19 | } 20 | } 21 | } 22 | 23 | fn parse(input: &str) -> Vec> { 24 | input 25 | .lines() 26 | .map(|line| line.chars().map(Tile::from).collect()) 27 | .collect() 28 | } 29 | 30 | fn calculate_load(map: &Vec>) -> usize { 31 | let (width, height) = (map[0].len(), map.len()); 32 | (0..width) 33 | .into_par_iter() 34 | .map(|x| { 35 | let mut next_y = 0; 36 | let mut weighted_sum = 0; 37 | for y in 0..height { 38 | match map[y][x] { 39 | Tile::Open => {} 40 | Tile::Block => next_y = y + 1, 41 | Tile::Rock => { 42 | weighted_sum += height - next_y; 43 | next_y += 1; 44 | } 45 | } 46 | } 47 | weighted_sum 48 | }) 49 | .sum() 50 | } 51 | 52 | pub fn part1(input: &str) -> usize { 53 | let map = parse(input); 54 | calculate_load(&map) 55 | } 56 | 57 | #[derive(Copy, Clone)] 58 | enum Rotation { 59 | Zero, 60 | Ninety, 61 | OneEighty, 62 | TwoSeventy, 63 | } 64 | 65 | struct RotatableMap { 66 | map: Vec>, 67 | size: usize, 68 | } 69 | 70 | impl std::fmt::Display for RotatableMap { 71 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 72 | for row in &self.map { 73 | for tile in row { 74 | match tile { 75 | Tile::Open => write!(f, ".")?, 76 | Tile::Block => write!(f, "#")?, 77 | Tile::Rock => write!(f, "O")?, 78 | } 79 | } 80 | writeln!(f)?; 81 | } 82 | Ok(()) 83 | } 84 | } 85 | 86 | impl RotatableMap { 87 | fn new(map: Vec>) -> Self { 88 | let (width, height) = (map[0].len(), map.len()); 89 | assert_eq!(width, height); // makes things easier 90 | let size = width; 91 | Self { map, size } 92 | } 93 | 94 | fn map_coords(&self, (x, y): (usize, usize), rotation: Rotation) -> (usize, usize) { 95 | match rotation { 96 | Rotation::Zero => (x, y), 97 | Rotation::Ninety => (self.size - y - 1, x), 98 | Rotation::OneEighty => (x, self.size - y - 1), 99 | Rotation::TwoSeventy => (y, self.size - x - 1), 100 | } 101 | } 102 | 103 | fn get(&self, (x, y): (usize, usize), rotation: Rotation) -> Tile { 104 | let (x, y) = self.map_coords((x, y), rotation); 105 | self.map[y][x] 106 | } 107 | 108 | fn set(&mut self, (x, y): (usize, usize), rotation: Rotation, tile: Tile) { 109 | let (x, y) = self.map_coords((x, y), rotation); 110 | self.map[y][x] = tile; 111 | } 112 | 113 | fn tilt(&mut self, rotation: Rotation) { 114 | for x in 0..self.size { 115 | let mut next_y = 0; 116 | for y in 0..self.size { 117 | match self.get((x, y), rotation) { 118 | Tile::Open => {} 119 | Tile::Block => next_y = y + 1, 120 | Tile::Rock => { 121 | self.set((x, y), rotation, Tile::Open); 122 | self.set((x, next_y), rotation, Tile::Rock); 123 | next_y += 1; 124 | } 125 | } 126 | } 127 | } 128 | } 129 | } 130 | 131 | fn spin_cycle(map: &mut RotatableMap) { 132 | map.tilt(Rotation::Zero); 133 | map.tilt(Rotation::TwoSeventy); 134 | map.tilt(Rotation::OneEighty); 135 | map.tilt(Rotation::Ninety); 136 | } 137 | 138 | fn hash_map(map: &Vec>) -> usize { 139 | use std::hash::{Hash, Hasher}; 140 | let mut hasher = std::collections::hash_map::DefaultHasher::new(); 141 | map.hash(&mut hasher); 142 | hasher.finish() as usize 143 | } 144 | 145 | // TODO: SO CLOSE! 146 | pub fn part2(input: &str) -> usize { 147 | let map = parse(input); 148 | let mut map = RotatableMap::new(map); 149 | 150 | let mut history: HashMap = HashMap::new(); 151 | 152 | let mut i = 0; 153 | loop { 154 | let hash = hash_map(&map.map); 155 | if history.contains_key(&hash) { 156 | // found the cycle 157 | let cycle_start = history[&hash]; 158 | let cycle_length = i - cycle_start; 159 | let remaining = 1_000_000_000 - cycle_start; 160 | let remaining = remaining % cycle_length; 161 | for _ in 0..remaining { 162 | spin_cycle(&mut map); 163 | } 164 | return calculate_load(&map.map); 165 | } else { 166 | history.insert(hash, i); 167 | spin_cycle(&mut map); 168 | i += 1; 169 | } 170 | } 171 | } 172 | 173 | pub fn run(input: &str) -> (Option, Option) { 174 | (Some(part1(input)), None) 175 | } 176 | 177 | #[cfg(test)] 178 | mod test { 179 | use super::*; 180 | 181 | const SAMPLE: &'static str = r#"O....#.... 182 | O.OO#....# 183 | .....##... 184 | OO.#O....O 185 | .O.....O#. 186 | O.#..O.#.# 187 | ..O..#O..O 188 | .......O.. 189 | #....###.. 190 | #OO..#...."#; 191 | 192 | #[test] 193 | fn day14_sample_part1() { 194 | assert_eq!(part1(SAMPLE), 136); 195 | } 196 | 197 | #[test] 198 | #[ignore] 199 | fn day14_sample_part2() { 200 | assert_eq!(part2(SAMPLE), 64); 201 | } 202 | 203 | #[test] 204 | fn can_map_coords() { 205 | let map = RotatableMap::new(parse(SAMPLE)); 206 | assert_eq!(map.map_coords((0, 0), Rotation::Zero), (0, 0)); 207 | assert_eq!(map.map_coords((0, 9), Rotation::Zero), (0, 9)); 208 | 209 | assert_eq!(map.map_coords((0, 0), Rotation::Ninety), (9, 0)); 210 | assert_eq!(map.map_coords((9, 0), Rotation::Ninety), (9, 9)); 211 | 212 | assert_eq!(map.map_coords((0, 0), Rotation::OneEighty), (0, 9)); 213 | assert_eq!(map.map_coords((0, 9), Rotation::OneEighty), (0, 0)); 214 | 215 | assert_eq!(map.map_coords((0, 0), Rotation::TwoSeventy), (0, 9)); 216 | assert_eq!(map.map_coords((9, 0), Rotation::TwoSeventy), (0, 0)); 217 | } 218 | 219 | #[test] 220 | fn can_tilt() { 221 | let mut map = RotatableMap::new(parse(SAMPLE)); 222 | map.tilt(Rotation::Zero); 223 | assert_eq!( 224 | map.to_string(), 225 | r#"OOOO.#.O.. 226 | OO..#....# 227 | OO..O##..O 228 | O..#.OO... 229 | ........#. 230 | ..#....#.# 231 | ..O..#.O.O 232 | ..O....... 233 | #....###.. 234 | #....#.... 235 | "# 236 | ); 237 | 238 | map.tilt(Rotation::Ninety); 239 | assert_eq!( 240 | map.to_string(), 241 | r#".OOOO#...O 242 | ..OO#....# 243 | ..OOO##..O 244 | ..O#....OO 245 | ........#. 246 | ..#....#.# 247 | ....O#..OO 248 | .........O 249 | #....###.. 250 | #....#.... 251 | "# 252 | ); 253 | 254 | map.tilt(Rotation::OneEighty); 255 | assert_eq!( 256 | map.to_string(), 257 | r#"...OO#...O 258 | ..OO#....# 259 | ..OO.##... 260 | ..O#....OO 261 | ..O.....#O 262 | ..#....#.# 263 | .....#.... 264 | .......... 265 | #...O###.O 266 | #O..O#..OO 267 | "# 268 | ); 269 | 270 | map.tilt(Rotation::TwoSeventy); 271 | assert_eq!( 272 | map.to_string(), 273 | r#"OO...#O... 274 | OO..#....# 275 | OO...##... 276 | O..#OO.... 277 | O.......#O 278 | ..#....#.# 279 | .....#.... 280 | .......... 281 | #O...###O. 282 | #OO..#OO.. 283 | "# 284 | ); 285 | } 286 | 287 | #[test] 288 | fn can_spin_cycle() { 289 | let mut map = RotatableMap::new(parse(SAMPLE)); 290 | spin_cycle(&mut map); 291 | assert_eq!( 292 | map.to_string(), 293 | r#".....#.... 294 | ....#...O# 295 | ...OO##... 296 | .OO#...... 297 | .....OOO#. 298 | .O#...O#.# 299 | ....O#.... 300 | ......OOOO 301 | #...O###.. 302 | #..OO#.... 303 | "# 304 | ); 305 | spin_cycle(&mut map); 306 | assert_eq!( 307 | map.to_string(), 308 | r#".....#.... 309 | ....#...O# 310 | .....##... 311 | ..O#...... 312 | .....OOO#. 313 | .O#...O#.# 314 | ....O#...O 315 | .......OOO 316 | #..OO###.. 317 | #.OOO#...O 318 | "# 319 | ); 320 | spin_cycle(&mut map); 321 | assert_eq!( 322 | map.to_string(), 323 | r#".....#.... 324 | ....#...O# 325 | .....##... 326 | ..O#...... 327 | .....OOO#. 328 | .O#...O#.# 329 | ....O#...O 330 | .......OOO 331 | #...O###.O 332 | #.OOO#...O 333 | "# 334 | ); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /aoc2023/src/day07/part1.rs: -------------------------------------------------------------------------------- 1 | use rayon::prelude::*; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Copy, Clone)] 5 | enum Card { 6 | Number(u8), 7 | Jack, 8 | Queen, 9 | King, 10 | Ace, 11 | } 12 | 13 | impl Ord for Card { 14 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 15 | match (self, other) { 16 | (Card::Number(a), Card::Number(b)) => a.cmp(b), 17 | (Card::Number(_), _) => std::cmp::Ordering::Less, 18 | (_, Card::Number(_)) => std::cmp::Ordering::Greater, 19 | (Card::Jack, Card::Jack) => std::cmp::Ordering::Equal, 20 | (Card::Jack, _) => std::cmp::Ordering::Less, 21 | (_, Card::Jack) => std::cmp::Ordering::Greater, 22 | (Card::Queen, Card::Queen) => std::cmp::Ordering::Equal, 23 | (Card::Queen, _) => std::cmp::Ordering::Less, 24 | (_, Card::Queen) => std::cmp::Ordering::Greater, 25 | (Card::King, Card::King) => std::cmp::Ordering::Equal, 26 | (Card::King, _) => std::cmp::Ordering::Less, 27 | (_, Card::King) => std::cmp::Ordering::Greater, 28 | (Card::Ace, Card::Ace) => std::cmp::Ordering::Equal, 29 | } 30 | } 31 | } 32 | 33 | impl Card { 34 | fn parse(c: char) -> Self { 35 | match c { 36 | 'T' => Card::Number(10), 37 | 'J' => Card::Jack, 38 | 'Q' => Card::Queen, 39 | 'K' => Card::King, 40 | 'A' => Card::Ace, 41 | n if n >= '2' && n <= '9' => Card::Number(n as u8 - '0' as u8), 42 | _ => panic!("invalid card {c}"), 43 | } 44 | } 45 | } 46 | 47 | type Cards = [Card; 5]; 48 | 49 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] 50 | enum HandType { 51 | HighCard, 52 | OnePair, 53 | TwoPair, 54 | ThreeOfAKind, 55 | FullHouse, 56 | FourOfAKind, 57 | FiveOfAKind, 58 | } 59 | 60 | #[derive(PartialEq, Eq, Copy, Clone, Ord)] 61 | struct Hand { 62 | cards: Cards, 63 | hand_type: HandType, 64 | bid: usize, 65 | } 66 | 67 | impl std::fmt::Display for Hand { 68 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 69 | for &card in &self.cards { 70 | match card { 71 | Card::Number(n) if n < 10 => write!(f, "{}", n)?, 72 | Card::Number(10) => write!(f, "T")?, 73 | Card::Jack => write!(f, "J")?, 74 | Card::Queen => write!(f, "Q")?, 75 | Card::King => write!(f, "K")?, 76 | Card::Ace => write!(f, "A")?, 77 | _ => panic!("invalid card"), 78 | } 79 | } 80 | write!(f, " ({:>3})", self.bid) 81 | } 82 | } 83 | 84 | impl std::fmt::Debug for Hand { 85 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 86 | let kind = match self.hand_type { 87 | HandType::HighCard => "HighCard", 88 | HandType::OnePair => "OnePair", 89 | HandType::TwoPair => "TwoPair", 90 | HandType::ThreeOfAKind => "ThreeOfAKind", 91 | HandType::FullHouse => "FullHouse", 92 | HandType::FourOfAKind => "FourOfAKind", 93 | HandType::FiveOfAKind => "FiveOfAKind", 94 | }; 95 | write!(f, "{self} ({kind:>12})") 96 | } 97 | } 98 | 99 | impl PartialOrd for Hand { 100 | fn partial_cmp(&self, other: &Self) -> Option { 101 | //eprintln!("comparing {self} to {other}"); 102 | match self.hand_type.cmp(&other.hand_type) { 103 | std::cmp::Ordering::Equal => { 104 | //eprintln!("hand types are equal"); 105 | for i in 0..5 { 106 | match self.cards[i].cmp(&other.cards[i]) { 107 | std::cmp::Ordering::Equal => {} 108 | o => return Some(o), 109 | } 110 | } 111 | Some(std::cmp::Ordering::Equal) 112 | } 113 | o => { 114 | //eprintln!("{o:#?}"); 115 | Some(o) 116 | } 117 | } 118 | } 119 | } 120 | 121 | impl Hand { 122 | fn parse(input: &str, hashmap: &mut HashMap) -> Self { 123 | let mut cards = [Card::Number(0); 5]; 124 | let mut parts = input.split_whitespace(); 125 | let mut card_chars = parts.next().unwrap().chars(); 126 | for i in 0..5 { 127 | cards[i] = Card::parse(card_chars.next().expect("card char")); 128 | } 129 | let bid = parts.next().unwrap().parse().unwrap(); 130 | let hand_type = Hand::hand_type(&cards, hashmap); 131 | Hand { 132 | cards, 133 | hand_type, 134 | bid, 135 | } 136 | } 137 | 138 | fn hand_type(cards: &Cards, hashmap: &mut HashMap) -> HandType { 139 | hashmap.clear(); 140 | for &card in cards { 141 | hashmap.entry(card).and_modify(|e| *e += 1).or_insert(1); 142 | } 143 | let mut counts = hashmap.values().collect::>(); 144 | debug_assert!(counts.len() <= 5); 145 | counts.sort(); 146 | 147 | match counts.as_slice() { 148 | [1, 1, 1, 1, 1] => HandType::HighCard, 149 | [1, 1, 1, 2] => HandType::OnePair, 150 | [1, 2, 2] => HandType::TwoPair, 151 | [1, 1, 3] => HandType::ThreeOfAKind, 152 | [2, 3] => HandType::FullHouse, 153 | [1, 4] => HandType::FourOfAKind, 154 | [5] => HandType::FiveOfAKind, 155 | _ => panic!("invalid hand"), 156 | } 157 | } 158 | } 159 | 160 | pub fn part1(input: &str) -> usize { 161 | let mut hands = input 162 | .par_lines() 163 | .map_with(HashMap::new(), |mut hashmap, line| { 164 | Hand::parse(line, &mut hashmap) 165 | }) 166 | .collect::>(); 167 | hands.sort(); 168 | //eprintln!("hands:"); 169 | // for (i, hand) in hands.iter().enumerate() { 170 | //eprintln!("{:02} {hand} => {}", i + 1, (i + 1) * hand.bid); 171 | // } 172 | 173 | hands 174 | .into_iter() 175 | .enumerate() 176 | .map(|(i, hand)| hand.bid * (i + 1)) 177 | .sum::() 178 | } 179 | 180 | #[cfg(test)] 181 | mod test { 182 | use super::*; 183 | 184 | const SAMPLE: &'static str = r#"32T3K 765 185 | T55J5 684 186 | KK677 28 187 | KTJJT 220 188 | QQQJA 483 189 | "#; 190 | 191 | #[test] 192 | fn day07_part1_correct_ordering() { 193 | assert!(Card::Number(2) < Card::Number(3)); 194 | assert!(Card::Number(3) < Card::Number(4)); 195 | assert!(Card::Number(4) < Card::Number(5)); 196 | assert!(Card::Number(5) < Card::Number(6)); 197 | assert!(Card::Number(6) < Card::Number(7)); 198 | assert!(Card::Number(7) < Card::Number(8)); 199 | assert!(Card::Number(8) < Card::Number(9)); 200 | assert!(Card::Number(9) < Card::Number(10)); 201 | assert!(Card::Number(10) < Card::Jack); 202 | assert!(Card::Jack < Card::Queen); 203 | assert!(Card::Queen < Card::King); 204 | assert!(Card::King < Card::Ace); 205 | 206 | assert!(HandType::HighCard < HandType::OnePair); 207 | assert!(HandType::OnePair < HandType::TwoPair); 208 | assert!(HandType::TwoPair < HandType::ThreeOfAKind); 209 | assert!(HandType::ThreeOfAKind < HandType::FullHouse); 210 | assert!(HandType::FullHouse < HandType::FourOfAKind); 211 | assert!(HandType::FourOfAKind < HandType::FiveOfAKind); 212 | } 213 | 214 | #[test] 215 | fn day07_part1_can_parse_hand() { 216 | let mut hashmap: HashMap = HashMap::new(); 217 | 218 | //eprintln!("Parsing 32T3K 765"); 219 | let hand = Hand::parse("32T3K 765", &mut hashmap); 220 | assert_eq!( 221 | hand, 222 | Hand { 223 | cards: [ 224 | Card::Number(3), 225 | Card::Number(2), 226 | Card::Number(10), 227 | Card::Number(3), 228 | Card::King 229 | ], 230 | hand_type: HandType::OnePair, 231 | bid: 765 232 | } 233 | ); 234 | 235 | //eprintln!("Parsing QQQJA 483"); 236 | let hand = Hand::parse("QQQJA 483", &mut hashmap); 237 | assert_eq!( 238 | hand, 239 | Hand { 240 | cards: [Card::Queen, Card::Queen, Card::Queen, Card::Jack, Card::Ace], 241 | hand_type: HandType::ThreeOfAKind, 242 | bid: 483 243 | } 244 | ); 245 | } 246 | 247 | #[test] 248 | fn day07_part1_can_order_hands() { 249 | let mut hashmap: HashMap = HashMap::new(); 250 | let hand1 = Hand::parse("32T3K 765", &mut hashmap); 251 | let hand2 = Hand::parse("T55J5 684", &mut hashmap); 252 | let hand3 = Hand::parse("KK677 28", &mut hashmap); 253 | let hand4 = Hand::parse("KTJJT 220", &mut hashmap); 254 | let hand5 = Hand::parse("QQQJA 483", &mut hashmap); 255 | let mut hands = vec![hand1, hand2, hand3, hand4, hand5]; 256 | hands.sort(); 257 | assert_eq!(hands, vec![hand1, hand4, hand3, hand2, hand5]); 258 | } 259 | 260 | #[test] 261 | fn day07_sample_part1() { 262 | assert_eq!(part1(SAMPLE), 6440); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /aoc2023/src/day10/mod.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, PartialEq)] 2 | enum Pipe { 3 | Horizontal, 4 | Vertical, 5 | NorthEast, 6 | NorthWest, 7 | SouthEast, 8 | SouthWest, 9 | Start, 10 | Ground, 11 | } 12 | 13 | impl From for Pipe { 14 | fn from(c: char) -> Self { 15 | match c { 16 | '-' => Self::Horizontal, 17 | '|' => Self::Vertical, 18 | 'L' => Self::NorthEast, 19 | 'J' => Self::NorthWest, 20 | '7' => Self::SouthWest, 21 | 'F' => Self::SouthEast, 22 | 'S' => Self::Start, 23 | '.' => Self::Ground, 24 | _ => panic!("invalid pipe {}", c), 25 | } 26 | } 27 | } 28 | 29 | struct LoopStart { 30 | start: (usize, usize), 31 | map: Vec>, 32 | } 33 | 34 | impl From>> for LoopStart { 35 | fn from(map: Vec>) -> Self { 36 | let start = find_start(&map); 37 | Self { start, map } 38 | } 39 | } 40 | 41 | impl IntoIterator for LoopStart { 42 | type IntoIter = Loop; 43 | type Item = LoopStep; 44 | 45 | fn into_iter(self) -> Self::IntoIter { 46 | Loop { 47 | current: self.start, 48 | previous: self.start, 49 | distance: 0, 50 | map: self.map, 51 | } 52 | } 53 | } 54 | 55 | struct Loop { 56 | current: (usize, usize), 57 | previous: (usize, usize), 58 | distance: usize, 59 | map: Vec>, 60 | } 61 | 62 | struct LoopStep { 63 | coords: (usize, usize), 64 | distance: usize, 65 | } 66 | 67 | impl Iterator for Loop { 68 | type Item = LoopStep; 69 | 70 | fn next(&mut self) -> Option { 71 | let (x, y) = self.current; 72 | let pipe = self.map[y][x]; 73 | 74 | let old = self.current; 75 | // assume we never have to deal with overflowing map boundaries 76 | // TODO: use directions to make this less verbose 77 | match pipe { 78 | Pipe::Start if self.distance == 0 => { 79 | match self.map[y][x + 1] { 80 | Pipe::Horizontal | Pipe::NorthWest | Pipe::SouthWest => { 81 | self.current = (x + 1, y); 82 | } 83 | _ => {} 84 | } 85 | if self.current == self.previous { 86 | match self.map[y + 1][x] { 87 | Pipe::Vertical | Pipe::NorthWest | Pipe::NorthEast => { 88 | self.current = (x, y + 1); 89 | } 90 | _ => {} 91 | } 92 | } 93 | if self.current == self.previous { 94 | panic!("didn't find any pipes connecting to start!"); 95 | } 96 | } 97 | Pipe::Start => { 98 | // back to start (distance > 0) 99 | return None; 100 | } 101 | Pipe::Horizontal => { 102 | if self.previous.0 < x { 103 | // moving right 104 | self.current = (x + 1, y); 105 | } else { 106 | // moving left 107 | self.current = (x - 1, y); 108 | } 109 | } 110 | Pipe::Vertical => { 111 | if self.previous.1 < y { 112 | // moving down 113 | self.current = (x, y + 1); 114 | } else { 115 | // moving up 116 | self.current = (x, y - 1); 117 | } 118 | } 119 | Pipe::NorthEast => { 120 | if self.previous.1 < y { 121 | // moving right 122 | self.current = (x + 1, y); 123 | } else { 124 | // moving up 125 | self.current = (x, y - 1); 126 | } 127 | } 128 | Pipe::NorthWest => { 129 | if self.previous.1 < y { 130 | // moving left 131 | self.current = (x - 1, y); 132 | } else { 133 | // moving up 134 | self.current = (x, y - 1); 135 | } 136 | } 137 | Pipe::SouthEast => { 138 | if self.previous.1 > y { 139 | // moving right 140 | self.current = (x + 1, y); 141 | } else { 142 | // moving down 143 | self.current = (x, y + 1); 144 | } 145 | } 146 | Pipe::SouthWest => { 147 | if self.previous.1 > y { 148 | // moving left 149 | self.current = (x - 1, y); 150 | } else { 151 | // moving down 152 | self.current = (x, y + 1); 153 | } 154 | } 155 | Pipe::Ground => { 156 | panic!("your pipe is leaking"); 157 | } 158 | } 159 | self.previous = old; 160 | 161 | self.distance += 1; 162 | Some(LoopStep { 163 | coords: self.current, 164 | distance: self.distance, 165 | }) 166 | } 167 | } 168 | 169 | fn parse(input: &str) -> Vec> { 170 | input 171 | .lines() 172 | .map(|line| line.chars().map(From::from).collect()) 173 | .collect() 174 | } 175 | 176 | fn find_start(map: &Vec>) -> (usize, usize) { 177 | for (y, row) in map.iter().enumerate() { 178 | for (x, pipe) in row.iter().enumerate() { 179 | if *pipe == Pipe::Start { 180 | return (x, y); 181 | } 182 | } 183 | } 184 | panic!("no start found"); 185 | } 186 | 187 | pub fn part1(input: &str) -> usize { 188 | let map = parse(input); 189 | let loop_start = LoopStart::from(map); 190 | loop_start 191 | .into_iter() 192 | .last() 193 | .map(|s| s.distance) 194 | .expect("loop") 195 | / 2 196 | } 197 | 198 | pub fn part2(input: &str) -> usize { 199 | let map = parse(input); 200 | let start = find_start(&map); 201 | let mut pipe: Vec>> = vec![vec![None; map[0].len()]; map.len()]; 202 | pipe[start.1][start.0] = Some(0); 203 | let loop_start = LoopStart::from(map); 204 | let mut max_distance = 0; 205 | for step in loop_start.into_iter() { 206 | let (x, y) = step.coords; 207 | pipe[y][x] = Some(step.distance); 208 | max_distance = step.distance; 209 | } 210 | 211 | let mut inside_count = 0; 212 | for y in 0..pipe.len() { 213 | let mut winding: isize = 0; 214 | for x in 0..pipe[y].len() { 215 | if pipe[y][x].is_none() { 216 | if winding != 0 { 217 | inside_count += 1; 218 | } 219 | continue; 220 | } 221 | let d_this = pipe[y][x].unwrap(); 222 | 223 | if y + 1 >= pipe.len() { 224 | continue; 225 | } 226 | if let Some(d_below) = pipe[y + 1][x] { 227 | let dy = (d_this as isize - d_below as isize) % max_distance as isize; 228 | if dy == 1 { 229 | // moved down 230 | winding += 1; 231 | } else if dy == -1 { 232 | // moved up 233 | winding -= 1; 234 | } 235 | } 236 | } 237 | } 238 | 239 | inside_count 240 | } 241 | 242 | pub fn run(input: &str) -> (Option, Option) { 243 | (Some(part1(input)), Some(part2(input))) 244 | } 245 | 246 | #[cfg(test)] 247 | mod test { 248 | use super::*; 249 | 250 | const SAMPLE: &'static str = r#"..F7. 251 | .FJ|. 252 | SJ.L7 253 | |F--J 254 | LJ..."#; 255 | 256 | #[test] 257 | fn day10_sample_part1() { 258 | assert_eq!(part1(SAMPLE), 8); 259 | } 260 | 261 | const SAMPLE1: &'static str = r#"........... 262 | .S-------7. 263 | .|F-----7|. 264 | .||.....||. 265 | .||.....||. 266 | .|L-7.F-J|. 267 | .|..|.|..|. 268 | .L--J.L--J. 269 | ........... 270 | "#; 271 | const SAMPLE2: &'static str = r#".......... 272 | .S------7. 273 | .|F----7|. 274 | .||....||. 275 | .||....||. 276 | .|L-7F-J|. 277 | .|..||..|. 278 | .L--JL--J. 279 | .......... 280 | "#; 281 | const SAMPLE3: &'static str = r#".F----7F7F7F7F-7.... 282 | .|F--7||||||||FJ.... 283 | .||.FJ||||||||L7.... 284 | FJL7L7LJLJ||LJ.L-7.. 285 | L--J.L7...LJS7F-7L7. 286 | ....F-J..F7FJ|L7L7L7 287 | ....L7.F7||L7|.L7L7| 288 | .....|FJLJ|FJ|F7|.LJ 289 | ....FJL-7.||.||||... 290 | ....L---J.LJ.LJLJ... 291 | "#; 292 | const SAMPLE4: &'static str = r#"FF7FSF7F7F7F7F7F---7 293 | L|LJ||||||||||||F--J 294 | FL-7LJLJ||||||LJL-77 295 | F--JF--7||LJLJ7F7FJ- 296 | L---JF-JLJ.||-FJLJJ7 297 | |F|F-JF---7F7-L7L|7| 298 | |FFJF7L7F-JF7|JL---7 299 | 7-L-JL7||F7|L7F-7F7| 300 | L.L7LFJ|||||FJL7||LJ 301 | L7JLJL-JLJLJL--JLJ.L"#; 302 | 303 | #[test] 304 | fn day10_sample1_part2() { 305 | assert_eq!(part2(SAMPLE1), 4); 306 | } 307 | 308 | #[test] 309 | fn day10_sample2_part2() { 310 | assert_eq!(part2(SAMPLE2), 4); 311 | } 312 | 313 | #[test] 314 | fn day10_sample3_part2() { 315 | assert_eq!(part2(SAMPLE3), 8); 316 | } 317 | 318 | #[test] 319 | fn day10_sample4_part2() { 320 | assert_eq!(part2(SAMPLE4), 10); 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /aoc2023/src/day07/part2.rs: -------------------------------------------------------------------------------- 1 | use rayon::prelude::*; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Copy, Clone)] 5 | enum Card { 6 | Joker, 7 | Number(u8), 8 | Queen, 9 | King, 10 | Ace, 11 | } 12 | 13 | impl Ord for Card { 14 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 15 | match (self, other) { 16 | (Card::Joker, Card::Joker) => std::cmp::Ordering::Equal, 17 | (Card::Joker, _) => std::cmp::Ordering::Less, 18 | (_, Card::Joker) => std::cmp::Ordering::Greater, 19 | (Card::Number(a), Card::Number(b)) => a.cmp(b), 20 | (Card::Number(_), _) => std::cmp::Ordering::Less, 21 | (_, Card::Number(_)) => std::cmp::Ordering::Greater, 22 | (Card::Queen, Card::Queen) => std::cmp::Ordering::Equal, 23 | (Card::Queen, _) => std::cmp::Ordering::Less, 24 | (_, Card::Queen) => std::cmp::Ordering::Greater, 25 | (Card::King, Card::King) => std::cmp::Ordering::Equal, 26 | (Card::King, _) => std::cmp::Ordering::Less, 27 | (_, Card::King) => std::cmp::Ordering::Greater, 28 | (Card::Ace, Card::Ace) => std::cmp::Ordering::Equal, 29 | } 30 | } 31 | } 32 | 33 | impl Card { 34 | fn parse(c: char) -> Self { 35 | match c { 36 | 'J' => Card::Joker, 37 | 'T' => Card::Number(10), 38 | 'Q' => Card::Queen, 39 | 'K' => Card::King, 40 | 'A' => Card::Ace, 41 | n if n >= '2' && n <= '9' => Card::Number(n as u8 - '0' as u8), 42 | _ => panic!("invalid card {c}"), 43 | } 44 | } 45 | } 46 | 47 | type Cards = [Card; 5]; 48 | 49 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] 50 | enum HandType { 51 | HighCard, 52 | OnePair, 53 | TwoPair, 54 | ThreeOfAKind, 55 | FullHouse, 56 | FourOfAKind, 57 | FiveOfAKind, 58 | } 59 | 60 | #[derive(PartialEq, Eq, Copy, Clone, Ord)] 61 | struct Hand { 62 | cards: Cards, 63 | hand_type: HandType, 64 | bid: usize, 65 | } 66 | 67 | impl std::fmt::Display for Hand { 68 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 69 | for &card in &self.cards { 70 | match card { 71 | Card::Joker => write!(f, "J")?, 72 | Card::Number(n) if n < 10 => write!(f, "{}", n)?, 73 | Card::Number(10) => write!(f, "T")?, 74 | Card::Queen => write!(f, "Q")?, 75 | Card::King => write!(f, "K")?, 76 | Card::Ace => write!(f, "A")?, 77 | _ => panic!("invalid card"), 78 | } 79 | } 80 | write!(f, " ({:>3})", self.bid) 81 | } 82 | } 83 | 84 | impl std::fmt::Debug for Hand { 85 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 86 | let kind = match self.hand_type { 87 | HandType::HighCard => "HighCard", 88 | HandType::OnePair => "OnePair", 89 | HandType::TwoPair => "TwoPair", 90 | HandType::ThreeOfAKind => "ThreeOfAKind", 91 | HandType::FullHouse => "FullHouse", 92 | HandType::FourOfAKind => "FourOfAKind", 93 | HandType::FiveOfAKind => "FiveOfAKind", 94 | }; 95 | write!(f, "{self} ({kind:>12})") 96 | } 97 | } 98 | 99 | impl PartialOrd for Hand { 100 | fn partial_cmp(&self, other: &Self) -> Option { 101 | match self.hand_type.cmp(&other.hand_type) { 102 | std::cmp::Ordering::Equal => { 103 | for i in 0..5 { 104 | match self.cards[i].cmp(&other.cards[i]) { 105 | std::cmp::Ordering::Equal => {} 106 | o => return Some(o), 107 | } 108 | } 109 | Some(std::cmp::Ordering::Equal) 110 | } 111 | o => Some(o), 112 | } 113 | } 114 | } 115 | 116 | impl Hand { 117 | fn parse(input: &str, hashmap: &mut HashMap) -> Self { 118 | let mut cards = [Card::Number(0); 5]; 119 | let mut parts = input.split_whitespace(); 120 | let mut card_chars = parts.next().unwrap().chars(); 121 | for i in 0..5 { 122 | cards[i] = Card::parse(card_chars.next().expect("card char")); 123 | } 124 | let bid = parts.next().unwrap().parse().unwrap(); 125 | let hand_type = Hand::hand_type(&cards, hashmap); 126 | Hand { 127 | cards, 128 | hand_type, 129 | bid, 130 | } 131 | } 132 | 133 | fn hand_type(cards: &Cards, hashmap: &mut HashMap) -> HandType { 134 | hashmap.clear(); 135 | for &card in cards { 136 | hashmap.entry(card).and_modify(|e| *e += 1).or_insert(1); 137 | } 138 | let joker_count = hashmap.remove(&Card::Joker).unwrap_or(0); 139 | let mut counts = hashmap.values().collect::>(); 140 | debug_assert!(counts.len() <= 5); 141 | counts.sort(); 142 | 143 | if joker_count == 0 { 144 | match counts.as_slice() { 145 | [1, 1, 1, 1, 1] => HandType::HighCard, 146 | [1, 1, 1, 2] => HandType::OnePair, 147 | [1, 2, 2] => HandType::TwoPair, 148 | [1, 1, 3] => HandType::ThreeOfAKind, 149 | [2, 3] => HandType::FullHouse, 150 | [1, 4] => HandType::FourOfAKind, 151 | [5] => HandType::FiveOfAKind, 152 | _ => panic!("invalid hand"), 153 | } 154 | } else { 155 | match counts.len() { 156 | 0 | 1 => HandType::FiveOfAKind, 157 | 2 => { 158 | if counts[0] == &1 { 159 | HandType::FourOfAKind 160 | } else { 161 | HandType::FullHouse 162 | } 163 | } 164 | 3 => { 165 | if counts[0] == &1 { 166 | HandType::ThreeOfAKind 167 | } else { 168 | HandType::TwoPair 169 | } 170 | } 171 | 4 => HandType::OnePair, 172 | _ => panic!("invalid hand: {}; cards: {cards:?}", counts.len()), 173 | } 174 | } 175 | } 176 | } 177 | 178 | pub fn part2(input: &str) -> usize { 179 | let mut hands = input 180 | .par_lines() 181 | .map_with(HashMap::new(), |mut hashmap, line| { 182 | Hand::parse(line, &mut hashmap) 183 | }) 184 | .collect::>(); 185 | hands.sort(); 186 | 187 | hands 188 | .into_iter() 189 | .enumerate() 190 | .map(|(i, hand)| hand.bid * (i + 1)) 191 | .sum::() 192 | } 193 | 194 | #[cfg(test)] 195 | mod test { 196 | use super::*; 197 | 198 | const SAMPLE: &'static str = r#"32T3K 765 199 | T55J5 684 200 | KK677 28 201 | KTJJT 220 202 | QQQJA 483 203 | "#; 204 | 205 | #[test] 206 | fn day07_part1_correct_ordering() { 207 | assert!(Card::Joker < Card::Number(2)); 208 | assert!(Card::Number(2) < Card::Number(3)); 209 | assert!(Card::Number(3) < Card::Number(4)); 210 | assert!(Card::Number(4) < Card::Number(5)); 211 | assert!(Card::Number(5) < Card::Number(6)); 212 | assert!(Card::Number(6) < Card::Number(7)); 213 | assert!(Card::Number(7) < Card::Number(8)); 214 | assert!(Card::Number(8) < Card::Number(9)); 215 | assert!(Card::Number(9) < Card::Number(10)); 216 | assert!(Card::Number(10) < Card::Queen); 217 | assert!(Card::Queen < Card::King); 218 | assert!(Card::King < Card::Ace); 219 | 220 | assert!(HandType::HighCard < HandType::OnePair); 221 | assert!(HandType::OnePair < HandType::TwoPair); 222 | assert!(HandType::TwoPair < HandType::ThreeOfAKind); 223 | assert!(HandType::ThreeOfAKind < HandType::FullHouse); 224 | assert!(HandType::FullHouse < HandType::FourOfAKind); 225 | assert!(HandType::FourOfAKind < HandType::FiveOfAKind); 226 | } 227 | 228 | #[test] 229 | fn day07_part2_can_parse_hand() { 230 | let mut hashmap: HashMap = HashMap::new(); 231 | 232 | let hand = Hand::parse("32T3K 765", &mut hashmap); 233 | assert_eq!( 234 | hand, 235 | Hand { 236 | cards: [ 237 | Card::Number(3), 238 | Card::Number(2), 239 | Card::Number(10), 240 | Card::Number(3), 241 | Card::King 242 | ], 243 | hand_type: HandType::OnePair, 244 | bid: 765 245 | } 246 | ); 247 | 248 | let hand = Hand::parse("QQQJA 483", &mut hashmap); 249 | assert_eq!( 250 | hand, 251 | Hand { 252 | cards: [ 253 | Card::Queen, 254 | Card::Queen, 255 | Card::Queen, 256 | Card::Joker, 257 | Card::Ace 258 | ], 259 | hand_type: HandType::FourOfAKind, 260 | bid: 483 261 | } 262 | ); 263 | } 264 | 265 | #[test] 266 | fn day07_part2_can_order_hands() { 267 | let mut hashmap: HashMap = HashMap::new(); 268 | let hand1 = Hand::parse("32T3K 765", &mut hashmap); 269 | let hand2 = Hand::parse("T55J5 684", &mut hashmap); 270 | let hand3 = Hand::parse("KK677 28", &mut hashmap); 271 | let hand4 = Hand::parse("KTJJT 220", &mut hashmap); 272 | let hand5 = Hand::parse("QQQJA 483", &mut hashmap); 273 | let mut hands = vec![hand1, hand2, hand3, hand4, hand5]; 274 | hands.sort(); 275 | assert_eq!(hands, vec![hand1, hand3, hand2, hand5, hand4]); 276 | } 277 | 278 | #[test] 279 | fn day07_sample_part2() { 280 | assert_eq!(part2(SAMPLE), 5905); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /aoc2023/src/day16/mod.rs: -------------------------------------------------------------------------------- 1 | use rayon::prelude::*; 2 | use std::collections::HashSet; 3 | 4 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] 5 | enum Tile { 6 | Empty, 7 | SplitVertical, 8 | SplitHorizontal, 9 | BounceSlash, 10 | BounceBackslash, 11 | } 12 | 13 | impl From for Tile { 14 | fn from(c: char) -> Self { 15 | match c { 16 | '.' => Tile::Empty, 17 | '|' => Tile::SplitVertical, 18 | '-' => Tile::SplitHorizontal, 19 | '/' => Tile::BounceSlash, 20 | '\\' => Tile::BounceBackslash, 21 | _ => panic!("Invalid tile: {}", c), 22 | } 23 | } 24 | } 25 | 26 | impl std::fmt::Display for Tile { 27 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 28 | match self { 29 | Tile::Empty => write!(f, "."), 30 | Tile::SplitVertical => write!(f, "|"), 31 | Tile::SplitHorizontal => write!(f, "-"), 32 | Tile::BounceSlash => write!(f, "/"), 33 | Tile::BounceBackslash => write!(f, "\\"), 34 | } 35 | } 36 | } 37 | 38 | // fn draw_map(map: &Vec>, current: Coordinate, dir: Direction, energized_tiles: &HashSet) { 39 | // for (y, row) in map.iter().enumerate() { 40 | // for (x, tile) in row.iter().enumerate() { 41 | // let (es, ee) = if energized_tiles.contains(&(x as isize, y as isize)) { 42 | // ("\x1b[93m", "\x1b[0m") 43 | // } 44 | // else { 45 | // ("", "") 46 | // }; 47 | // if (x as isize, y as isize) == current { 48 | // eprint!("{es}{dir}{ee}"); 49 | // } 50 | // else { 51 | // eprint!("{es}{tile}{ee}"); 52 | // } 53 | // } 54 | // eprintln!(); 55 | // } 56 | // eprintln!(); 57 | // } 58 | 59 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] 60 | enum Direction { 61 | Up, 62 | Down, 63 | Left, 64 | Right, 65 | } 66 | 67 | impl std::fmt::Display for Direction { 68 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 69 | match self { 70 | Direction::Up => write!(f, "^"), 71 | Direction::Down => write!(f, "v"), 72 | Direction::Left => write!(f, "<"), 73 | Direction::Right => write!(f, ">"), 74 | } 75 | } 76 | } 77 | 78 | impl Direction { 79 | fn delta(&self) -> (isize, isize) { 80 | match self { 81 | Direction::Up => (0, -1), 82 | Direction::Down => (0, 1), 83 | Direction::Left => (-1, 0), 84 | Direction::Right => (1, 0), 85 | } 86 | } 87 | 88 | fn is_horizontal(&self) -> bool { 89 | match self { 90 | Direction::Left | Direction::Right => true, 91 | _ => false, 92 | } 93 | } 94 | 95 | fn is_vertical(&self) -> bool { 96 | !self.is_horizontal() 97 | } 98 | } 99 | 100 | type Coordinate = (isize, isize); 101 | 102 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 103 | enum Action { 104 | Continue, 105 | Stop, 106 | Bounce(Direction), 107 | SplitVertical, 108 | SplitHorizontal, 109 | } 110 | 111 | fn get_action(map: &Vec>, coord: Coordinate, dir: Direction) -> Action { 112 | // fall off the edge of the map 113 | if coord.0 < 0 114 | || coord.1 < 0 115 | || coord.0 >= map[0].len() as isize 116 | || coord.1 >= map.len() as isize 117 | { 118 | return Action::Stop; 119 | } 120 | 121 | match map[coord.1 as usize][coord.0 as usize] { 122 | Tile::SplitVertical if dir.is_horizontal() => Action::SplitVertical, 123 | Tile::SplitHorizontal if dir.is_vertical() => Action::SplitHorizontal, 124 | Tile::BounceSlash => match dir { 125 | Direction::Right => Action::Bounce(Direction::Up), 126 | Direction::Up => Action::Bounce(Direction::Right), 127 | Direction::Down => Action::Bounce(Direction::Left), 128 | Direction::Left => Action::Bounce(Direction::Down), 129 | }, 130 | Tile::BounceBackslash => match dir { 131 | Direction::Up => Action::Bounce(Direction::Left), 132 | Direction::Down => Action::Bounce(Direction::Right), 133 | Direction::Left => Action::Bounce(Direction::Up), 134 | Direction::Right => Action::Bounce(Direction::Down), 135 | }, 136 | _ => Action::Continue, 137 | } 138 | } 139 | 140 | fn parse(input: &str) -> Vec> { 141 | input 142 | .lines() 143 | .map(|line| line.trim().chars().map(Tile::from).collect()) 144 | .collect() 145 | } 146 | 147 | fn count_energized_tiles(map: &Vec>, ray: (Coordinate, Direction)) -> usize { 148 | let num_tiles = map.len() * map[0].len(); 149 | let mut evaluated_rays: HashSet<(Coordinate, Direction)> = HashSet::new(); 150 | let mut pending_rays: Vec<(Coordinate, Direction)> = Vec::new(); 151 | let mut energized_tiles: HashSet = HashSet::new(); 152 | 153 | pending_rays.push(ray); 154 | 'rays: while !pending_rays.is_empty() { 155 | let ray = pending_rays.pop().expect("pending_rays is not empty"); 156 | //eprintln!("evaluating ray: {ray:?}"); 157 | evaluated_rays.insert(ray); 158 | let (start, current_dir) = ray; 159 | 160 | let mut current = start; 161 | if current.0 >= 0 162 | && current.1 < map[0].len() as isize 163 | && current.1 >= 0 164 | && current.1 < map.len() as isize 165 | { 166 | energized_tiles.insert(current); 167 | } 168 | // draw_map(&map, current, current_dir, &energized_tiles); 169 | 'raymarch: loop { 170 | if energized_tiles.len() == num_tiles { 171 | break 'rays; 172 | } 173 | 174 | current = ( 175 | current.0 + current_dir.delta().0, 176 | current.1 + current_dir.delta().1, 177 | ); 178 | energized_tiles.insert(current); 179 | 180 | // draw_map(&map, current, current_dir, &energized_tiles); 181 | 182 | match get_action(&map, current, current_dir) { 183 | Action::Continue => continue 'raymarch, 184 | Action::Stop => { 185 | //eprintln!("killing ray"); 186 | // it took me way too long to realize that this was the problem 187 | energized_tiles.remove(¤t); 188 | break 'raymarch; 189 | } 190 | Action::Bounce(new_dir) => { 191 | //eprintln!("bouncing to {new_dir}"); 192 | if !evaluated_rays.contains(&(current, new_dir)) { 193 | pending_rays.push((current, new_dir)); 194 | } 195 | break 'raymarch; 196 | } 197 | Action::SplitVertical => { 198 | //eprintln!("splitting vertical"); 199 | if !evaluated_rays.contains(&(current, Direction::Up)) { 200 | pending_rays.push((current, Direction::Up)); 201 | } 202 | if !evaluated_rays.contains(&(current, Direction::Down)) { 203 | pending_rays.push((current, Direction::Down)); 204 | } 205 | break 'raymarch; 206 | } 207 | Action::SplitHorizontal => { 208 | //eprintln!("splitting horizontal"); 209 | if !evaluated_rays.contains(&(current, Direction::Left)) { 210 | pending_rays.push((current, Direction::Left)); 211 | } 212 | if !evaluated_rays.contains(&(current, Direction::Right)) { 213 | pending_rays.push((current, Direction::Right)); 214 | } 215 | break 'raymarch; 216 | } 217 | } 218 | } 219 | } 220 | 221 | // for y in 0..map.len() { 222 | // for x in 0..map[0].len() { 223 | // if energized_tiles.contains(&(x as isize, y as isize)) { 224 | // //eprint!("\x1b[91m#\x1b[0m"); 225 | // } 226 | // else { 227 | // //eprint!("."); 228 | // } 229 | // } 230 | // //eprintln!(); 231 | // } 232 | // //eprintln!(); 233 | 234 | // let mut derp = energized_tiles.iter().collect::>(); 235 | // derp.sort(); 236 | // for (i, coord) in derp.iter().enumerate() { 237 | // //eprintln!("{i: >2}: {coord:?}"); 238 | // } 239 | 240 | // draw_map(&map, (0, 0), Direction::Right, &energized_tiles); 241 | energized_tiles.len() 242 | } 243 | 244 | pub fn part1(input: &str) -> usize { 245 | let map = parse(input.trim()); 246 | count_energized_tiles(&map, ((-1, 0), Direction::Right)) 247 | } 248 | 249 | pub fn part2(input: &str) -> usize { 250 | let map = parse(input.trim()); 251 | let (width, height) = (map[0].len() as isize, map.len() as isize); 252 | let mut rays: Vec<(Coordinate, Direction)> = Vec::new(); 253 | for y in 0..height { 254 | rays.push(((-1, y), Direction::Right)); 255 | rays.push(((width, y), Direction::Left)); 256 | } 257 | for x in 1..width { 258 | rays.push(((x, -1), Direction::Down)); 259 | rays.push(((x, height), Direction::Up)); 260 | } 261 | 262 | rays.into_par_iter() 263 | .map(|ray| count_energized_tiles(&map, ray)) 264 | .max() 265 | .unwrap_or(0) 266 | } 267 | 268 | pub fn run(input: &str) -> (Option, Option) { 269 | (Some(part1(input)), Some(part2(input))) 270 | } 271 | 272 | #[cfg(test)] 273 | mod test { 274 | use super::*; 275 | 276 | const SAMPLE: &'static str = r#".|...\.... 277 | |.-.\..... 278 | .....|-... 279 | ........|. 280 | .......... 281 | .........\ 282 | ..../.\\.. 283 | .-.-/..|.. 284 | .|....-|.\ 285 | ..//.|.... 286 | "#; 287 | 288 | #[test] 289 | fn day15_sample_part1() { 290 | assert_eq!(part1(SAMPLE), 46); 291 | } 292 | 293 | #[test] 294 | fn day15_sample_part2() { 295 | assert_eq!(part2(SAMPLE), 51); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -------------------------------------------------------------------------------- /aoc2023/src/day19/mod.rs: -------------------------------------------------------------------------------- 1 | use nom::{ 2 | branch::alt, 3 | bytes::complete::take_while1, 4 | character::complete::{char, digit1, one_of}, 5 | combinator::opt, 6 | multi::separated_list1, 7 | sequence::{delimited, pair, preceded}, 8 | IResult, 9 | }; 10 | use rayon::prelude::*; 11 | use std::collections::HashMap; 12 | 13 | #[derive(Debug, Clone, PartialEq, Eq)] 14 | struct Workflow<'s> { 15 | name: &'s str, 16 | rules: Vec>, 17 | } 18 | 19 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 20 | struct Rule<'s> { 21 | check: Option, 22 | action: Action<'s>, 23 | } 24 | 25 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 26 | struct RuleCheck { 27 | category: Category, 28 | condition: Condition, 29 | value: usize, 30 | } 31 | 32 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 33 | enum Category { 34 | ExtremelyCoolLooking, 35 | Musical, 36 | Aerodynamic, 37 | Shiny, 38 | } 39 | 40 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 41 | enum Condition { 42 | GreaterThan, 43 | LessThan, 44 | } 45 | 46 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 47 | enum Action<'s> { 48 | GotoWorkflow(&'s str), 49 | Reject, 50 | Accept, 51 | } 52 | 53 | #[derive(Debug, Copy, Clone)] 54 | struct Part { 55 | x: usize, 56 | m: usize, 57 | a: usize, 58 | s: usize, 59 | } 60 | 61 | impl Part { 62 | pub fn rating(&self) -> usize { 63 | self.x + self.m + self.a + self.s 64 | } 65 | } 66 | 67 | impl std::fmt::Display for Part { 68 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 69 | write!(f, "{{x={},m={},a={},s={}}}", self.x, self.m, self.a, self.s) 70 | } 71 | } 72 | 73 | fn parse<'s>(input: &'s str) -> (HashMap<&'s str, Workflow<'s>>, Vec) { 74 | let mut halves = input.split("\n\n"); 75 | let workflows = halves 76 | .next() 77 | .unwrap() 78 | .lines() 79 | .map(parser::parse_workflow) 80 | .map(|w| (w.name, w)) 81 | .collect(); 82 | let parts = halves 83 | .next() 84 | .unwrap() 85 | .lines() 86 | .map(parser::parse_part) 87 | .collect(); 88 | (workflows, parts) 89 | } 90 | 91 | mod parser { 92 | use nom::bytes::complete::tag; 93 | 94 | use super::*; 95 | 96 | fn workflow_name(input: &str) -> IResult<&str, &str> { 97 | take_while1(|c: char| c.is_ascii_lowercase())(input) 98 | } 99 | 100 | fn category(input: &str) -> IResult<&str, Category> { 101 | let (input, c) = one_of("xmas")(input)?; 102 | Ok(( 103 | input, 104 | match c { 105 | 'x' => Category::ExtremelyCoolLooking, 106 | 'm' => Category::Musical, 107 | 'a' => Category::Aerodynamic, 108 | 's' => Category::Shiny, 109 | _ => unreachable!(), 110 | }, 111 | )) 112 | } 113 | 114 | fn condition(input: &str) -> IResult<&str, Condition> { 115 | let (input, c) = one_of("<>")(input)?; 116 | Ok(( 117 | input, 118 | match c { 119 | '<' => Condition::LessThan, 120 | '>' => Condition::GreaterThan, 121 | _ => unreachable!(), 122 | }, 123 | )) 124 | } 125 | 126 | fn value(input: &str) -> IResult<&str, usize> { 127 | digit1(input).map(|(input, digits)| (input, digits.parse().unwrap())) 128 | } 129 | 130 | fn rule_check(input: &str) -> IResult<&str, RuleCheck> { 131 | let (input, category) = category(input)?; 132 | let (input, condition) = condition(input)?; 133 | let (input, value) = value(input)?; 134 | Ok(( 135 | input, 136 | RuleCheck { 137 | category, 138 | condition, 139 | value, 140 | }, 141 | )) 142 | } 143 | 144 | fn accept(input: &str) -> IResult<&str, Action> { 145 | char('A')(input).map(|(input, _)| (input, Action::Accept)) 146 | } 147 | 148 | fn reject(input: &str) -> IResult<&str, Action> { 149 | char('R')(input).map(|(input, _)| (input, Action::Reject)) 150 | } 151 | 152 | fn goto(input: &str) -> IResult<&str, Action> { 153 | workflow_name(input).map(|(input, workflow)| (input, Action::GotoWorkflow(workflow))) 154 | } 155 | 156 | fn action(input: &str) -> IResult<&str, Action> { 157 | preceded(opt(char(':')), alt((accept, reject, goto)))(input) 158 | } 159 | 160 | fn rule(input: &str) -> IResult<&str, Rule> { 161 | let (input, check) = opt(rule_check)(input)?; 162 | let (input, action) = action(input)?; 163 | Ok((input, Rule { check, action })) 164 | } 165 | 166 | fn rules(input: &str) -> IResult<&str, Vec> { 167 | separated_list1(char(','), rule)(input) 168 | } 169 | 170 | pub fn parse_workflow(line: &str) -> Workflow { 171 | let workflow = pair(workflow_name, delimited(char('{'), rules, char('}')))(line); 172 | match workflow { 173 | Ok((_, (name, rules))) => Workflow { name, rules }, 174 | Err(_) => panic!("Failed to parse workflow: '{line}'"), 175 | } 176 | } 177 | 178 | fn part_categories(input: &str) -> IResult<&str, Part> { 179 | let (input, _) = tag("x=")(input)?; 180 | let (input, x) = value(input)?; 181 | let (input, _) = tag(",m=")(input)?; 182 | let (input, m) = value(input)?; 183 | let (input, _) = tag(",a=")(input)?; 184 | let (input, a) = value(input)?; 185 | let (input, _) = tag(",s=")(input)?; 186 | let (input, s) = value(input)?; 187 | Ok((input, Part { x, m, a, s })) 188 | } 189 | 190 | pub fn parse_part(line: &str) -> Part { 191 | let part = delimited(char('{'), part_categories, char('}'))(line); 192 | match part { 193 | Ok((_, part)) => part, 194 | Err(_) => panic!("Failed to parse part: '{line}'"), 195 | } 196 | } 197 | 198 | #[cfg(test)] 199 | mod test { 200 | use super::*; 201 | 202 | #[test] 203 | fn can_parse_workflow_name() { 204 | let (input, name) = workflow_name("px{a<2006:qkq,m>2090:A,rfg}").unwrap(); 205 | assert_eq!(input, "{a<2006:qkq,m>2090:A,rfg}"); 206 | assert_eq!(name, "px"); 207 | } 208 | 209 | #[test] 210 | fn can_parse_category() { 211 | let (input, cat) = category("x").unwrap(); 212 | assert_eq!(input, ""); 213 | assert_eq!(cat, Category::ExtremelyCoolLooking); 214 | let (input, cat) = category("m").unwrap(); 215 | assert_eq!(input, ""); 216 | assert_eq!(cat, Category::Musical); 217 | let (input, cat) = category("a").unwrap(); 218 | assert_eq!(input, ""); 219 | assert_eq!(cat, Category::Aerodynamic); 220 | let (input, cat) = category("s").unwrap(); 221 | assert_eq!(input, ""); 222 | assert_eq!(cat, Category::Shiny); 223 | } 224 | 225 | #[test] 226 | fn can_parse_condition() { 227 | let (input, cond) = condition("<").unwrap(); 228 | assert_eq!(input, ""); 229 | assert_eq!(cond, Condition::LessThan); 230 | let (input, cond) = condition(">").unwrap(); 231 | assert_eq!(input, ""); 232 | assert_eq!(cond, Condition::GreaterThan); 233 | } 234 | 235 | #[test] 236 | fn can_parse_value() { 237 | let (input, value) = value("123").unwrap(); 238 | assert_eq!(input, ""); 239 | assert_eq!(value, 123); 240 | } 241 | 242 | #[test] 243 | fn can_parse_rule_check() { 244 | let (input, check) = rule_check("a<2006").unwrap(); 245 | assert_eq!(input, ""); 246 | assert_eq!(check.category, Category::Aerodynamic); 247 | assert_eq!(check.condition, Condition::LessThan); 248 | assert_eq!(check.value, 2006); 249 | } 250 | 251 | #[test] 252 | fn can_parse_accept_action() { 253 | let (input, action) = accept("A").unwrap(); 254 | assert_eq!(input, ""); 255 | assert_eq!(action, Action::Accept); 256 | } 257 | 258 | #[test] 259 | fn can_parse_reject_action() { 260 | let (input, action) = reject("R").unwrap(); 261 | assert_eq!(input, ""); 262 | assert_eq!(action, Action::Reject); 263 | } 264 | 265 | #[test] 266 | fn can_parse_goto_action() { 267 | let (input, action) = goto("rfg").unwrap(); 268 | assert_eq!(input, ""); 269 | assert_eq!(action, Action::GotoWorkflow("rfg")); 270 | } 271 | 272 | #[test] 273 | fn can_parse_action() { 274 | let (input, act) = action("A").unwrap(); 275 | assert_eq!(input, ""); 276 | assert_eq!(act, Action::Accept); 277 | 278 | let (input, act) = action(":A").unwrap(); 279 | assert_eq!(input, ""); 280 | assert_eq!(act, Action::Accept); 281 | 282 | let (input, act) = action("R").unwrap(); 283 | assert_eq!(input, ""); 284 | assert_eq!(act, Action::Reject); 285 | 286 | let (input, act) = action(":R").unwrap(); 287 | assert_eq!(input, ""); 288 | assert_eq!(act, Action::Reject); 289 | 290 | let (input, act) = action("rfg").unwrap(); 291 | assert_eq!(input, ""); 292 | assert_eq!(act, Action::GotoWorkflow("rfg")); 293 | 294 | let (input, act) = action(":qkq").unwrap(); 295 | assert_eq!(input, ""); 296 | assert_eq!(act, Action::GotoWorkflow("qkq")); 297 | } 298 | 299 | #[test] 300 | fn can_parse_rule() { 301 | let (input, r) = rule("a<2006:qkq").unwrap(); 302 | assert_eq!(input, ""); 303 | assert_eq!(r.check.unwrap().category, Category::Aerodynamic); 304 | assert_eq!(r.check.unwrap().condition, Condition::LessThan); 305 | assert_eq!(r.check.unwrap().value, 2006); 306 | assert_eq!(r.action, Action::GotoWorkflow("qkq")); 307 | 308 | let (input, r) = rule("m>2090:A").unwrap(); 309 | assert_eq!(input, ""); 310 | assert_eq!(r.check.unwrap().category, Category::Musical); 311 | assert_eq!(r.check.unwrap().condition, Condition::GreaterThan); 312 | assert_eq!(r.check.unwrap().value, 2090); 313 | assert_eq!(r.action, Action::Accept); 314 | 315 | let (input, r) = rule("rfg").unwrap(); 316 | assert_eq!(input, ""); 317 | assert!(r.check.is_none()); 318 | assert_eq!(r.action, Action::GotoWorkflow("rfg")); 319 | } 320 | 321 | #[test] 322 | fn can_parse_rules() { 323 | let (input, rules) = rules("a<2006:qkq,m>2090:A,rfg").unwrap(); 324 | assert_eq!(input, ""); 325 | assert_eq!(rules.len(), 3); 326 | assert_eq!(rules[0].check.unwrap().category, Category::Aerodynamic); 327 | assert_eq!(rules[0].check.unwrap().condition, Condition::LessThan); 328 | assert_eq!(rules[0].check.unwrap().value, 2006); 329 | assert_eq!(rules[0].action, Action::GotoWorkflow("qkq")); 330 | assert_eq!(rules[1].check.unwrap().category, Category::Musical); 331 | assert_eq!(rules[1].check.unwrap().condition, Condition::GreaterThan); 332 | assert_eq!(rules[1].check.unwrap().value, 2090); 333 | assert_eq!(rules[1].action, Action::Accept); 334 | assert!(rules[2].check.is_none()); 335 | assert_eq!(rules[2].action, Action::GotoWorkflow("rfg")); 336 | } 337 | 338 | #[test] 339 | fn can_parse_workflow() { 340 | let workflow = "px{a<2006:qkq,m>2090:A,rfg}"; 341 | let parsed = parse_workflow(workflow); 342 | assert_eq!(parsed.name, "px"); 343 | assert_eq!(parsed.rules.len(), 3); 344 | assert_eq!( 345 | parsed.rules[0].check.unwrap().category, 346 | Category::Aerodynamic 347 | ); 348 | assert_eq!( 349 | parsed.rules[0].check.unwrap().condition, 350 | Condition::LessThan 351 | ); 352 | assert_eq!(parsed.rules[0].check.unwrap().value, 2006); 353 | assert_eq!(parsed.rules[0].action, Action::GotoWorkflow("qkq")); 354 | assert_eq!(parsed.rules[1].check.unwrap().category, Category::Musical); 355 | assert_eq!( 356 | parsed.rules[1].check.unwrap().condition, 357 | Condition::GreaterThan 358 | ); 359 | assert_eq!(parsed.rules[1].check.unwrap().value, 2090); 360 | assert_eq!(parsed.rules[1].action, Action::Accept); 361 | assert!(parsed.rules[2].check.is_none()); 362 | assert_eq!(parsed.rules[2].action, Action::GotoWorkflow("rfg")); 363 | } 364 | 365 | #[test] 366 | fn can_parse_part() { 367 | let part = "{x=787,m=2655,a=1222,s=2876}"; 368 | let parsed = parse_part(part); 369 | assert_eq!(parsed.x, 787); 370 | assert_eq!(parsed.m, 2655); 371 | assert_eq!(parsed.a, 1222); 372 | assert_eq!(parsed.s, 2876); 373 | } 374 | } 375 | } 376 | 377 | fn is_part_accepted<'s>(workflows: &'s HashMap<&'s str, Workflow<'s>>, part: Part) -> bool { 378 | let mut workflow = workflows.get("in").expect("in workflow exists"); 379 | 'workflows: loop { 380 | for rule in workflow.rules.iter() { 381 | let apply_rule = if let Some(check) = rule.check { 382 | let value = match check.category { 383 | Category::ExtremelyCoolLooking => part.x, 384 | Category::Musical => part.m, 385 | Category::Aerodynamic => part.a, 386 | Category::Shiny => part.s, 387 | }; 388 | match check.condition { 389 | Condition::LessThan => value < check.value, 390 | Condition::GreaterThan => value > check.value, 391 | } 392 | } else { 393 | // always apply rule if there is no check 394 | // (this should only happen on the last rule) 395 | true 396 | }; 397 | if apply_rule { 398 | match rule.action { 399 | Action::Accept => return true, 400 | Action::Reject => return false, 401 | Action::GotoWorkflow(name) => { 402 | workflow = workflows.get(name).expect("workflow exists"); 403 | continue 'workflows; 404 | } 405 | } 406 | } 407 | } 408 | } 409 | } 410 | 411 | pub fn part1(input: &str) -> usize { 412 | let (workflows, parts) = parse(input); 413 | parts 414 | .into_par_iter() 415 | .filter(|part| is_part_accepted(&workflows, *part)) 416 | .map(|part| part.rating()) 417 | .sum() 418 | } 419 | 420 | pub fn part2(_input: &str) -> usize { 421 | 0 422 | } 423 | 424 | pub fn run(input: &str) -> (Option, Option) { 425 | (Some(part1(input)), None) 426 | } 427 | 428 | #[cfg(test)] 429 | mod test { 430 | use super::*; 431 | 432 | const SAMPLE: &'static str = r#"px{a<2006:qkq,m>2090:A,rfg} 433 | pv{a>1716:R,A} 434 | lnx{m>1548:A,A} 435 | rfg{s<537:gd,x>2440:R,A} 436 | qs{s>3448:A,lnx} 437 | qkq{x<1416:A,crn} 438 | crn{x>2662:A,R} 439 | in{s<1351:px,qqz} 440 | qqz{s>2770:qs,m<1801:hdj,R} 441 | gd{a>3333:R,R} 442 | hdj{m>838:A,pv} 443 | 444 | {x=787,m=2655,a=1222,s=2876} 445 | {x=1679,m=44,a=2067,s=496} 446 | {x=2036,m=264,a=79,s=2244} 447 | {x=2461,m=1339,a=466,s=291} 448 | {x=2127,m=1623,a=2188,s=1013} 449 | "#; 450 | 451 | #[test] 452 | fn day19_part1_sample() { 453 | assert_eq!(part1(SAMPLE), 19114); 454 | } 455 | 456 | #[test] 457 | #[ignore] 458 | fn day19_part2_sample() { 459 | assert_eq!(part2(SAMPLE), 167409079868000); 460 | } 461 | } 462 | -------------------------------------------------------------------------------- /aoc2023/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "1.1.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "android-tzdata" 22 | version = "0.1.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 25 | 26 | [[package]] 27 | name = "android_system_properties" 28 | version = "0.1.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 31 | dependencies = [ 32 | "libc", 33 | ] 34 | 35 | [[package]] 36 | name = "anes" 37 | version = "0.1.6" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 40 | 41 | [[package]] 42 | name = "anyhow" 43 | version = "1.0.75" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 46 | 47 | [[package]] 48 | name = "aoc2023" 49 | version = "0.1.0" 50 | dependencies = [ 51 | "anyhow", 52 | "chrono", 53 | "criterion", 54 | "dialoguer", 55 | "dotenv", 56 | "itertools 0.12.0", 57 | "nom", 58 | "rayon", 59 | "regex", 60 | "termcolor", 61 | "ureq", 62 | ] 63 | 64 | [[package]] 65 | name = "atty" 66 | version = "0.2.14" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 69 | dependencies = [ 70 | "hermit-abi", 71 | "libc", 72 | "winapi", 73 | ] 74 | 75 | [[package]] 76 | name = "autocfg" 77 | version = "1.1.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 80 | 81 | [[package]] 82 | name = "base64" 83 | version = "0.21.5" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" 86 | 87 | [[package]] 88 | name = "bitflags" 89 | version = "1.3.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 92 | 93 | [[package]] 94 | name = "bitflags" 95 | version = "2.4.1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 98 | 99 | [[package]] 100 | name = "bumpalo" 101 | version = "3.14.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 104 | 105 | [[package]] 106 | name = "cast" 107 | version = "0.3.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 110 | 111 | [[package]] 112 | name = "cc" 113 | version = "1.0.83" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 116 | dependencies = [ 117 | "libc", 118 | ] 119 | 120 | [[package]] 121 | name = "cfg-if" 122 | version = "1.0.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 125 | 126 | [[package]] 127 | name = "chrono" 128 | version = "0.4.31" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" 131 | dependencies = [ 132 | "android-tzdata", 133 | "iana-time-zone", 134 | "js-sys", 135 | "num-traits", 136 | "wasm-bindgen", 137 | "windows-targets 0.48.5", 138 | ] 139 | 140 | [[package]] 141 | name = "ciborium" 142 | version = "0.2.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" 145 | dependencies = [ 146 | "ciborium-io", 147 | "ciborium-ll", 148 | "serde", 149 | ] 150 | 151 | [[package]] 152 | name = "ciborium-io" 153 | version = "0.2.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" 156 | 157 | [[package]] 158 | name = "ciborium-ll" 159 | version = "0.2.1" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" 162 | dependencies = [ 163 | "ciborium-io", 164 | "half", 165 | ] 166 | 167 | [[package]] 168 | name = "clap" 169 | version = "3.2.25" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" 172 | dependencies = [ 173 | "bitflags 1.3.2", 174 | "clap_lex", 175 | "indexmap", 176 | "textwrap", 177 | ] 178 | 179 | [[package]] 180 | name = "clap_lex" 181 | version = "0.2.4" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 184 | dependencies = [ 185 | "os_str_bytes", 186 | ] 187 | 188 | [[package]] 189 | name = "console" 190 | version = "0.15.7" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" 193 | dependencies = [ 194 | "encode_unicode", 195 | "lazy_static", 196 | "libc", 197 | "unicode-width", 198 | "windows-sys 0.45.0", 199 | ] 200 | 201 | [[package]] 202 | name = "cookie" 203 | version = "0.17.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" 206 | dependencies = [ 207 | "percent-encoding", 208 | "time", 209 | "version_check", 210 | ] 211 | 212 | [[package]] 213 | name = "cookie_store" 214 | version = "0.20.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" 217 | dependencies = [ 218 | "cookie", 219 | "idna 0.3.0", 220 | "indexmap", 221 | "log", 222 | "serde", 223 | "serde_derive", 224 | "serde_json", 225 | "time", 226 | "url", 227 | ] 228 | 229 | [[package]] 230 | name = "core-foundation-sys" 231 | version = "0.8.6" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 234 | 235 | [[package]] 236 | name = "crc32fast" 237 | version = "1.3.2" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 240 | dependencies = [ 241 | "cfg-if", 242 | ] 243 | 244 | [[package]] 245 | name = "criterion" 246 | version = "0.4.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" 249 | dependencies = [ 250 | "anes", 251 | "atty", 252 | "cast", 253 | "ciborium", 254 | "clap", 255 | "criterion-plot", 256 | "itertools 0.10.5", 257 | "lazy_static", 258 | "num-traits", 259 | "oorandom", 260 | "plotters", 261 | "rayon", 262 | "regex", 263 | "serde", 264 | "serde_derive", 265 | "serde_json", 266 | "tinytemplate", 267 | "walkdir", 268 | ] 269 | 270 | [[package]] 271 | name = "criterion-plot" 272 | version = "0.5.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 275 | dependencies = [ 276 | "cast", 277 | "itertools 0.10.5", 278 | ] 279 | 280 | [[package]] 281 | name = "crossbeam-deque" 282 | version = "0.8.3" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" 285 | dependencies = [ 286 | "cfg-if", 287 | "crossbeam-epoch", 288 | "crossbeam-utils", 289 | ] 290 | 291 | [[package]] 292 | name = "crossbeam-epoch" 293 | version = "0.9.15" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" 296 | dependencies = [ 297 | "autocfg", 298 | "cfg-if", 299 | "crossbeam-utils", 300 | "memoffset", 301 | "scopeguard", 302 | ] 303 | 304 | [[package]] 305 | name = "crossbeam-utils" 306 | version = "0.8.16" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 309 | dependencies = [ 310 | "cfg-if", 311 | ] 312 | 313 | [[package]] 314 | name = "deranged" 315 | version = "0.3.10" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" 318 | dependencies = [ 319 | "powerfmt", 320 | ] 321 | 322 | [[package]] 323 | name = "dialoguer" 324 | version = "0.11.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" 327 | dependencies = [ 328 | "console", 329 | "shell-words", 330 | "tempfile", 331 | "thiserror", 332 | "zeroize", 333 | ] 334 | 335 | [[package]] 336 | name = "dotenv" 337 | version = "0.15.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 340 | 341 | [[package]] 342 | name = "either" 343 | version = "1.9.0" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 346 | 347 | [[package]] 348 | name = "encode_unicode" 349 | version = "0.3.6" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 352 | 353 | [[package]] 354 | name = "errno" 355 | version = "0.3.8" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 358 | dependencies = [ 359 | "libc", 360 | "windows-sys 0.52.0", 361 | ] 362 | 363 | [[package]] 364 | name = "fastrand" 365 | version = "2.0.1" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 368 | 369 | [[package]] 370 | name = "flate2" 371 | version = "1.0.28" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 374 | dependencies = [ 375 | "crc32fast", 376 | "miniz_oxide", 377 | ] 378 | 379 | [[package]] 380 | name = "form_urlencoded" 381 | version = "1.2.1" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 384 | dependencies = [ 385 | "percent-encoding", 386 | ] 387 | 388 | [[package]] 389 | name = "getrandom" 390 | version = "0.2.11" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 393 | dependencies = [ 394 | "cfg-if", 395 | "libc", 396 | "wasi", 397 | ] 398 | 399 | [[package]] 400 | name = "half" 401 | version = "1.8.2" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" 404 | 405 | [[package]] 406 | name = "hashbrown" 407 | version = "0.12.3" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 410 | 411 | [[package]] 412 | name = "hermit-abi" 413 | version = "0.1.19" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 416 | dependencies = [ 417 | "libc", 418 | ] 419 | 420 | [[package]] 421 | name = "iana-time-zone" 422 | version = "0.1.58" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" 425 | dependencies = [ 426 | "android_system_properties", 427 | "core-foundation-sys", 428 | "iana-time-zone-haiku", 429 | "js-sys", 430 | "wasm-bindgen", 431 | "windows-core", 432 | ] 433 | 434 | [[package]] 435 | name = "iana-time-zone-haiku" 436 | version = "0.1.2" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 439 | dependencies = [ 440 | "cc", 441 | ] 442 | 443 | [[package]] 444 | name = "idna" 445 | version = "0.3.0" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 448 | dependencies = [ 449 | "unicode-bidi", 450 | "unicode-normalization", 451 | ] 452 | 453 | [[package]] 454 | name = "idna" 455 | version = "0.5.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 458 | dependencies = [ 459 | "unicode-bidi", 460 | "unicode-normalization", 461 | ] 462 | 463 | [[package]] 464 | name = "indexmap" 465 | version = "1.9.3" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 468 | dependencies = [ 469 | "autocfg", 470 | "hashbrown", 471 | ] 472 | 473 | [[package]] 474 | name = "itertools" 475 | version = "0.10.5" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 478 | dependencies = [ 479 | "either", 480 | ] 481 | 482 | [[package]] 483 | name = "itertools" 484 | version = "0.12.0" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" 487 | dependencies = [ 488 | "either", 489 | ] 490 | 491 | [[package]] 492 | name = "itoa" 493 | version = "1.0.10" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 496 | 497 | [[package]] 498 | name = "js-sys" 499 | version = "0.3.66" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" 502 | dependencies = [ 503 | "wasm-bindgen", 504 | ] 505 | 506 | [[package]] 507 | name = "lazy_static" 508 | version = "1.4.0" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 511 | 512 | [[package]] 513 | name = "libc" 514 | version = "0.2.150" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" 517 | 518 | [[package]] 519 | name = "linux-raw-sys" 520 | version = "0.4.12" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" 523 | 524 | [[package]] 525 | name = "log" 526 | version = "0.4.20" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 529 | 530 | [[package]] 531 | name = "memchr" 532 | version = "2.6.4" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 535 | 536 | [[package]] 537 | name = "memoffset" 538 | version = "0.9.0" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 541 | dependencies = [ 542 | "autocfg", 543 | ] 544 | 545 | [[package]] 546 | name = "minimal-lexical" 547 | version = "0.2.1" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 550 | 551 | [[package]] 552 | name = "miniz_oxide" 553 | version = "0.7.1" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 556 | dependencies = [ 557 | "adler", 558 | ] 559 | 560 | [[package]] 561 | name = "nom" 562 | version = "7.1.3" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 565 | dependencies = [ 566 | "memchr", 567 | "minimal-lexical", 568 | ] 569 | 570 | [[package]] 571 | name = "num-traits" 572 | version = "0.2.17" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 575 | dependencies = [ 576 | "autocfg", 577 | ] 578 | 579 | [[package]] 580 | name = "once_cell" 581 | version = "1.19.0" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 584 | 585 | [[package]] 586 | name = "oorandom" 587 | version = "11.1.3" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" 590 | 591 | [[package]] 592 | name = "os_str_bytes" 593 | version = "6.6.1" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" 596 | 597 | [[package]] 598 | name = "percent-encoding" 599 | version = "2.3.1" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 602 | 603 | [[package]] 604 | name = "plotters" 605 | version = "0.3.5" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" 608 | dependencies = [ 609 | "num-traits", 610 | "plotters-backend", 611 | "plotters-svg", 612 | "wasm-bindgen", 613 | "web-sys", 614 | ] 615 | 616 | [[package]] 617 | name = "plotters-backend" 618 | version = "0.3.5" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" 621 | 622 | [[package]] 623 | name = "plotters-svg" 624 | version = "0.3.5" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" 627 | dependencies = [ 628 | "plotters-backend", 629 | ] 630 | 631 | [[package]] 632 | name = "powerfmt" 633 | version = "0.2.0" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 636 | 637 | [[package]] 638 | name = "proc-macro2" 639 | version = "1.0.70" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" 642 | dependencies = [ 643 | "unicode-ident", 644 | ] 645 | 646 | [[package]] 647 | name = "quote" 648 | version = "1.0.33" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 651 | dependencies = [ 652 | "proc-macro2", 653 | ] 654 | 655 | [[package]] 656 | name = "rayon" 657 | version = "1.8.0" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" 660 | dependencies = [ 661 | "either", 662 | "rayon-core", 663 | ] 664 | 665 | [[package]] 666 | name = "rayon-core" 667 | version = "1.12.0" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" 670 | dependencies = [ 671 | "crossbeam-deque", 672 | "crossbeam-utils", 673 | ] 674 | 675 | [[package]] 676 | name = "redox_syscall" 677 | version = "0.4.1" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 680 | dependencies = [ 681 | "bitflags 1.3.2", 682 | ] 683 | 684 | [[package]] 685 | name = "regex" 686 | version = "1.10.2" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" 689 | dependencies = [ 690 | "aho-corasick", 691 | "memchr", 692 | "regex-automata", 693 | "regex-syntax", 694 | ] 695 | 696 | [[package]] 697 | name = "regex-automata" 698 | version = "0.4.3" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 701 | dependencies = [ 702 | "aho-corasick", 703 | "memchr", 704 | "regex-syntax", 705 | ] 706 | 707 | [[package]] 708 | name = "regex-syntax" 709 | version = "0.8.2" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 712 | 713 | [[package]] 714 | name = "ring" 715 | version = "0.17.7" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" 718 | dependencies = [ 719 | "cc", 720 | "getrandom", 721 | "libc", 722 | "spin", 723 | "untrusted", 724 | "windows-sys 0.48.0", 725 | ] 726 | 727 | [[package]] 728 | name = "rustix" 729 | version = "0.38.28" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" 732 | dependencies = [ 733 | "bitflags 2.4.1", 734 | "errno", 735 | "libc", 736 | "linux-raw-sys", 737 | "windows-sys 0.52.0", 738 | ] 739 | 740 | [[package]] 741 | name = "rustls" 742 | version = "0.21.10" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" 745 | dependencies = [ 746 | "log", 747 | "ring", 748 | "rustls-webpki", 749 | "sct", 750 | ] 751 | 752 | [[package]] 753 | name = "rustls-webpki" 754 | version = "0.101.7" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" 757 | dependencies = [ 758 | "ring", 759 | "untrusted", 760 | ] 761 | 762 | [[package]] 763 | name = "ryu" 764 | version = "1.0.15" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 767 | 768 | [[package]] 769 | name = "same-file" 770 | version = "1.0.6" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 773 | dependencies = [ 774 | "winapi-util", 775 | ] 776 | 777 | [[package]] 778 | name = "scopeguard" 779 | version = "1.2.0" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 782 | 783 | [[package]] 784 | name = "sct" 785 | version = "0.7.1" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 788 | dependencies = [ 789 | "ring", 790 | "untrusted", 791 | ] 792 | 793 | [[package]] 794 | name = "serde" 795 | version = "1.0.193" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" 798 | dependencies = [ 799 | "serde_derive", 800 | ] 801 | 802 | [[package]] 803 | name = "serde_derive" 804 | version = "1.0.193" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" 807 | dependencies = [ 808 | "proc-macro2", 809 | "quote", 810 | "syn", 811 | ] 812 | 813 | [[package]] 814 | name = "serde_json" 815 | version = "1.0.108" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" 818 | dependencies = [ 819 | "itoa", 820 | "ryu", 821 | "serde", 822 | ] 823 | 824 | [[package]] 825 | name = "shell-words" 826 | version = "1.1.0" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 829 | 830 | [[package]] 831 | name = "spin" 832 | version = "0.9.8" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 835 | 836 | [[package]] 837 | name = "syn" 838 | version = "2.0.39" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" 841 | dependencies = [ 842 | "proc-macro2", 843 | "quote", 844 | "unicode-ident", 845 | ] 846 | 847 | [[package]] 848 | name = "tempfile" 849 | version = "3.8.1" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" 852 | dependencies = [ 853 | "cfg-if", 854 | "fastrand", 855 | "redox_syscall", 856 | "rustix", 857 | "windows-sys 0.48.0", 858 | ] 859 | 860 | [[package]] 861 | name = "termcolor" 862 | version = "1.4.0" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" 865 | dependencies = [ 866 | "winapi-util", 867 | ] 868 | 869 | [[package]] 870 | name = "textwrap" 871 | version = "0.16.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" 874 | 875 | [[package]] 876 | name = "thiserror" 877 | version = "1.0.51" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" 880 | dependencies = [ 881 | "thiserror-impl", 882 | ] 883 | 884 | [[package]] 885 | name = "thiserror-impl" 886 | version = "1.0.51" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" 889 | dependencies = [ 890 | "proc-macro2", 891 | "quote", 892 | "syn", 893 | ] 894 | 895 | [[package]] 896 | name = "time" 897 | version = "0.3.31" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" 900 | dependencies = [ 901 | "deranged", 902 | "itoa", 903 | "powerfmt", 904 | "serde", 905 | "time-core", 906 | "time-macros", 907 | ] 908 | 909 | [[package]] 910 | name = "time-core" 911 | version = "0.1.2" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 914 | 915 | [[package]] 916 | name = "time-macros" 917 | version = "0.2.16" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" 920 | dependencies = [ 921 | "time-core", 922 | ] 923 | 924 | [[package]] 925 | name = "tinytemplate" 926 | version = "1.2.1" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 929 | dependencies = [ 930 | "serde", 931 | "serde_json", 932 | ] 933 | 934 | [[package]] 935 | name = "tinyvec" 936 | version = "1.6.0" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 939 | dependencies = [ 940 | "tinyvec_macros", 941 | ] 942 | 943 | [[package]] 944 | name = "tinyvec_macros" 945 | version = "0.1.1" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 948 | 949 | [[package]] 950 | name = "unicode-bidi" 951 | version = "0.3.14" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" 954 | 955 | [[package]] 956 | name = "unicode-ident" 957 | version = "1.0.12" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 960 | 961 | [[package]] 962 | name = "unicode-normalization" 963 | version = "0.1.22" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 966 | dependencies = [ 967 | "tinyvec", 968 | ] 969 | 970 | [[package]] 971 | name = "unicode-width" 972 | version = "0.1.11" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 975 | 976 | [[package]] 977 | name = "untrusted" 978 | version = "0.9.0" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 981 | 982 | [[package]] 983 | name = "ureq" 984 | version = "2.9.1" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97" 987 | dependencies = [ 988 | "base64", 989 | "cookie", 990 | "cookie_store", 991 | "flate2", 992 | "log", 993 | "once_cell", 994 | "rustls", 995 | "rustls-webpki", 996 | "url", 997 | "webpki-roots", 998 | ] 999 | 1000 | [[package]] 1001 | name = "url" 1002 | version = "2.5.0" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 1005 | dependencies = [ 1006 | "form_urlencoded", 1007 | "idna 0.5.0", 1008 | "percent-encoding", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "version_check" 1013 | version = "0.9.4" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1016 | 1017 | [[package]] 1018 | name = "walkdir" 1019 | version = "2.4.0" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" 1022 | dependencies = [ 1023 | "same-file", 1024 | "winapi-util", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "wasi" 1029 | version = "0.11.0+wasi-snapshot-preview1" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1032 | 1033 | [[package]] 1034 | name = "wasm-bindgen" 1035 | version = "0.2.89" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" 1038 | dependencies = [ 1039 | "cfg-if", 1040 | "wasm-bindgen-macro", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "wasm-bindgen-backend" 1045 | version = "0.2.89" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" 1048 | dependencies = [ 1049 | "bumpalo", 1050 | "log", 1051 | "once_cell", 1052 | "proc-macro2", 1053 | "quote", 1054 | "syn", 1055 | "wasm-bindgen-shared", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "wasm-bindgen-macro" 1060 | version = "0.2.89" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" 1063 | dependencies = [ 1064 | "quote", 1065 | "wasm-bindgen-macro-support", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "wasm-bindgen-macro-support" 1070 | version = "0.2.89" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" 1073 | dependencies = [ 1074 | "proc-macro2", 1075 | "quote", 1076 | "syn", 1077 | "wasm-bindgen-backend", 1078 | "wasm-bindgen-shared", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "wasm-bindgen-shared" 1083 | version = "0.2.89" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" 1086 | 1087 | [[package]] 1088 | name = "web-sys" 1089 | version = "0.3.66" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" 1092 | dependencies = [ 1093 | "js-sys", 1094 | "wasm-bindgen", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "webpki-roots" 1099 | version = "0.25.3" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" 1102 | 1103 | [[package]] 1104 | name = "winapi" 1105 | version = "0.3.9" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1108 | dependencies = [ 1109 | "winapi-i686-pc-windows-gnu", 1110 | "winapi-x86_64-pc-windows-gnu", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "winapi-i686-pc-windows-gnu" 1115 | version = "0.4.0" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1118 | 1119 | [[package]] 1120 | name = "winapi-util" 1121 | version = "0.1.6" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 1124 | dependencies = [ 1125 | "winapi", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "winapi-x86_64-pc-windows-gnu" 1130 | version = "0.4.0" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1133 | 1134 | [[package]] 1135 | name = "windows-core" 1136 | version = "0.51.1" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" 1139 | dependencies = [ 1140 | "windows-targets 0.48.5", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "windows-sys" 1145 | version = "0.45.0" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1148 | dependencies = [ 1149 | "windows-targets 0.42.2", 1150 | ] 1151 | 1152 | [[package]] 1153 | name = "windows-sys" 1154 | version = "0.48.0" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1157 | dependencies = [ 1158 | "windows-targets 0.48.5", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "windows-sys" 1163 | version = "0.52.0" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1166 | dependencies = [ 1167 | "windows-targets 0.52.0", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "windows-targets" 1172 | version = "0.42.2" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1175 | dependencies = [ 1176 | "windows_aarch64_gnullvm 0.42.2", 1177 | "windows_aarch64_msvc 0.42.2", 1178 | "windows_i686_gnu 0.42.2", 1179 | "windows_i686_msvc 0.42.2", 1180 | "windows_x86_64_gnu 0.42.2", 1181 | "windows_x86_64_gnullvm 0.42.2", 1182 | "windows_x86_64_msvc 0.42.2", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "windows-targets" 1187 | version = "0.48.5" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1190 | dependencies = [ 1191 | "windows_aarch64_gnullvm 0.48.5", 1192 | "windows_aarch64_msvc 0.48.5", 1193 | "windows_i686_gnu 0.48.5", 1194 | "windows_i686_msvc 0.48.5", 1195 | "windows_x86_64_gnu 0.48.5", 1196 | "windows_x86_64_gnullvm 0.48.5", 1197 | "windows_x86_64_msvc 0.48.5", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "windows-targets" 1202 | version = "0.52.0" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 1205 | dependencies = [ 1206 | "windows_aarch64_gnullvm 0.52.0", 1207 | "windows_aarch64_msvc 0.52.0", 1208 | "windows_i686_gnu 0.52.0", 1209 | "windows_i686_msvc 0.52.0", 1210 | "windows_x86_64_gnu 0.52.0", 1211 | "windows_x86_64_gnullvm 0.52.0", 1212 | "windows_x86_64_msvc 0.52.0", 1213 | ] 1214 | 1215 | [[package]] 1216 | name = "windows_aarch64_gnullvm" 1217 | version = "0.42.2" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1220 | 1221 | [[package]] 1222 | name = "windows_aarch64_gnullvm" 1223 | version = "0.48.5" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1226 | 1227 | [[package]] 1228 | name = "windows_aarch64_gnullvm" 1229 | version = "0.52.0" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 1232 | 1233 | [[package]] 1234 | name = "windows_aarch64_msvc" 1235 | version = "0.42.2" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1238 | 1239 | [[package]] 1240 | name = "windows_aarch64_msvc" 1241 | version = "0.48.5" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1244 | 1245 | [[package]] 1246 | name = "windows_aarch64_msvc" 1247 | version = "0.52.0" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 1250 | 1251 | [[package]] 1252 | name = "windows_i686_gnu" 1253 | version = "0.42.2" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1256 | 1257 | [[package]] 1258 | name = "windows_i686_gnu" 1259 | version = "0.48.5" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1262 | 1263 | [[package]] 1264 | name = "windows_i686_gnu" 1265 | version = "0.52.0" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 1268 | 1269 | [[package]] 1270 | name = "windows_i686_msvc" 1271 | version = "0.42.2" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1274 | 1275 | [[package]] 1276 | name = "windows_i686_msvc" 1277 | version = "0.48.5" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1280 | 1281 | [[package]] 1282 | name = "windows_i686_msvc" 1283 | version = "0.52.0" 1284 | source = "registry+https://github.com/rust-lang/crates.io-index" 1285 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 1286 | 1287 | [[package]] 1288 | name = "windows_x86_64_gnu" 1289 | version = "0.42.2" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1292 | 1293 | [[package]] 1294 | name = "windows_x86_64_gnu" 1295 | version = "0.48.5" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1298 | 1299 | [[package]] 1300 | name = "windows_x86_64_gnu" 1301 | version = "0.52.0" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 1304 | 1305 | [[package]] 1306 | name = "windows_x86_64_gnullvm" 1307 | version = "0.42.2" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1310 | 1311 | [[package]] 1312 | name = "windows_x86_64_gnullvm" 1313 | version = "0.48.5" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1316 | 1317 | [[package]] 1318 | name = "windows_x86_64_gnullvm" 1319 | version = "0.52.0" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 1322 | 1323 | [[package]] 1324 | name = "windows_x86_64_msvc" 1325 | version = "0.42.2" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1328 | 1329 | [[package]] 1330 | name = "windows_x86_64_msvc" 1331 | version = "0.48.5" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1334 | 1335 | [[package]] 1336 | name = "windows_x86_64_msvc" 1337 | version = "0.52.0" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 1340 | 1341 | [[package]] 1342 | name = "zeroize" 1343 | version = "1.7.0" 1344 | source = "registry+https://github.com/rust-lang/crates.io-index" 1345 | checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" 1346 | --------------------------------------------------------------------------------