Compare commits
1 commit
master
...
2023-day1-
Author | SHA1 | Date | |
---|---|---|---|
969d6d3977 |
33 changed files with 72 additions and 2398 deletions
25
2023/rust/Cargo.lock
generated
25
2023/rust/Cargo.lock
generated
|
@ -5,28 +5,3 @@ version = 3
|
|||
[[package]]
|
||||
name = "aoc2023"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
|
|
@ -103,4 +103,3 @@ name = "day25"
|
|||
path = "src/day25.rs"
|
||||
|
||||
[dependencies]
|
||||
nom = "7.1.3"
|
||||
|
|
|
@ -1,115 +1,102 @@
|
|||
use crate::prelude::*;
|
||||
use std::cell::OnceCell;
|
||||
|
||||
mod prelude;
|
||||
|
||||
const DIGITS = OnceCell::new();
|
||||
|
||||
fn main() {
|
||||
Day1::show(day_input(1), day_input(1))
|
||||
}
|
||||
|
||||
const ASCII_ZERO: u8 = 0x30;
|
||||
const ASCII_NINE: u8 = 0x39;
|
||||
|
||||
let value: &String = digits.get_or_init(|| {
|
||||
[
|
||||
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
|
||||
]
|
||||
.map(str::as_bytes)});
|
||||
assert_eq!(value, "Hello, World!");
|
||||
assert!(cell.get().is_some());
|
||||
|
||||
const DIGITS: [&[u8]; 10] = ;
|
||||
const DIGITS_REV: [&[u8]; 10] = [
|
||||
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
|
||||
]
|
||||
.map(|s| s.chars().rev().collect::<String>().as_bytes());
|
||||
|
||||
type Solution = i64;
|
||||
|
||||
struct Day1 {}
|
||||
impl Day1 {
|
||||
fn calibration_value(line: &str) -> i128 {
|
||||
println!("{line}");
|
||||
let bytes = line.as_bytes();
|
||||
let mut first_digit: Option<u8> = None;
|
||||
let mut last_digit: Option<u8> = None;
|
||||
for i in 0..bytes.len() {
|
||||
let n = bytes.len() - 1 - i;
|
||||
println!("{n} {i}");
|
||||
if first_digit.is_none() && (0x30..=0x39).contains(&bytes[i]) {
|
||||
first_digit = Some(bytes[i] - 0x30);
|
||||
println!("found first {first_digit:?}");
|
||||
}
|
||||
if last_digit.is_none() && (0x30..=0x39).contains(&bytes[n]) {
|
||||
last_digit = Some(bytes[n] - 0x30);
|
||||
println!("found last {last_digit:?}");
|
||||
}
|
||||
if first_digit.is_some() && last_digit.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
println!("{:?} {:?}", first_digit, last_digit);
|
||||
Into::<i128>::into(first_digit.or(last_digit).unwrap() * 10)
|
||||
+ Into::<i128>::into(last_digit.or(first_digit).unwrap())
|
||||
fn is_digit(b: &u8) -> bool {
|
||||
(ASCII_ZERO..=ASCII_NINE).contains(b)
|
||||
}
|
||||
|
||||
fn num_name(s: &[u8]) -> Option<u8> {
|
||||
for (i, w) in [
|
||||
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
|
||||
]
|
||||
.map(|s| s.as_bytes())
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if s.starts_with(w) {
|
||||
return Some(i as u8);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
fn to_digit(b: &u8) -> Solution {
|
||||
i64::from(b - ASCII_ZERO)
|
||||
}
|
||||
|
||||
fn num_name_end(s: &[u8]) -> Option<u8> {
|
||||
for (i, w) in [
|
||||
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
|
||||
]
|
||||
.map(|s| s.as_bytes())
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if s.ends_with(w) {
|
||||
return Some(i as u8);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
fn calibration_value(line_bytes: &[u8]) -> i64 {
|
||||
let i1 = line_bytes.iter().position(Self::is_digit).unwrap();
|
||||
let i2 = line_bytes[i1..]
|
||||
.iter()
|
||||
.rev()
|
||||
.position(Self::is_digit)
|
||||
.unwrap_or(i1);
|
||||
|
||||
Self::to_digit(&line_bytes[i1]) * 10 + Self::to_digit(&line_bytes[i2])
|
||||
}
|
||||
|
||||
fn calibration_value_words(line: &str) -> i128 {
|
||||
println!("{line}");
|
||||
let bytes = line.as_bytes();
|
||||
let mut first_digit: Option<u8> = None;
|
||||
let mut last_digit: Option<u8> = None;
|
||||
for i in 0..bytes.len() {
|
||||
let n = bytes.len() - 1 - i;
|
||||
println!("NI: {n} {i}");
|
||||
if first_digit.is_none() {
|
||||
if (0x30..=0x39).contains(&bytes[i]) {
|
||||
first_digit = Some(bytes[i] - 0x30);
|
||||
println!("found first {first_digit:?}");
|
||||
} else if let Some(n) = Self::num_name(&bytes[i..]) {
|
||||
first_digit = Some(n);
|
||||
println!("found first text {first_digit:?}");
|
||||
// part 2
|
||||
|
||||
fn num_name(s: &[u8], digits: &[&[u8]]) -> Option<i64> {
|
||||
digits
|
||||
.iter()
|
||||
.position(|d| s.starts_with(d))
|
||||
.map(|n| n as i64)
|
||||
}
|
||||
|
||||
fn num_or_digit(s: &[u8], digits: &[&[u8]]) -> Option<Solution> {
|
||||
if Self::is_digit(&s[0]) {
|
||||
Some(Self::to_digit(&s[0]))
|
||||
} else {
|
||||
Self::num_name(s, digits)
|
||||
}
|
||||
}
|
||||
|
||||
fn calibration_value_words(line_bytes: &[u8]) -> i64 {
|
||||
for i in 0..line_bytes.len() {
|
||||
if let Some(first_digit) = Self::num_or_digit(&line_bytes[i..], &DIGITS) {
|
||||
for j in (i..line_bytes.len()).rev() {
|
||||
if let Some(last_digit) = Self::num_or_digit(&line_bytes[i..j], &DIGITS_REV) {
|
||||
return first_digit * 10 + last_digit;
|
||||
}
|
||||
}
|
||||
}
|
||||
if last_digit.is_none() {
|
||||
println!("{:?}", &bytes[..=n]);
|
||||
if (0x30..=0x39).contains(&bytes[n]) {
|
||||
last_digit = Some(bytes[n] - 0x30);
|
||||
println!("found last {last_digit:?}");
|
||||
} else if let Some(n) = Self::num_name_end(&bytes[..=n]) {
|
||||
last_digit = Some(n);
|
||||
println!("found last text {last_digit:?}");
|
||||
}
|
||||
}
|
||||
if first_digit.is_some() && last_digit.is_some() {
|
||||
break;
|
||||
return first_digit * 10 + first_digit;
|
||||
}
|
||||
}
|
||||
println!("{:?} {:?}", first_digit, last_digit);
|
||||
Into::<i128>::into(first_digit.or(last_digit).unwrap() * 10)
|
||||
+ Into::<i128>::into(last_digit.or(first_digit).unwrap())
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl AoCSolution for Day1 {
|
||||
type Input = String;
|
||||
type Solution = i128;
|
||||
type Solution = i64;
|
||||
|
||||
fn part1(input: Self::Input) -> Self::Solution {
|
||||
input.lines().map(Self::calibration_value).sum()
|
||||
input
|
||||
.lines()
|
||||
.map(|l| Self::calibration_value(l.as_bytes()))
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn part2(input: Self::Input) -> Self::Solution {
|
||||
input.lines().map(Self::calibration_value_words).sum()
|
||||
input
|
||||
.lines()
|
||||
.map(|l| Self::calibration_value_words(l.as_bytes()))
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
mod prelude;
|
||||
extern crate nom;
|
||||
use crate::prelude::*;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{tag, take_while},
|
||||
combinator::{map_res, value},
|
||||
multi::separated_list0,
|
||||
IResult,
|
||||
};
|
||||
use std::cmp::max;
|
||||
|
||||
fn main() {
|
||||
Day2::show(day_input(2), day_input(2))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum Reveal {
|
||||
Red(usize),
|
||||
Green(usize),
|
||||
Blue(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Game {
|
||||
id: usize,
|
||||
reveals: Vec<Reveal>,
|
||||
}
|
||||
|
||||
// parser
|
||||
fn int(input: &str) -> IResult<&str, usize> {
|
||||
map_res(take_while(char::is_numeric), str::parse)(input)
|
||||
}
|
||||
|
||||
fn reveal(input: &str) -> IResult<&str, Reveal> {
|
||||
let (input, num) = int(input)?;
|
||||
alt((
|
||||
value(Reveal::Red(num), tag(" red")),
|
||||
value(Reveal::Green(num), tag(" green")),
|
||||
value(Reveal::Blue(num), tag(" blue")),
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn game(input: &str) -> IResult<&str, Game> {
|
||||
let (input, id) = int(tag("Game ")(input)?.0)?;
|
||||
let (input, reveals) =
|
||||
separated_list0(alt((tag(", "), tag("; "))), reveal)(tag(": ")(input)?.0)?;
|
||||
Ok((input, Game { id, reveals }))
|
||||
}
|
||||
|
||||
impl Game {
|
||||
fn possible_power(&self) -> usize {
|
||||
// TODO: probably the last place to cleanup nicely
|
||||
let mut factors: [usize; 3] = [0, 0, 0];
|
||||
for r in &self.reveals {
|
||||
match r {
|
||||
Reveal::Red(n) => factors[0] = max(*n, factors[0]),
|
||||
Reveal::Green(n) => factors[1] = max(*n, factors[1]),
|
||||
Reveal::Blue(n) => factors[2] = max(*n, factors[2]),
|
||||
}
|
||||
}
|
||||
factors.iter().fold(1, |a, b| a * b)
|
||||
}
|
||||
}
|
||||
|
||||
struct Day2 {}
|
||||
impl AoCSolution for Day2 {
|
||||
type Input = String;
|
||||
type Solution = usize;
|
||||
|
||||
fn part1(input: Self::Input) -> Self::Solution {
|
||||
let valid = |r: &Reveal| match r {
|
||||
Reveal::Red(n) => *n <= 12,
|
||||
Reveal::Green(n) => *n <= 13,
|
||||
Reveal::Blue(n) => *n <= 14,
|
||||
};
|
||||
|
||||
input
|
||||
.lines()
|
||||
.map(|s| game(s).unwrap().1)
|
||||
.filter(|g| g.reveals.iter().all(valid))
|
||||
.map(|g| g.id)
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn part2(input: Self::Input) -> Self::Solution {
|
||||
input
|
||||
.lines()
|
||||
.map(|s| game(s).unwrap().1.possible_power())
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = r#"Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
|
||||
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
|
||||
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
|
||||
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
|
||||
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green"#;
|
||||
assert_eq!(Day2::part1(input.into()), 8);
|
||||
assert_eq!(Day2::part2(input.into()), 2286);
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
mod prelude;
|
||||
use std::{collections::HashSet, iter::FromIterator};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
Day3::show(day_input(3), day_input(3))
|
||||
}
|
||||
|
||||
struct Day3 {}
|
||||
|
||||
impl Day3 {
|
||||
fn adjacent_nums(chargrid: &Vec<Vec<char>>, y: usize, x: usize) -> Vec<usize> {
|
||||
let mut result = vec![];
|
||||
let h = chargrid.len();
|
||||
let w = chargrid[0].len();
|
||||
let miny = (y - 1).clamp(0, h);
|
||||
let minx = (x - 1).clamp(0, w);
|
||||
let maxy = (y + 1).clamp(0, h);
|
||||
let maxx = (x + 1).clamp(0, w);
|
||||
let mut scanned: HashSet<(usize, usize)> = HashSet::new();
|
||||
println!("symbol at {y} {x}: {}", chargrid[y][x]);
|
||||
|
||||
for y in miny..=maxy {
|
||||
for x in minx..=maxx {
|
||||
if scanned.contains(&(y, x)) {
|
||||
continue;
|
||||
}
|
||||
if chargrid[y][x].is_digit(10) {
|
||||
println!("{y} {x} {}", chargrid[y][x]);
|
||||
scanned.insert((y, x));
|
||||
let mut rx = x;
|
||||
let mut lx = x;
|
||||
for rrx in (x + 1)..w {
|
||||
if !chargrid[y][rrx].is_digit(10) {
|
||||
break;
|
||||
}
|
||||
rx = rrx;
|
||||
scanned.insert((y, rx));
|
||||
}
|
||||
for llx in (0..=(x - 1)).rev() {
|
||||
if !chargrid[y][llx].is_digit(10) {
|
||||
break;
|
||||
}
|
||||
lx = llx;
|
||||
scanned.insert((y, lx));
|
||||
}
|
||||
let ns = String::from_iter(&chargrid[y][lx..=rx]);
|
||||
println!("ns: {}", ns);
|
||||
if let Ok(n) = ns.parse() {
|
||||
result.push(n);
|
||||
println!("FOUND: {n}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl AoCSolution for Day3 {
|
||||
type Input = String;
|
||||
type Solution = usize;
|
||||
|
||||
fn part1(input: Self::Input) -> Self::Solution {
|
||||
let mut result = 0;
|
||||
let chargrid: Vec<Vec<char>> = input.lines().map(|s| s.chars().collect()).collect();
|
||||
println!("{chargrid:?}");
|
||||
for y in 0..chargrid.len() {
|
||||
let line = &chargrid[y];
|
||||
for x in 0..line.len() {
|
||||
let c = line[x];
|
||||
if !c.is_digit(10) && c != '.' {
|
||||
result += Self::adjacent_nums(&chargrid, y, x).iter().sum::<usize>();
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn part2(input: Self::Input) -> Self::Solution {
|
||||
let mut result = 0;
|
||||
let chargrid: Vec<Vec<char>> = input.lines().map(|s| s.chars().collect()).collect();
|
||||
println!("{chargrid:?}");
|
||||
for y in 0..chargrid.len() {
|
||||
let line = &chargrid[y];
|
||||
for x in 0..line.len() {
|
||||
let c = line[x];
|
||||
if c == '*' {
|
||||
let nums = Self::adjacent_nums(&chargrid, y, x);
|
||||
println!("nums: {nums:?}");
|
||||
if nums.len() == 2 {
|
||||
result += nums[0] * nums[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = r#"467..114..
|
||||
...*......
|
||||
..35..633.
|
||||
......#...
|
||||
617*......
|
||||
.....+.58.
|
||||
..592.....
|
||||
......755.
|
||||
...$.*....
|
||||
.664.598.."#;
|
||||
println!("input:\n{input}");
|
||||
assert_eq!(Day3::part1(input.into()), 4361);
|
||||
assert_eq!(Day3::part2(input.into()), 467835);
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
mod prelude;
|
||||
extern crate nom;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{tag, take_while},
|
||||
character::complete::space1,
|
||||
combinator::{map_res, value},
|
||||
multi::separated_list0,
|
||||
sequence::tuple,
|
||||
IResult,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
iter::FromIterator,
|
||||
};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
Day4::show(day_input(4), day_input(4))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Card {
|
||||
id: usize,
|
||||
winning: HashSet<usize>,
|
||||
has: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Card {
|
||||
fn num_winning(&self) -> usize {
|
||||
self.has.iter().filter(|n| self.winning.contains(n)).count() as usize
|
||||
}
|
||||
|
||||
fn points(&self) -> usize {
|
||||
let n = self.num_winning();
|
||||
if n >= 1 {
|
||||
2_u64.pow((n - 1) as u32) as usize
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parser
|
||||
fn int(input: &str) -> IResult<&str, usize> {
|
||||
map_res(take_while(char::is_numeric), str::parse)(input)
|
||||
}
|
||||
|
||||
fn card(input: &str) -> IResult<&str, Card> {
|
||||
let (input, id) = int(tuple((tag("Card"), space1))(input)?.0)?;
|
||||
let (input, winning_list) = separated_list0(space1, int)(tuple((tag(":"), space1))(input)?.0)?;
|
||||
let (input, has) = separated_list0(space1, int)(tuple((space1, tag("|"), space1))(input)?.0)?;
|
||||
let winning = HashSet::from_iter(winning_list);
|
||||
Ok((input, Card { id, winning, has }))
|
||||
}
|
||||
|
||||
struct Day4 {}
|
||||
impl Day4 {}
|
||||
impl AoCSolution for Day4 {
|
||||
type Input = String;
|
||||
type Solution = usize;
|
||||
|
||||
fn part1(input: Self::Input) -> Self::Solution {
|
||||
input.lines().map(|s| card(s).unwrap().1.points()).sum()
|
||||
}
|
||||
|
||||
fn part2(input: Self::Input) -> Self::Solution {
|
||||
let mut result = 0;
|
||||
let mut copies: HashMap<usize, usize> = HashMap::new();
|
||||
input.lines().for_each(|s| {
|
||||
let card = card(s).unwrap().1;
|
||||
println!("{}, cur result: {}", card.id, result);
|
||||
let mut num_copies = 1;
|
||||
if let Some(extra_copies) = copies.get(&card.id) {
|
||||
num_copies += extra_copies;
|
||||
}
|
||||
for _ in 1..=num_copies {
|
||||
for i in card.id..=(card.id + card.num_winning()) {
|
||||
if let Some(n) = copies.get_mut(&i) {
|
||||
*n += 1;
|
||||
} else {
|
||||
copies.insert(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
result += num_copies
|
||||
});
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = r#"Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
|
||||
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
|
||||
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
|
||||
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
|
||||
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
|
||||
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11"#;
|
||||
println!("input:\n{input}");
|
||||
assert_eq!(Day4::part1(input.into()), 13);
|
||||
assert_eq!(Day4::part2(input.into()), 30);
|
||||
}
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
mod prelude;
|
||||
use std::collections::HashMap;
|
||||
extern crate nom;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{tag, take_until, take_while},
|
||||
character::complete::{line_ending, newline, space1},
|
||||
combinator::{map_res, value},
|
||||
multi::separated_list0,
|
||||
sequence::tuple,
|
||||
IResult,
|
||||
};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
Day5::show(day_input(5), day_input(5))
|
||||
}
|
||||
|
||||
#[derive(Hash, Debug, PartialEq, Eq, Clone)]
|
||||
enum Layer {
|
||||
Seed,
|
||||
Soil,
|
||||
Fertilizer,
|
||||
Water,
|
||||
Light,
|
||||
Temperature,
|
||||
Humidity,
|
||||
Location,
|
||||
}
|
||||
|
||||
#[derive(Hash, Eq, Debug, PartialEq, Clone)]
|
||||
struct LayerDir {
|
||||
from: Layer,
|
||||
to: Layer,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Mapping {
|
||||
from_start: usize,
|
||||
to_start: usize,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Almanac {
|
||||
seeds: Vec<usize>,
|
||||
mappings: HashMap<LayerDir, Vec<Mapping>>,
|
||||
}
|
||||
|
||||
fn layer(input: &str) -> IResult<&str, Layer> {
|
||||
alt((
|
||||
value(Layer::Seed, tag("seed")),
|
||||
value(Layer::Soil, tag("soil")),
|
||||
value(Layer::Fertilizer, tag("fertilizer")),
|
||||
value(Layer::Water, tag("water")),
|
||||
value(Layer::Light, tag("light")),
|
||||
value(Layer::Temperature, tag("temperature")),
|
||||
value(Layer::Humidity, tag("humidity")),
|
||||
value(Layer::Location, tag("location")),
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn int(input: &str) -> IResult<&str, usize> {
|
||||
map_res(take_while(char::is_numeric), str::parse)(input)
|
||||
}
|
||||
|
||||
fn mapping(input: &str) -> IResult<&str, Mapping> {
|
||||
println!("mapping input: {input:?}");
|
||||
let (input, nums) = separated_list0(space1, int)(input)?;
|
||||
println!("nums: {nums:?}");
|
||||
Ok((
|
||||
input,
|
||||
Mapping {
|
||||
from_start: nums[0],
|
||||
to_start: nums[1],
|
||||
length: nums[2],
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn mapping_entry(input: &str) -> IResult<&str, (LayerDir, Vec<Mapping>)> {
|
||||
let (input, (from, _, to, _)) = tuple((layer, tag("-to-"), layer, tag(" map:\n")))(input)?;
|
||||
let (rest, input) = take_until("\n\n")(input)?;
|
||||
let (input, mappings) = separated_list0(newline, mapping)(input)?;
|
||||
Ok((rest, (LayerDir { from, to }, mappings)))
|
||||
}
|
||||
|
||||
fn almanac(input: &str) -> IResult<&str, Almanac> {
|
||||
let (input, seeds) = separated_list0(space1, int)(tag("seeds: ")(input)?.0)?;
|
||||
let (input, mapping_tuples) =
|
||||
separated_list0(tuple((line_ending, line_ending)), mapping_entry)(tag("\n\n")(input)?.0)?;
|
||||
let mappings: HashMap<LayerDir, Vec<Mapping>> = mapping_tuples.into_iter().collect();
|
||||
Ok((input, Almanac { seeds, mappings }))
|
||||
}
|
||||
|
||||
struct Day5 {}
|
||||
impl AoCSolution for Day5 {
|
||||
type Input = String;
|
||||
type Solution = usize;
|
||||
|
||||
fn part1(input: Self::Input) -> Self::Solution {
|
||||
let almanac = almanac(&input);
|
||||
println!("{almanac:?}");
|
||||
0
|
||||
}
|
||||
|
||||
fn part2(input: Self::Input) -> Self::Solution {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = r#"seeds: 79 14 55 13
|
||||
|
||||
seed-to-soil map:
|
||||
50 98 2
|
||||
52 50 48
|
||||
|
||||
soil-to-fertilizer map:
|
||||
0 15 37
|
||||
37 52 2
|
||||
39 0 15
|
||||
|
||||
fertilizer-to-water map:
|
||||
49 53 8
|
||||
0 11 42
|
||||
42 0 7
|
||||
57 7 4
|
||||
|
||||
water-to-light map:
|
||||
88 18 7
|
||||
18 25 70
|
||||
|
||||
light-to-temperature map:
|
||||
45 77 23
|
||||
81 45 19
|
||||
68 64 13
|
||||
|
||||
temperature-to-humidity map:
|
||||
0 69 1
|
||||
1 0 69
|
||||
|
||||
humidity-to-location map:
|
||||
60 56 37
|
||||
56 93 4"#;
|
||||
assert_eq!(Day5::part1(input.into()), 35);
|
||||
assert_eq!(Day5::part2(input.into()), 0);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@ pub use std::io::Read;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::{env, io};
|
||||
|
||||
pub type Reader = Box<dyn Read>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InputFileError {
|
||||
VarError(env::VarError),
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
mod prelude;
|
||||
use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
Day5::show(day_input(5), day_input(5))
|
||||
}
|
||||
|
||||
struct Day5 {}
|
||||
impl AoCSolution for Day5 {
|
||||
type Input = String;
|
||||
type Solution = usize;
|
||||
|
||||
fn part1(input: Self::Input) -> Self::Solution {
|
||||
0
|
||||
}
|
||||
|
||||
fn part2(input: Self::Input) -> Self::Solution {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = r#""#;
|
||||
assert_eq!(Day2::part1(input.into()), 1);
|
||||
assert_eq!(Day2::part2(input.into()), 0);
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
AOC_YEAR="${AOC_YEAR:-2024}"
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "Error: No day provided"
|
||||
exit 1
|
||||
fi
|
||||
DAY="$1"
|
||||
f="$HOME/.cache/aoc$AOC_YEAR/$DAY.input"
|
||||
if [ -f "$f" ]; then
|
||||
echo "Skip: File already exists"
|
||||
exit 0
|
||||
fi
|
||||
url="https://adventofcode.com/$AOC_YEAR/day/$DAY/input"
|
||||
cookie_file="$HOME/.advent-of-code-session-cookie"
|
||||
if [[ ! -f "$cookie_file" ]]; then
|
||||
echo "Cookie file not found: $cookie_file"
|
||||
exit 1
|
||||
fi
|
||||
cookie="$(cat "$cookie_file")"
|
||||
mkdir -p "$(dirname "$f")"
|
||||
if curl -s --fail-with-body -X GET "$url" -H "Cookie:$cookie" > "$f"; then
|
||||
cat "$f"
|
||||
echo "Downloaded $url to $f - contents have been output to this terminal as well"
|
||||
exit 0
|
||||
else
|
||||
echo "Error: curl failed (are you sure the input is available now?)"
|
||||
rm -f "$f"
|
||||
exit 1
|
||||
fi
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1 +0,0 @@
|
|||
use flake
|
3
2024/rust/.gitignore
vendored
3
2024/rust/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
/target
|
||||
|
||||
/.direnv
|
7
2024/rust/Cargo.lock
generated
7
2024/rust/Cargo.lock
generated
|
@ -1,7 +0,0 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aoc2024"
|
||||
version = "1.0.0"
|
|
@ -1,104 +0,0 @@
|
|||
[package]
|
||||
name = "aoc2024"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "day1"
|
||||
path = "src/day1.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day2"
|
||||
path = "src/day2.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day3"
|
||||
path = "src/day3.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day4"
|
||||
path = "src/day4.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day5"
|
||||
path = "src/day5.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day6"
|
||||
path = "src/day6.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day7"
|
||||
path = "src/day7.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day8"
|
||||
path = "src/day8.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day9"
|
||||
path = "src/day9.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day10"
|
||||
path = "src/day10.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day11"
|
||||
path = "src/day11.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day12"
|
||||
path = "src/day12.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day13"
|
||||
path = "src/day13.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day14"
|
||||
path = "src/day14.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day15"
|
||||
path = "src/day15.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day16"
|
||||
path = "src/day16.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day17"
|
||||
path = "src/day17.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day18"
|
||||
path = "src/day18.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day19"
|
||||
path = "src/day19.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day20"
|
||||
path = "src/day20.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day21"
|
||||
path = "src/day21.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day22"
|
||||
path = "src/day22.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day23"
|
||||
path = "src/day23.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day24"
|
||||
path = "src/day24.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day25"
|
||||
path = "src/day25.rs"
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1732937961,
|
||||
"narHash": "sha256-B5pYT+IVaqcrfOekkwKvx/iToDnuQWzc2oyDxzzBDc4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4703b8d2c708e13a8cab03d865f90973536dcdf5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
}: let
|
||||
supportedSystems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"];
|
||||
forEachSupportedSystem = f:
|
||||
nixpkgs.lib.genAttrs supportedSystems (system:
|
||||
f {
|
||||
pkgs = import nixpkgs {inherit system;};
|
||||
});
|
||||
in {
|
||||
devShells = forEachSupportedSystem ({pkgs}: {
|
||||
default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
cargo
|
||||
rustc
|
||||
rustfmt
|
||||
rustPackages.clippy
|
||||
rust-analyzer
|
||||
curl
|
||||
];
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
# Rust Advent of Code 2024 Solutions
|
||||
|
||||
## Competing
|
||||
|
||||
I compete very lightly. I use [my `at` script][at] like `at 2022-12-02 && ../
|
||||
fetch-input.sh 2` to fetch input as soon as it's available and I use `watchexec
|
||||
-e rs 'clear; cargo test --bin day2 && cargo run --bin day2'` to run my code
|
||||
with tests as I edit them.
|
||||
|
||||
## Running
|
||||
|
||||
### Debug
|
||||
|
||||
```bash
|
||||
cargo run --bin day1
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
```bash
|
||||
cargo test --bin day1
|
||||
```
|
||||
|
||||
### Release Mode
|
||||
|
||||
For speeeeeed!
|
||||
|
||||
```bash
|
||||
cargo build --release --bin day1
|
||||
time ./target/release/day1
|
||||
```
|
||||
|
||||
### Everything
|
||||
|
||||
You can use this `fish` script to build all binaries in release mode and run/
|
||||
time them all:
|
||||
|
||||
```fish
|
||||
cargo build --release --bins
|
||||
time for f in (fd 'day.' target/release/ --type executable --max-depth 1); echo $f; time $f; end
|
||||
```
|
||||
|
||||
[at]: https://git.lyte.dev/lytedev/nix/src/branch/main/modules/home-manager/scripts/common/bin/at
|
|
@ -1,56 +0,0 @@
|
|||
mod prelude;
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut note: HistorianNote = day_input(1).parse().unwrap();
|
||||
show_answers(note.smallests_distances(), note.similarity_score());
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct HistorianNote {
|
||||
left: Vec<i64>,
|
||||
right: Vec<i64>,
|
||||
}
|
||||
|
||||
impl FromStr for HistorianNote {
|
||||
type Err = ParseIntError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut result = Self::default();
|
||||
let mut line_tokens = s.lines().map(|l| l.split_once(" "));
|
||||
while let Some((l, r)) = line_tokens.next().flatten() {
|
||||
result.left.push(l.parse()?);
|
||||
result.right.push(r.parse()?);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl HistorianNote {
|
||||
fn smallests_distances(&mut self) -> i64 {
|
||||
self.left.sort();
|
||||
self.right.sort();
|
||||
zip(&self.left, &self.right).fold(0, |sum, (l, r)| sum + (l - r).abs())
|
||||
}
|
||||
|
||||
fn similarity_score(&self) -> i64 {
|
||||
let frequencies = self.right.iter().fold(HashMap::new(), |mut map, r| {
|
||||
*map.entry(r).or_default() += 1;
|
||||
map
|
||||
});
|
||||
self.left
|
||||
.iter()
|
||||
.fold(0, |sum, l| sum + frequencies.get(l).unwrap_or(&0) * l)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let mut note: HistorianNote = "3 4\n4 3\n2 5\n1 3\n3 9\n3 3".parse().unwrap();
|
||||
assert_eq!(note.smallests_distances(), 11);
|
||||
assert_eq!(note.similarity_score(), 31);
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
mod prelude;
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(10);
|
||||
show_answers(part1(&input), part2(&input));
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> usize {
|
||||
println!("{input}");
|
||||
let mut sum = 0;
|
||||
let map: Vec<Vec<u8>> = input
|
||||
.lines()
|
||||
.map(|l| l.bytes().map(|b| b - b'0').collect())
|
||||
.collect();
|
||||
for (y, row) in map.iter().enumerate() {
|
||||
for (x, height) in row.iter().enumerate() {
|
||||
let mut scores = HashSet::new();
|
||||
if *height == 0 {
|
||||
capture_scores(&map, *height, x, y, &mut scores);
|
||||
sum += scores.len();
|
||||
}
|
||||
println!("{x} {y} {height}");
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
fn capture_scores(
|
||||
map: &Vec<Vec<u8>>,
|
||||
height: u8,
|
||||
x: usize,
|
||||
y: usize,
|
||||
scores: &mut HashSet<(usize, usize)>,
|
||||
) {
|
||||
if height == 9 {
|
||||
scores.insert((x, y));
|
||||
println!("found a 9");
|
||||
}
|
||||
for (nx, ny) in [
|
||||
(x + 1, y),
|
||||
(x.saturating_sub(1), y),
|
||||
(x, y + 1),
|
||||
(x, y.saturating_sub(1)),
|
||||
] {
|
||||
if nx >= map[0].len() || ny >= map.len() {
|
||||
continue;
|
||||
}
|
||||
let next_height = map[ny][nx];
|
||||
if next_height.saturating_sub(height) == 1 {
|
||||
println!("{height} -> {next_height} at {nx},{ny}");
|
||||
capture_scores(map, next_height, nx, ny, scores)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn part2(input: &str) -> usize {
|
||||
println!("{input}");
|
||||
let mut sum = 0;
|
||||
let map: Vec<Vec<u8>> = input
|
||||
.lines()
|
||||
.map(|l| l.bytes().map(|b| b - b'0').collect())
|
||||
.collect();
|
||||
for (y, row) in map.iter().enumerate() {
|
||||
for (x, height) in row.iter().enumerate() {
|
||||
if *height == 0 {
|
||||
sum += find_paths_to_nines(&map, *height, x, y);
|
||||
}
|
||||
println!("{x} {y} {height}");
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
fn find_paths_to_nines(map: &Vec<Vec<u8>>, height: u8, x: usize, y: usize) -> usize {
|
||||
let mut sum = 0;
|
||||
if height == 9 {
|
||||
return 1;
|
||||
}
|
||||
for (nx, ny) in [
|
||||
(x + 1, y),
|
||||
(x.saturating_sub(1), y),
|
||||
(x, y + 1),
|
||||
(x, y.saturating_sub(1)),
|
||||
] {
|
||||
if nx >= map[0].len() || ny >= map.len() {
|
||||
continue;
|
||||
}
|
||||
let next_height = map[ny][nx];
|
||||
if next_height.saturating_sub(height) == 1 {
|
||||
println!("{height} -> {next_height} at {nx},{ny}");
|
||||
sum += find_paths_to_nines(map, next_height, nx, ny)
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = r#"89010123
|
||||
78121874
|
||||
87430965
|
||||
96549874
|
||||
45678903
|
||||
32019012
|
||||
01329801
|
||||
10456732"#;
|
||||
assert_eq!(part1(input), 36);
|
||||
assert_eq!(part2(input), 81);
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
mod prelude;
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(11);
|
||||
show_answers(part1(&input), part2(&input));
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> usize {
|
||||
stone_blinks_by_count(input, 25)
|
||||
}
|
||||
|
||||
fn stone_blink(s: usize) -> Vec<usize> {
|
||||
if s == 0 {
|
||||
return vec![1];
|
||||
}
|
||||
let ss = s.to_string();
|
||||
if ss.len() % 2 == 0 {
|
||||
let mid = ss.len() / 2;
|
||||
let (s1, s2) = (ss[0..mid].parse().unwrap(), ss[mid..].parse().unwrap());
|
||||
return vec![s1, s2];
|
||||
}
|
||||
vec![s * 2024]
|
||||
}
|
||||
|
||||
fn stone_blinks_by_count(input: &str, count: usize) -> usize {
|
||||
let mut stones: HashMap<usize, usize> = input
|
||||
.trim()
|
||||
.split(" ")
|
||||
.map(|n| n.parse().unwrap())
|
||||
.fold(HashMap::new(), |mut acc, n| {
|
||||
*acc.entry(n).or_default() += 1;
|
||||
acc
|
||||
});
|
||||
let mut next_stones: HashMap<usize, usize> = HashMap::new();
|
||||
for _c in 0..count {
|
||||
for (n, c) in &stones {
|
||||
for nn in stone_blink(*n) {
|
||||
*next_stones.entry(nn).or_default() += c;
|
||||
}
|
||||
}
|
||||
println!("afterblink\n{stones:?}\n{next_stones:?}");
|
||||
(stones, next_stones) = (next_stones, HashMap::new());
|
||||
}
|
||||
stones.into_values().sum()
|
||||
}
|
||||
|
||||
fn part2(input: &str) -> usize {
|
||||
stone_blinks_by_count(input, 75)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = r#"125 17"#;
|
||||
assert_eq!(part1(input), 55312);
|
||||
assert_eq!(part2(input), 65601038650482);
|
||||
}
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
mod prelude;
|
||||
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(12);
|
||||
show_answers(part1(&input), part2(&input));
|
||||
}
|
||||
|
||||
enum Dir {
|
||||
N,
|
||||
S,
|
||||
E,
|
||||
W,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Region {
|
||||
locations: HashSet<(usize, usize)>,
|
||||
perimeters: usize,
|
||||
}
|
||||
|
||||
impl Region {
|
||||
fn measure_at(bytes: &Vec<Vec<u8>>, sx: usize, sy: usize) -> Self {
|
||||
let target = bytes[sy][sx];
|
||||
let (height, width) = (bytes.len(), bytes[0].len());
|
||||
let mut candidates = vec![(sx, sy)];
|
||||
let mut locations = HashSet::from_iter(candidates.clone());
|
||||
let mut perimeters = 0;
|
||||
|
||||
while let Some((x, y)) = candidates.pop() {
|
||||
print!("AT {x},{y}: ");
|
||||
for (nx, ny) in [
|
||||
(x, y.saturating_sub(1)),
|
||||
(x, y + 1),
|
||||
(x + 1, y),
|
||||
(x.saturating_sub(1), y),
|
||||
] {
|
||||
if (nx, ny) == (x, y) || nx >= width || ny >= height {
|
||||
print!("EDGE@{nx},{ny}; ");
|
||||
perimeters += 1;
|
||||
continue;
|
||||
}
|
||||
if bytes[ny][nx] != target {
|
||||
print!("EDGE@{nx},{ny}; ");
|
||||
perimeters += 1;
|
||||
continue;
|
||||
}
|
||||
if !locations.contains(&(nx, ny)) {
|
||||
candidates.push((nx, ny));
|
||||
locations.insert((nx, ny));
|
||||
}
|
||||
}
|
||||
print!("\n");
|
||||
}
|
||||
Self {
|
||||
locations,
|
||||
perimeters,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn regions(input: &str) -> Vec<Region> {
|
||||
let mut regions = vec![];
|
||||
let bytes: Vec<Vec<u8>> = input
|
||||
.trim()
|
||||
.lines()
|
||||
.map(|l| l.trim().bytes().collect())
|
||||
.collect();
|
||||
let mut regionated: HashSet<(usize, usize)> = HashSet::new();
|
||||
for y in 0..bytes.len() {
|
||||
for x in 0..bytes[y].len() {
|
||||
if regionated.contains(&(x, y)) {
|
||||
continue;
|
||||
}
|
||||
let region = Region::measure_at(&bytes, x, y);
|
||||
regionated.extend(®ion.locations);
|
||||
regions.push(region);
|
||||
}
|
||||
}
|
||||
regions
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> usize {
|
||||
regions(input)
|
||||
.iter()
|
||||
.map(|r| r.locations.len() * r.perimeters)
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn part2(input: &str) -> usize {
|
||||
regions(input)
|
||||
.iter()
|
||||
.map(|r| {
|
||||
println!("{r:?}");
|
||||
// find top edge in region
|
||||
if r.locations.len() <= 2 {
|
||||
return r.locations.len() * 4;
|
||||
}
|
||||
let (sx, sy) = {
|
||||
let (x, mut y) = *r.locations.iter().next().unwrap();
|
||||
y = (1..y)
|
||||
.rev()
|
||||
.find(|y| r.locations.contains(&(x, y - 1)))
|
||||
.unwrap_or(0);
|
||||
(x, y)
|
||||
};
|
||||
|
||||
// walk along region edge and count turns until we end up where we started
|
||||
let mut turns = 0;
|
||||
let ops = [
|
||||
|(x, y)| (x + 1, y),
|
||||
|(x, y)| (x, y + 1),
|
||||
|(x, y): (usize, usize)| (x.saturating_sub(1), y),
|
||||
|(x, y): (usize, usize)| (x, y.saturating_sub(1)),
|
||||
];
|
||||
let mut dir = 0;
|
||||
let (mut x, mut y) = (sx, sy);
|
||||
println!("start at {x},{y}");
|
||||
loop {
|
||||
let initial_dir = dir;
|
||||
while !r.locations.contains(&ops[dir]((x, y))) || ops[dir]((x, y)) == (x, y) {
|
||||
println!("turning @ {x},{y}");
|
||||
dir = (dir + 1) % ops.len();
|
||||
if dir == initial_dir {
|
||||
panic!("turned too many times in one place");
|
||||
}
|
||||
turns += 1;
|
||||
}
|
||||
(x, y) = ops[dir]((x, y));
|
||||
println!("move to {x},{y}");
|
||||
if turns >= 3 && x == sx && y == sy {
|
||||
break;
|
||||
}
|
||||
}
|
||||
println!("turns {turns}");
|
||||
|
||||
// walk each side, counting turns
|
||||
r.locations.len() * turns
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = r#"RRRRIICCFF
|
||||
RRRRIICCCF
|
||||
VVRRRCCFFF
|
||||
VVRCCCJFFF
|
||||
VVVVCJJCFE
|
||||
VVIVCCJJEE
|
||||
VVIIICJJEE
|
||||
MIIIIIJJEE
|
||||
MIIISIJEEE
|
||||
MMMISSJEEE"#;
|
||||
let bytes: Vec<Vec<u8>> = input
|
||||
.trim()
|
||||
.lines()
|
||||
.map(|l| l.trim().bytes().collect())
|
||||
.collect();
|
||||
let region = Region::measure_at(&bytes, 0, 0);
|
||||
assert_eq!(region.locations.len(), 12);
|
||||
assert_eq!(region.perimeters, 18);
|
||||
assert_eq!(part1(input), 1930);
|
||||
assert_eq!(part2(input), 1206);
|
||||
}
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
mod prelude;
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let data: Data = day_input(2).parse().unwrap();
|
||||
show_answers(data.num_safe(), data.num_safe_with_dampener());
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct Report {
|
||||
levels: Vec<i64>,
|
||||
}
|
||||
|
||||
impl FromStr for Report {
|
||||
type Err = ParseIntError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let levels = s
|
||||
.split(" ")
|
||||
.map(|s| s.parse())
|
||||
.collect::<Result<Vec<i64>, ParseIntError>>()?;
|
||||
Ok(Self { levels })
|
||||
}
|
||||
}
|
||||
|
||||
impl Report {
|
||||
fn is_safe(&self, skip_index: Option<usize>) -> bool {
|
||||
let mut ordering: Option<Ordering> = None;
|
||||
let skip_index = skip_index.unwrap_or(self.levels.len() + 1);
|
||||
let max = if skip_index < self.levels.len() {
|
||||
self.levels.len() - 2
|
||||
} else {
|
||||
self.levels.len() - 1
|
||||
};
|
||||
|
||||
for i in 0..max {
|
||||
let mut ai = i;
|
||||
let mut bi = ai + 1;
|
||||
if ai >= skip_index {
|
||||
ai += 1
|
||||
};
|
||||
if bi >= skip_index {
|
||||
bi += 1
|
||||
};
|
||||
let (a, b) = (self.levels[ai], self.levels[bi]);
|
||||
if !Self::safe_level_pair(a, b, &ordering) {
|
||||
return false;
|
||||
}
|
||||
if ordering == None {
|
||||
ordering = Some(a.cmp(&b))
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn is_safe_with_any_single_level_removed(&self) -> bool {
|
||||
for i in 0..self.levels.len() {
|
||||
if self.is_safe(Some(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn safe_level_pair(a: i64, b: i64, kind: &Option<Ordering>) -> bool {
|
||||
if a == b {
|
||||
return false;
|
||||
}
|
||||
if (a - b).abs() > 3 {
|
||||
return false;
|
||||
}
|
||||
if kind.map(|o| o == a.cmp(&b)) == Some(false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Data {
|
||||
reports: Vec<Report>,
|
||||
}
|
||||
|
||||
impl FromStr for Data {
|
||||
type Err = ParseIntError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let reports = s
|
||||
.lines()
|
||||
.map(|s| s.parse())
|
||||
.collect::<Result<Vec<Report>, ParseIntError>>()?;
|
||||
Ok(Self { reports })
|
||||
}
|
||||
}
|
||||
|
||||
impl Data {
|
||||
fn num_safe(&self) -> usize {
|
||||
self.reports.iter().filter(|r| r.is_safe(None)).count()
|
||||
}
|
||||
|
||||
fn num_safe_with_dampener(&self) -> usize {
|
||||
self.reports
|
||||
.iter()
|
||||
.filter(|r| r.is_safe_with_any_single_level_removed())
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let data: Data = "7 6 4 2 1
|
||||
1 2 7 8 9
|
||||
9 7 6 2 1
|
||||
1 3 2 4 5
|
||||
8 6 4 4 1
|
||||
1 3 6 7 9"
|
||||
.parse()
|
||||
.unwrap();
|
||||
assert_eq!(data.num_safe(), 2);
|
||||
assert_eq!(data.num_safe_with_dampener(), 4);
|
||||
// assert_eq!(report.(), 31);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test2() {
|
||||
let report: Report = Report {
|
||||
levels: vec![1, 2, 5, 8, 9, 15],
|
||||
};
|
||||
assert_eq!(report.is_safe(None), false);
|
||||
assert_eq!(report.is_safe_with_any_single_level_removed(), true);
|
||||
// assert_eq!(report.(), 31);
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
mod prelude;
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(3);
|
||||
show_answers(sum_muls(&input), sum_muls_with_do_and_dont(&input));
|
||||
}
|
||||
|
||||
fn sum_muls(input: &str) -> usize {
|
||||
let mut sum = 0;
|
||||
|
||||
// this string slice will be how we keep track of what is "left" to parse
|
||||
// though it would likely be easier to use an index and slice as needed :shrug:
|
||||
let mut s = &input[..];
|
||||
|
||||
// look for the next valid mul operation
|
||||
while let Some(i) = s.find("mul(") {
|
||||
// re-slice to the remaining string
|
||||
s = &s[i + 4..];
|
||||
|
||||
// variables to contain our operands
|
||||
let mut left: Option<usize> = None;
|
||||
let mut right: Option<usize> = None;
|
||||
|
||||
// variable to contain our currently-parsing token, as we will continue
|
||||
// from here one character at a time
|
||||
let mut token = &s[0..0];
|
||||
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
// any digit simply gets "appended" to our token (in reality, we are just increasing the length of the token slice)
|
||||
'0'..='9' => {
|
||||
token = &s[0..token.len() + 1];
|
||||
// the problem indicates that numbers may not be longer than
|
||||
// 3 digits, so if we encounter one, we break out of this
|
||||
// loop and head to the next mul operation
|
||||
if token.len() > 3 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// once we hit a comma, we check that we don't already have
|
||||
// a left operand then parse the number (which we can unwrap
|
||||
// since we are assuring we are dealing with digits above,
|
||||
// though we could easily optimize that out with bit of
|
||||
// arithmetic), advance through our string slice, and reset our
|
||||
// currently-parsing token
|
||||
',' => {
|
||||
if left.is_some() {
|
||||
break;
|
||||
}
|
||||
left = Some(token.parse().unwrap());
|
||||
s = &s[token.len() + 1..];
|
||||
token = &s[0..0];
|
||||
}
|
||||
|
||||
// pretty much the same logic for finding a comma, but for the
|
||||
// right operand
|
||||
')' => {
|
||||
if right.is_some() {
|
||||
break;
|
||||
}
|
||||
right = Some(token.parse().unwrap());
|
||||
s = &s[token.len() + 1..];
|
||||
token = &s[0..0];
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
if let (Some(left), Some(right)) = (left, right) {
|
||||
sum += left * right;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
fn sum_muls_with_do_and_dont(input: &str) -> usize {
|
||||
// the gist of this function is that we look for string segments that are
|
||||
// between do() and don't() operations and only sum_muls for those NOT
|
||||
// between a don't() and do()
|
||||
let mut sum = 0;
|
||||
let mut s = &input[..];
|
||||
|
||||
// since there may be a case where the last do() or don't() is a do(),
|
||||
// we need to know whether or not to parse the "remainder" after we have
|
||||
// processed the last don't() segment which may have ended with a do()
|
||||
let mut mul_rest = true;
|
||||
while let Some(i) = s.find("don't()") {
|
||||
// once we find a don't, we know we want to sum_muls for everything up
|
||||
// to the current position
|
||||
sum += sum_muls(&s[0..i]);
|
||||
|
||||
// then we can advance our slice
|
||||
s = &s[i..];
|
||||
mul_rest = false;
|
||||
if let Some(i) = s.find("do()") {
|
||||
// if we find a do(), set our mul_rest flag appropriately, advance
|
||||
// along the slice, and head to the next iteration (or be done in
|
||||
// the loop)
|
||||
s = &s[i..];
|
||||
mul_rest = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if we "ended" with a d(), we want to sum_muls for the rest of the slice
|
||||
if mul_rest {
|
||||
sum += sum_muls(s);
|
||||
}
|
||||
|
||||
sum
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
assert_eq!(
|
||||
sum_muls("xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"),
|
||||
161
|
||||
);
|
||||
assert_eq!(
|
||||
sum_muls_with_do_and_dont(
|
||||
"xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"
|
||||
),
|
||||
48
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
mod prelude;
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(4);
|
||||
let grid: Vec<Vec<u8>> = input
|
||||
.trim()
|
||||
.lines()
|
||||
.map(|l| l.trim().bytes().collect())
|
||||
.collect();
|
||||
show_answers(count_xmas(&grid), count_mas_x(&grid));
|
||||
}
|
||||
|
||||
fn count_xmas(grid: &Vec<Vec<u8>>) -> usize {
|
||||
let height = grid.len();
|
||||
let width = grid[0].len();
|
||||
let mut counter = 0;
|
||||
|
||||
const XMAS: (u8, u8, u8, u8) = (b'X', b'M', b'A', b'S');
|
||||
const SAMX: (u8, u8, u8, u8) = (b'S', b'A', b'M', b'X');
|
||||
|
||||
for (y, line) in grid.iter().enumerate() {
|
||||
for (x, letter) in line.iter().enumerate() {
|
||||
// let near_left_edge = x < 3;
|
||||
let near_top_edge = y < 3;
|
||||
let near_right_edge = x >= width - 3;
|
||||
let near_bottom_edge = y >= height - 3;
|
||||
|
||||
if !near_right_edge && !near_top_edge {
|
||||
// north-west
|
||||
let ray = (
|
||||
*letter,
|
||||
grid[y - 1][x + 1],
|
||||
grid[y - 2][x + 2],
|
||||
grid[y - 3][x + 3],
|
||||
);
|
||||
if ray == XMAS || ray == SAMX {
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !near_right_edge {
|
||||
// west
|
||||
let ray = (*letter, grid[y][x + 1], grid[y][x + 2], grid[y][x + 3]);
|
||||
if ray == XMAS || ray == SAMX {
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !near_right_edge && !near_bottom_edge {
|
||||
// south-west
|
||||
let ray = (
|
||||
*letter,
|
||||
grid[y + 1][x + 1],
|
||||
grid[y + 2][x + 2],
|
||||
grid[y + 3][x + 3],
|
||||
);
|
||||
if ray == XMAS || ray == SAMX {
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !near_bottom_edge {
|
||||
// south
|
||||
let ray = (*letter, grid[y + 1][x], grid[y + 2][x], grid[y + 3][x]);
|
||||
if ray == XMAS || ray == SAMX {
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
counter
|
||||
}
|
||||
|
||||
fn count_mas_x(grid: &Vec<Vec<u8>>) -> usize {
|
||||
let mut counter = 0;
|
||||
let (width, height) = (grid[0].len(), grid.len());
|
||||
for y in 1..height - 1 {
|
||||
for x in 1..width - 1 {
|
||||
// 'A' will always be in the middle, so we can easily just look for
|
||||
// middle 'A' occurences and then check for opposing 'S' and 'M'
|
||||
// characters
|
||||
if grid[y][x] != b'A' {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check opposing cross-characters
|
||||
let ray1 = (grid[y - 1][x - 1], grid[y + 1][x + 1]);
|
||||
let ray2 = (grid[y + 1][x - 1], grid[y - 1][x + 1]);
|
||||
if (ray1 == (b'M', b'S') || ray1 == (b'S', b'M'))
|
||||
&& (ray2 == (b'M', b'S') || ray2 == (b'S', b'M'))
|
||||
{
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
counter
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = r#"MMMSXXMASM
|
||||
MSAMXMSMSA
|
||||
AMXSXMAAMM
|
||||
MSAMASMSMX
|
||||
XMASAMXAMM
|
||||
XXAMMXXAMA
|
||||
SMSMSASXSS
|
||||
SAXAMASAAA
|
||||
MAMMMXMMMM
|
||||
MXMXAXMASX"#;
|
||||
|
||||
let grid: Vec<Vec<u8>> = input
|
||||
.trim()
|
||||
.lines()
|
||||
.map(|l| l.trim().bytes().collect())
|
||||
.collect();
|
||||
|
||||
assert_eq!(count_xmas(&grid), 18);
|
||||
assert_eq!(count_mas_x(&grid), 9);
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
mod prelude;
|
||||
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(5);
|
||||
let update: ManualUpdate = input.parse().unwrap();
|
||||
show_answers(update.part1(), update.part2());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ManualUpdate {
|
||||
rules: HashMap<i64, HashSet<i64>>,
|
||||
updates: Vec<Vec<i64>>,
|
||||
}
|
||||
|
||||
impl FromStr for ManualUpdate {
|
||||
type Err = ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (rules_string, updates_string) = s.split_once("\n\n").unwrap();
|
||||
let mut rules: HashMap<i64, HashSet<i64>> = HashMap::new();
|
||||
let mut updates = vec![];
|
||||
for rule in rules_string.lines() {
|
||||
let (left, right) = rule.split_once("|").unwrap();
|
||||
let (left, right): (i64, i64) = (left.parse()?, right.parse()?);
|
||||
|
||||
rules.entry(left).or_default().insert(right);
|
||||
}
|
||||
for update in updates_string.lines() {
|
||||
updates.push(
|
||||
update
|
||||
.split(",")
|
||||
.map(|page| page.parse())
|
||||
.collect::<Result<Vec<i64>, Self::Err>>()?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Self { rules, updates })
|
||||
}
|
||||
}
|
||||
|
||||
impl ManualUpdate {
|
||||
fn part1(&self) -> i64 {
|
||||
self.updates
|
||||
.iter()
|
||||
.filter(|update| self.is_update_valid(update))
|
||||
.map(Self::middle_page_number)
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn part2(&self) -> i64 {
|
||||
self.updates
|
||||
.iter()
|
||||
.filter(|update| !self.is_update_valid(update))
|
||||
.map(|update| self.sort_update(update))
|
||||
.map(|sorted_update| Self::middle_page_number(&sorted_update))
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn middle_page_number(update: &Vec<i64>) -> i64 {
|
||||
update[update.len() / 2]
|
||||
}
|
||||
|
||||
fn is_update_valid(&self, update: &Vec<i64>) -> bool {
|
||||
// work through pages in an update backwards
|
||||
for (i, page) in update.iter().rev().enumerate() {
|
||||
// so we can loop through each previous page
|
||||
for previous_page in update[0..update.len() - i].iter().rev() {
|
||||
if let Some(failure_pages) = self.rules.get(page) {
|
||||
if failure_pages.contains(previous_page) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn sort_update(&self, update: &Vec<i64>) -> Vec<i64> {
|
||||
let mut sorted_update = update.clone();
|
||||
sorted_update.sort_by(|a, b| {
|
||||
if let Some(after_pages) = self.rules.get(a) {
|
||||
if after_pages.contains(b) {
|
||||
return Ordering::Less;
|
||||
}
|
||||
}
|
||||
return Ordering::Greater;
|
||||
});
|
||||
sorted_update
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let manual_update: ManualUpdate = "47|53
|
||||
97|13
|
||||
97|61
|
||||
97|47
|
||||
75|29
|
||||
61|13
|
||||
75|53
|
||||
29|13
|
||||
97|29
|
||||
53|29
|
||||
61|53
|
||||
97|53
|
||||
61|29
|
||||
47|13
|
||||
75|47
|
||||
97|75
|
||||
47|61
|
||||
75|61
|
||||
47|29
|
||||
75|13
|
||||
53|13
|
||||
|
||||
75,47,61,53,29
|
||||
97,61,53,29,13
|
||||
75,29,13
|
||||
75,97,47,61,53
|
||||
61,13,29
|
||||
97,13,75,29,47"
|
||||
.parse()
|
||||
.unwrap();
|
||||
assert_eq!(manual_update.part1(), 143);
|
||||
assert_eq!(manual_update.part2(), 123);
|
||||
}
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
mod prelude;
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(6);
|
||||
let mut lab: Lab = input.parse().unwrap();
|
||||
let mut lab2: Lab = input.parse().unwrap();
|
||||
show_answers(
|
||||
lab.num_steps_until_guard_leaves_lab(),
|
||||
lab2.num_positions_of_new_obstructions_that_cause_guard_loop(),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone)]
|
||||
struct Vec2 {
|
||||
x: i64,
|
||||
y: i64,
|
||||
}
|
||||
|
||||
impl Vec2 {
|
||||
fn new(x: i64, y: i64) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
fn add(&self, v: &Vec2) -> Self {
|
||||
Self::new(self.x + v.x, self.y + v.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Vec2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Vec2({}, {})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Obstruction {
|
||||
pos: Vec2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
struct Guard {
|
||||
pos: Vec2,
|
||||
vel: Vec2,
|
||||
}
|
||||
|
||||
impl Guard {
|
||||
fn turn(&mut self) {
|
||||
// too lazy for trig today I guess
|
||||
if self.vel.x > 0 && self.vel.y > 0 {
|
||||
self.vel.x *= -1;
|
||||
} else if self.vel.x < 0 && self.vel.y > 0 {
|
||||
self.vel.y *= -1;
|
||||
} else if self.vel.x < 0 && self.vel.y < 0 {
|
||||
self.vel.x *= -1;
|
||||
} else if self.vel.x > 0 && self.vel.y < 0 {
|
||||
self.vel.y *= -1;
|
||||
} else if self.vel.y != 0 {
|
||||
self.vel.x = self.vel.y * -1;
|
||||
self.vel.y = 0;
|
||||
} else if self.vel.x != 0 {
|
||||
self.vel.y = self.vel.x;
|
||||
self.vel.x = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Lab {
|
||||
obstructions: Vec<Obstruction>,
|
||||
guard: Guard,
|
||||
size: Vec2,
|
||||
}
|
||||
|
||||
impl FromStr for Lab {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut size = Vec2::default();
|
||||
let mut obstructions = vec![];
|
||||
let mut guard: Option<Guard> = None;
|
||||
for (y, line) in s.lines().enumerate() {
|
||||
size.y += 1;
|
||||
size.x = line.len() as i64; // whatever - still linear ;)
|
||||
for (x, c) in line.bytes().enumerate() {
|
||||
match c {
|
||||
b'#' => obstructions.push(Obstruction {
|
||||
pos: Vec2::new(x as i64, y as i64),
|
||||
}),
|
||||
b'^' => {
|
||||
guard = Some(Guard {
|
||||
pos: Vec2::new(x as i64, y as i64),
|
||||
vel: Vec2::new(0, -1),
|
||||
})
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Lab {
|
||||
size,
|
||||
guard: guard.unwrap(),
|
||||
obstructions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Lab {
|
||||
fn step(&mut self) {
|
||||
// move guard
|
||||
let next_pos = self.guard.pos.add(&self.guard.vel);
|
||||
self.guard.pos = next_pos;
|
||||
|
||||
// after moving, check if we need to turn
|
||||
// TODO: obstructions should lookup by position instead of this
|
||||
// broad-phase "check each one each time" slow business
|
||||
while self.has_obstacle_at(&self.guard.pos.add(&self.guard.vel)) {
|
||||
self.guard.turn();
|
||||
}
|
||||
}
|
||||
|
||||
fn contains(&self, pos: &Vec2) -> bool {
|
||||
(0..self.size.x).contains(&pos.x) && (0..self.size.y).contains(&pos.y)
|
||||
}
|
||||
|
||||
fn num_steps_until_guard_leaves_lab(&mut self) -> usize {
|
||||
let mut visited = HashSet::new();
|
||||
visited.insert(self.guard.pos.clone());
|
||||
while self.contains(&self.guard.pos) {
|
||||
self.step();
|
||||
visited.insert(self.guard.pos.clone());
|
||||
}
|
||||
visited.len() - 1
|
||||
}
|
||||
|
||||
fn has_obstacle_at(&self, pos: &Vec2) -> bool {
|
||||
self.obstructions.iter().any(|o| o.pos == *pos)
|
||||
}
|
||||
|
||||
fn num_positions_of_new_obstructions_that_cause_guard_loop(&mut self) -> usize {
|
||||
let mut positions_with_cycles_detected = 0;
|
||||
let mut visited = HashSet::new();
|
||||
visited.insert(self.guard.clone());
|
||||
// TODO: avoid super brute force
|
||||
for x in 0..self.size.x {
|
||||
for y in 0..self.size.y {
|
||||
println!("Simulating at {x} {y}...");
|
||||
let mut alternate_visited = visited.clone();
|
||||
let mut alternate_lab = self.clone();
|
||||
alternate_lab.obstructions.push(Obstruction {
|
||||
pos: Vec2::new(x, y),
|
||||
});
|
||||
while alternate_lab.contains(&alternate_lab.guard.pos) {
|
||||
alternate_lab.step();
|
||||
if alternate_visited.contains(&alternate_lab.guard) {
|
||||
positions_with_cycles_detected += 1;
|
||||
break;
|
||||
}
|
||||
alternate_visited.insert(alternate_lab.guard.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
positions_with_cycles_detected
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = "
|
||||
....#.....
|
||||
.........#
|
||||
..........
|
||||
..#.......
|
||||
.......#..
|
||||
..........
|
||||
.#..^.....
|
||||
........#.
|
||||
#.........
|
||||
......#..."
|
||||
.trim();
|
||||
let mut lab: Lab = input.parse().unwrap();
|
||||
let mut lab2 = lab.clone();
|
||||
assert_eq!(lab.num_steps_until_guard_leaves_lab(), 41);
|
||||
assert_eq!(
|
||||
lab2.num_positions_of_new_obstructions_that_cause_guard_loop(),
|
||||
6
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
mod prelude;
|
||||
use std::slice::Windows;
|
||||
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(7);
|
||||
show_answers(
|
||||
sum_possibly_valid_tests(&input, &add_and_mul),
|
||||
sum_possibly_valid_tests(&input, &add_and_mul_and_concat),
|
||||
);
|
||||
}
|
||||
|
||||
fn sum_possibly_valid_tests(input: &str, f: &dyn Fn(Vec<usize>, &usize) -> Vec<usize>) -> usize {
|
||||
let mut sum = 0;
|
||||
for l in input.lines() {
|
||||
let (target, operands) = l.split_once(':').unwrap();
|
||||
let target: usize = target.parse().unwrap();
|
||||
let operands: Vec<usize> = operands
|
||||
.trim()
|
||||
.split(" ")
|
||||
.map(|s| s.parse().unwrap())
|
||||
.collect();
|
||||
if operands
|
||||
.iter()
|
||||
.skip(1)
|
||||
.fold(vec![operands[0]], f)
|
||||
.iter()
|
||||
.any(|n| *n == target)
|
||||
{
|
||||
sum += target;
|
||||
}
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
fn add_and_mul(lefts: Vec<usize>, right: &usize) -> Vec<usize> {
|
||||
let mut result = vec![];
|
||||
for left in lefts {
|
||||
result.push(left + right);
|
||||
result.push(left * right);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn add_and_mul_and_concat(lefts: Vec<usize>, right: &usize) -> Vec<usize> {
|
||||
let mut result = vec![];
|
||||
for left in lefts {
|
||||
result.push(left + right);
|
||||
result.push(left * right);
|
||||
result.push(format!("{}{}", left, right).parse().unwrap());
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
assert_eq!(
|
||||
sum_possibly_valid_tests(
|
||||
"190: 10 19
|
||||
3267: 81 40 27
|
||||
83: 17 5
|
||||
156: 15 6
|
||||
7290: 6 8 6 15
|
||||
161011: 16 10 13
|
||||
192: 17 8 14
|
||||
21037: 9 7 18 13
|
||||
292: 11 6 16 20",
|
||||
&add_and_mul
|
||||
),
|
||||
3749
|
||||
);
|
||||
assert_eq!(
|
||||
sum_possibly_valid_tests(
|
||||
"190: 10 19
|
||||
3267: 81 40 27
|
||||
83: 17 5
|
||||
156: 15 6
|
||||
7290: 6 8 6 15
|
||||
161011: 16 10 13
|
||||
192: 17 8 14
|
||||
21037: 9 7 18 13
|
||||
292: 11 6 16 20",
|
||||
&add_and_mul_and_concat
|
||||
),
|
||||
11387
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
mod prelude;
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(8);
|
||||
show_answers(part1(&input), part2(&input));
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> usize {
|
||||
let mut map: HashMap<u8, HashSet<(i64, i64)>> = HashMap::new();
|
||||
let mut antinodes: HashSet<(i64, i64)> = HashSet::new();
|
||||
let (width, height) = (
|
||||
input.lines().count() as i64,
|
||||
input.lines().next().unwrap().len() as i64,
|
||||
);
|
||||
for (y, line) in input.lines().enumerate() {
|
||||
for (x, c) in line.bytes().enumerate() {
|
||||
if c == b'.' {
|
||||
continue;
|
||||
}
|
||||
map.entry(c)
|
||||
.or_insert_with(|| HashSet::new())
|
||||
.insert((x as i64, y as i64));
|
||||
}
|
||||
}
|
||||
for nodes in map.values() {
|
||||
println!("{nodes:?}");
|
||||
for (i, (x1, y1)) in nodes.iter().enumerate() {
|
||||
for (j, (x2, y2)) in nodes.iter().enumerate() {
|
||||
if i == j {
|
||||
continue;
|
||||
}
|
||||
let (dx, dy) = (x1 - x2, y1 - y2);
|
||||
let (a1x, a1y) = (x1 + dx, y1 + dy);
|
||||
let (a2x, a2y) = (x2 - dx, y2 - dy);
|
||||
if a1x >= 0 && a1x < width && a1y >= 0 && a1y < height {
|
||||
antinodes.insert((a1x, a1y));
|
||||
}
|
||||
if a2x >= 0 && a2x < width && a2y >= 0 && a2y < height {
|
||||
antinodes.insert((a2x, a2y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
antinodes.len()
|
||||
}
|
||||
|
||||
fn part2(input: &str) -> usize {
|
||||
let mut map: HashMap<u8, HashSet<(i64, i64)>> = HashMap::new();
|
||||
let mut antinodes: HashSet<(i64, i64)> = HashSet::new();
|
||||
let (width, height) = (
|
||||
input.trim().lines().count() as i64,
|
||||
input.lines().next().unwrap().trim().len() as i64,
|
||||
);
|
||||
for (y, line) in input.lines().enumerate() {
|
||||
for (x, c) in line.bytes().enumerate() {
|
||||
if c == b'.' {
|
||||
continue;
|
||||
}
|
||||
map.entry(c)
|
||||
.or_insert_with(|| HashSet::new())
|
||||
.insert((x as i64, y as i64));
|
||||
}
|
||||
}
|
||||
for nodes in map.values() {
|
||||
for (i, (x1, y1)) in nodes.iter().enumerate() {
|
||||
for (j, (x2, y2)) in nodes.iter().enumerate() {
|
||||
antinodes.insert((*x1, *y1));
|
||||
antinodes.insert((*x2, *y2));
|
||||
if i == j {
|
||||
continue;
|
||||
}
|
||||
let (dx, dy) = (x1 - x2, y1 - y2);
|
||||
let (mut a1x, mut a1y) = (x1 + dx, y1 + dy);
|
||||
while a1x >= 0 && a1x < width && a1y >= 0 && a1y < height {
|
||||
antinodes.insert((a1x, a1y));
|
||||
println!("{a1x} {a1y} {}", antinodes.len());
|
||||
a1x += dx;
|
||||
a1y += dy;
|
||||
}
|
||||
let (mut a2x, mut a2y) = (x2 - dx, y2 - dy);
|
||||
while a2x >= 0 && a2x < width && a2y >= 0 && a2y < height {
|
||||
antinodes.insert((a2x, a2y));
|
||||
println!("{a2x} {a2y} {}", antinodes.len());
|
||||
a2x -= dx;
|
||||
a2y -= dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
antinodes.len()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = "............
|
||||
........0...
|
||||
.....0......
|
||||
.......0....
|
||||
....0.......
|
||||
......A.....
|
||||
............
|
||||
............
|
||||
........A...
|
||||
.........A..
|
||||
............
|
||||
............";
|
||||
assert_eq!(part1(input), 14);
|
||||
assert_eq!(part2(input), 34);
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
mod prelude;
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(9);
|
||||
show_answers(part1(&input), part2(&input));
|
||||
}
|
||||
|
||||
struct Disk {
|
||||
blocks: Vec<Option<usize>>,
|
||||
files: Vec<(usize, u8)>,
|
||||
free_blocks: usize,
|
||||
}
|
||||
|
||||
impl FromStr for Disk {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut blocks: Vec<Option<usize>> = vec![];
|
||||
let mut files = vec![];
|
||||
let mut free_blocks: usize = 0;
|
||||
for (i, len) in s.trim().bytes().map(|len| len - b'0').enumerate() {
|
||||
// every other iteration is "free space"
|
||||
let file_id = if i % 2 == 0 {
|
||||
Some((i / 2) as usize)
|
||||
} else {
|
||||
free_blocks += len as usize;
|
||||
None
|
||||
};
|
||||
if file_id.is_some() {
|
||||
files.push((blocks.len(), len));
|
||||
}
|
||||
for _ in 0..len {
|
||||
blocks.push(file_id);
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
files,
|
||||
blocks,
|
||||
free_blocks,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Disk {
|
||||
fn checksum(&self) -> usize {
|
||||
self.blocks
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, b)| i * b.unwrap_or_default())
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> usize {
|
||||
let mut disk: Disk = input.parse().unwrap();
|
||||
let mut right = disk.blocks.len() - 1;
|
||||
for left in 0..(right.clone() - disk.free_blocks + 1) {
|
||||
if disk.blocks[left].is_some() {
|
||||
continue;
|
||||
}
|
||||
while disk.blocks[right].is_none() {
|
||||
right -= 1;
|
||||
}
|
||||
(disk.blocks[left], disk.blocks[right]) = (disk.blocks[right], disk.blocks[left]);
|
||||
}
|
||||
|
||||
disk.checksum()
|
||||
}
|
||||
|
||||
fn part2(input: &str) -> usize {
|
||||
let mut disk: Disk = input.parse().unwrap();
|
||||
for (cur_i, len) in disk.files.iter().rev() {
|
||||
for left in 0..*cur_i {
|
||||
if disk.blocks[left..(left + (*len as usize))]
|
||||
.iter()
|
||||
.all(|f| f.is_none())
|
||||
{
|
||||
for i in 0..*len {
|
||||
let i = i as usize;
|
||||
(disk.blocks[left + i], disk.blocks[cur_i + i]) =
|
||||
(disk.blocks[cur_i + i], disk.blocks[left + i])
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
disk.checksum()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = r#"2333133121414131402"#;
|
||||
assert_eq!(part1(input), 1928);
|
||||
assert_eq!(part2(input), 2858);
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
pub use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
collections::HashSet,
|
||||
convert::Infallible,
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
io::Read,
|
||||
iter::zip,
|
||||
num::{ParseFloatError, ParseIntError},
|
||||
str::FromStr,
|
||||
sync::{Arc, LazyLock, Mutex},
|
||||
};
|
||||
|
||||
pub type AnyResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
pub type EmptyResult = AnyResult<()>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InputFileError {
|
||||
#[allow(dead_code)]
|
||||
EnvVar(std::env::VarError),
|
||||
|
||||
#[allow(dead_code)]
|
||||
Io(std::io::Error),
|
||||
}
|
||||
|
||||
/// Ensures that the input file exists
|
||||
fn ensure_input_file(day: u8) -> Result<PathBuf, InputFileError> {
|
||||
let path = Path::new(&std::env::var("HOME").map_err(InputFileError::EnvVar)?)
|
||||
.join(format!("./.cache/aoc2024/{0}.input", day));
|
||||
if !path.exists() {
|
||||
eprintln!("Running input downloaded script with day arg {}...", day);
|
||||
std::process::Command::new("sh")
|
||||
.args(["../fetch-input.sh", &day.to_string()])
|
||||
.status()
|
||||
.map_err(InputFileError::Io)?;
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Useful when you simply want the day's input as a String.
|
||||
pub fn day_input(day: u8) -> String {
|
||||
let mut buffer = String::new();
|
||||
day_input_file(day)
|
||||
.read_to_string(&mut buffer)
|
||||
.expect(&format!("invalid utf8 input for day {}", day));
|
||||
buffer
|
||||
}
|
||||
|
||||
/// Useful when you want the day's input for streaming for maximum performance nonsense
|
||||
pub fn day_input_file(day: u8) -> File {
|
||||
let f = ensure_input_file(day).expect(&format!("Failed to ensure input for day {}", day));
|
||||
File::open(&f).expect(format!("Failed to open file {}", f.display()).as_str())
|
||||
}
|
||||
|
||||
pub fn show_answers<T: std::fmt::Display>(part1: T, part2: T) {
|
||||
println!("Part 1: {}", part1);
|
||||
println!("Part 2: {}", part2);
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
mod prelude;
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(DAY);
|
||||
show_answers(part1(&input), part2(&input));
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn part2(input: &str) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = r#""#;
|
||||
assert_eq!(part1(input), 1);
|
||||
assert_eq!(part2(input), 0);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
This repository is meant to contain all my [Advent of Code][aoc] code throughout
|
||||
the years I've participated (to whatever extent).
|
||||
|
||||
- [2024](./2024)
|
||||
- [2023](./2023)
|
||||
- [2022](./2022)
|
||||
- [2021](./2021)
|
||||
|
|
Loading…
Reference in a new issue