diff options
author | Aria <me@aria.rip> | 2023-01-02 21:58:56 +0000 |
---|---|---|
committer | Aria <me@aria.rip> | 2023-01-02 21:58:56 +0000 |
commit | 5eb58ad076f2cd435b11b140820da224b60b73d5 (patch) | |
tree | 2a67939595fbf993ff04f69b9cd3f0aa20827d96 /2022/src |
initial commit
Diffstat (limited to '2022/src')
-rw-r--r-- | 2022/src/day01.rs | 21 | ||||
-rw-r--r-- | 2022/src/day02.rs | 105 | ||||
-rw-r--r-- | 2022/src/day03.rs | 62 | ||||
-rw-r--r-- | 2022/src/day04.rs | 35 | ||||
-rw-r--r-- | 2022/src/day05.rs | 145 | ||||
-rw-r--r-- | 2022/src/day06.rs | 47 | ||||
-rw-r--r-- | 2022/src/day07.rs | 110 | ||||
-rw-r--r-- | 2022/src/day08.rs | 110 | ||||
-rw-r--r-- | 2022/src/day09.html | 38 | ||||
-rw-r--r-- | 2022/src/day09.rs | 238 | ||||
-rw-r--r-- | 2022/src/day10.rs | 82 | ||||
-rw-r--r-- | 2022/src/day11.rs | 127 | ||||
-rw-r--r-- | 2022/src/day12.rs | 141 | ||||
-rw-r--r-- | 2022/src/day13.rs | 132 | ||||
-rw-r--r-- | 2022/src/day14.html | 38 | ||||
-rw-r--r-- | 2022/src/day14.rs | 230 | ||||
-rw-r--r-- | 2022/src/utils.rs | 59 |
17 files changed, 1720 insertions, 0 deletions
diff --git a/2022/src/day01.rs b/2022/src/day01.rs new file mode 100644 index 0000000..a328975 --- /dev/null +++ b/2022/src/day01.rs @@ -0,0 +1,21 @@ +use crate::utils::max_n; + +mod utils; + +fn main() { + let input = utils::read_input(); + + let mut elf_cal_counts: Vec<usize> = input + .split("\n\n") + .map(|xs| { + xs.lines() + .map(|x| x.parse::<usize>().unwrap()) + .sum::<usize>() + }) + .collect(); + + let top_3 = max_n(&mut elf_cal_counts, 3); + + println!("Max: {}", top_3.last().unwrap()); + println!("Sum of top 3: {}", top_3.iter().sum::<usize>()); +} diff --git a/2022/src/day02.rs b/2022/src/day02.rs new file mode 100644 index 0000000..d0b0c5f --- /dev/null +++ b/2022/src/day02.rs @@ -0,0 +1,105 @@ +mod utils; + +use utils::read_input; + +fn main() { + let input = read_input(); + + let turns_both_moves: Vec<_> = input + .lines() + .map(|xs| { + let mut parts = xs.split(' ').take(2).map(Move::parse); + (parts.next().unwrap(), parts.next().unwrap()) + }) + .collect(); + + let total_score_p1: usize = turns_both_moves + .iter() + .map(|(them, us)| score(*them, *us)) + .sum(); + + let total_score_p2: usize = turns_both_moves + .into_iter() + .map(|(them, us)| (them, us.to_outcome())) + .map(|(them, outcome)| (them, them.to_get(outcome))) + .map(|(them, us)| score(them, us)) + .sum(); + + println!("Part 1: {}", total_score_p1); + println!("Part 2: {}", total_score_p2); +} + +fn score(them: Move, us: Move) -> usize { + let won_val = match (them.beats(us), us.beats(them)) { + (true, false) => 0, + (false, true) => 6, + (false, false) => 3, + (a, b) => unreachable!("they beat us: {}, we beat them: {}", a, b), + }; + us.value() + won_val +} + +#[derive(Debug, Clone, Copy)] +enum Move { + Rock, + Paper, + Scissors, +} + +impl Move { + fn parse(inp: &str) -> Self { + match inp { + "A" => Move::Rock, + "B" => Move::Paper, + "C" => Move::Scissors, + "X" => Move::Rock, + "Y" => Move::Paper, + "Z" => Move::Scissors, + _ => panic!("unrecognised move: {}", inp), + } + } + + fn to_outcome(self) -> Outcome { + match self { + Move::Rock => Outcome::Lose, + Move::Paper => Outcome::Draw, + Move::Scissors => Outcome::Win, + } + } + + fn beats(self, other: Move) -> bool { + matches!( + (self, other), + (Move::Paper, Move::Rock) + | (Move::Rock, Move::Scissors) + | (Move::Scissors, Move::Paper) + ) + } + + fn to_get(self, outcome: Outcome) -> Self { + match (self, outcome) { + (_, Outcome::Draw) => self, + (Move::Rock, Outcome::Win) => Move::Paper, + (Move::Paper, Outcome::Win) => Move::Scissors, + (Move::Scissors, Outcome::Win) => Move::Rock, + (Move::Rock, Outcome::Lose) => Move::Scissors, + (Move::Paper, Outcome::Lose) => Move::Rock, + (Move::Scissors, Outcome::Lose) => Move::Paper, + } + } + + fn value(self) -> usize { + match self { + Move::Rock => 1, + Move::Paper => 2, + Move::Scissors => 3, + } + } +} + +#[derive(Debug, Clone, Copy)] +enum Outcome { + Win, + Draw, + Lose, +} diff --git a/2022/src/day03.rs b/2022/src/day03.rs new file mode 100644 index 0000000..7e245de --- /dev/null +++ b/2022/src/day03.rs @@ -0,0 +1,62 @@ +mod utils; + +use std::collections::HashSet; + +use utils::read_input; + +fn main() { + let input = read_input(); + + // Convert each line to a tuple of hashsets, one for each backpack pocket + let rucksack_items: Vec<_> = input + .lines() + .map(|line| (&line[..line.len() / 2], &line[line.len() / 2..])) + .map(|(a, b)| -> (HashSet<char>, HashSet<char>) { + (a.chars().collect(), b.chars().collect()) + }) + .collect(); + + let sum_error_priorities: u32 = rucksack_items + .iter() + .map(|(a, b)| { + *a.intersection(b) + .next() + .expect("no common item between two backpack segments!") + }) // Get common item + .map(priority) + .sum(); + + let sum_badge_priorities: u32 = rucksack_items // Construct chunked iterator + .iter() + .step_by(3) + .zip(rucksack_items.iter().skip(1).step_by(3)) + .zip(rucksack_items.iter().skip(2).step_by(3)) + .map(|(((e1l, e1r), (e2l, e2r)), (e3l, e3r))| { + for item in e1l.union(e1r) { + if (e2l.contains(item) || e2r.contains(item)) + && (e3l.contains(item) || e3r.contains(item)) + { + return *item; + } + } + panic!( + "group has no badge! {:?}", + ((e1l, e1r), (e2l, e2r), (e3l, e3r)) + ) + }) + .map(priority) + .sum(); + + println!("Part 1: {}", sum_error_priorities); + println!("Part 2: {}", sum_badge_priorities); +} + +fn priority(chr: char) -> u32 { + if chr.is_ascii_lowercase() { + chr as u32 - 96 + } else if chr.is_ascii_uppercase() { + chr as u32 - 38 + } else { + panic!("unrecognised item {}", chr) + } +} diff --git a/2022/src/day04.rs b/2022/src/day04.rs new file mode 100644 index 0000000..052dde1 --- /dev/null +++ b/2022/src/day04.rs @@ -0,0 +1,35 @@ +mod utils; + +use std::ops::RangeInclusive; + +use utils::{iter_pair, read_input}; + +fn main() { + let input = read_input(); + + let pairs = input.lines().map(|x| { + iter_pair(x.split(',').map(|x| -> RangeInclusive<usize> { + let (start, end) = iter_pair(x.split('-')); + start.parse().unwrap()..=end.parse().unwrap() + })) + }); + + let (fully_contains, partially_contains) = pairs + .map(|(a, b)| (fully_overlap(&a, &b), partially_overlap(&a, &b))) + .fold((0, 0), |(acc_full, acc_part), (full, part)| { + (acc_full + full as usize, acc_part + part as usize) + }); + + println!("Part 1: {}", fully_contains); + println!("Part 2: {}", partially_contains); +} + +#[inline(always)] +fn fully_overlap<T: PartialOrd>(a: &RangeInclusive<T>, b: &RangeInclusive<T>) -> bool { + (a.contains(b.start()) && a.contains(b.end())) || (b.contains(a.start()) && b.contains(a.end())) +} + +#[inline(always)] +fn partially_overlap<T: PartialOrd>(a: &RangeInclusive<T>, b: &RangeInclusive<T>) -> bool { + a.contains(b.start()) || a.contains(b.end()) || b.contains(a.start()) || b.contains(a.end()) +} diff --git a/2022/src/day05.rs b/2022/src/day05.rs new file mode 100644 index 0000000..f45cd0a --- /dev/null +++ b/2022/src/day05.rs @@ -0,0 +1,145 @@ +//! Day 5: Supply Stacks + +extern crate bumpalo; + +mod utils; + +use bumpalo::Bump; +use std::cell::Cell; +use utils::{parse_num, parse_static, read_input}; + +/// Base type for a crate +#[derive(Debug, Clone)] +struct Crate<'a> { + letter: char, + below: Cell<Option<&'a Crate<'a>>>, +} + +impl Default for Crate<'_> { + fn default() -> Self { + Crate { + letter: ' ', + below: None.into(), + } + } +} + +/// A move the crane may make +#[derive(Debug, Clone, Copy)] +struct Move { + reps: usize, + from: usize, + to: usize, +} +type Tops<'arena> = Vec<Option<&'arena Crate<'arena>>>; + +fn main() { + let input = read_input(); + + { + let arena = Bump::new(); + let mut tops = construct_piles(&input, &arena); + for mv in parse_moves(&input) { + perform_move_9000(&mut tops, mv); + } + + println!("Part 1: {}", top_letters(tops)); + } + + { + let arena = Bump::new(); + let mut tops = construct_piles(&input, &arena); + for mv in parse_moves(&input) { + perform_move_9001(&mut tops, mv); + } + + println!("Part 2: {}", top_letters(tops)); + } +} + +/// Get the message / the top letters of each stack +fn top_letters(tops: Tops<'_>) -> String { + tops.iter().map(|x| x.as_ref().unwrap().letter).collect() +} + +/// Perform a move for part 1 +fn perform_move_9000(tops: &mut Tops<'_>, mv: Move) { + for _ in 0..mv.reps { + let from = tops[mv.from].take().unwrap(); + let to = tops[mv.to].take(); + + tops[mv.from] = from.below.replace(to); + tops[mv.to] = Some(from); + } +} + +/// Perform a move for part 2 +fn perform_move_9001(tops: &mut Tops<'_>, mv: Move) { + let pickup_top = tops[mv.from].take().unwrap(); + + let mut pickup_bot = pickup_top; + for _ in 1..mv.reps { + pickup_bot = pickup_bot.below.get().as_ref().unwrap(); + } + + let to = tops[mv.to].take(); + + tops[mv.from] = pickup_bot.below.replace(to); + tops[mv.to] = Some(pickup_top); +} + +// Annoying parsing code! + +fn construct_piles<'a>(input: &str, arena: &'a Bump) -> Tops<'a> { + let mut piles = Vec::new(); + for layer_iter in input + .lines() + .take_while(|x| x.chars().find(|x| *x != ' ').unwrap() == '[') + .map(|x| x.chars().skip(1).step_by(4)) + { + for (stack, chr) in layer_iter.enumerate() { + if piles.len() < stack + 1 { + piles.resize(stack + 1, None); + } + + if chr != ' ' { + let val = &*arena.alloc(Crate { + letter: chr, + below: None.into(), + }); + if piles[stack].is_none() { + piles[stack] = Some(val); + } else { + let mut top = *piles[stack].as_ref().unwrap(); + while let Some(below) = top.below.get() { + top = below; + } + + top.below.set(Some(val)); + } + } + } + } + + piles +} + +fn parse_moves(input: &str) -> impl Iterator<Item = Move> + '_ { + input + .lines() + .skip_while(|line| !line.starts_with('m')) + .map(|line| { + let line = parse_static("move ", line); + let (reps, line) = parse_num(line); + let line = parse_static(" from ", line); + let (from, line) = parse_num::<usize>(line); + let line = parse_static(" to ", line); + let (to, _) = parse_num::<usize>(line); + + Move { + reps, + from: from - 1, + to: to - 1, + } + }) +} diff --git a/2022/src/day06.rs b/2022/src/day06.rs new file mode 100644 index 0000000..8bbfe6d --- /dev/null +++ b/2022/src/day06.rs @@ -0,0 +1,47 @@ +mod utils; +use utils::read_input; + +fn main() { + let input = read_input(); + + println!("Part 1: {}", find_chunk_nodup::<4>(&input)); + println!("Part 2: {}", find_chunk_nodup::<14>(&input)); +} + +fn find_chunk_nodup<const N: usize>(input: &str) -> usize { + let mut window = ['a'; N]; + let mut chars = input.chars(); + + // Fill up initial window + for item in window.iter_mut() { + *item = chars.next().unwrap(); + } + + for i in 0..input.len() { + // Shuffle everything in window down + for j in 0..N - 1 { + window[j] = window[j + 1]; + } + window[N - 1] = chars.next().unwrap(); + + if window + .iter() + .fold(Some(0), |acc, chr| add_char(acc?, *chr)) + .is_some() + { + return i + N + 1; + } + } + + panic!("no solution!") +} + +#[inline] +fn add_char(bits: u32, chr: char) -> Option<u32> { + let add = 1 << (chr.to_ascii_lowercase() as u32 - 97); + if (bits | add) != bits { + Some(bits | add) + } else { + None + } +} diff --git a/2022/src/day07.rs b/2022/src/day07.rs new file mode 100644 index 0000000..88bb647 --- /dev/null +++ b/2022/src/day07.rs @@ -0,0 +1,110 @@ +mod utils; +use utils::read_input; + +fn main() { + let input = read_input(); + + let mut unused = 0; + let mut sum_under_threshold = 0; + + for size in DirectorySizes::new(&input) { + if size < 100_000 { + sum_under_threshold += size; + } + unused = 70_000_000 - size; + } + + println!("Part 1: {}", sum_under_threshold); + let deficit = 30_000_000 - unused; + println!( + "Part 2: {}", + DirectorySizes::new(&input) + .filter(|x| *x > deficit) + .min() + .unwrap() + ); +} + +struct DirectorySizes<'a> { + lines: std::str::Lines<'a>, + directory_stack: [usize; 64], + stack_top: usize, +} + +impl<'a> DirectorySizes<'a> { + fn new(input: &'a str) -> Self { + let mut lines = input.lines(); + assert_eq!(lines.next(), Some("$ cd /")); + + DirectorySizes { + lines, + directory_stack: [0; 64], + stack_top: 0, + } + } +} + +impl Iterator for DirectorySizes<'_> { + type Item = usize; + + fn next(&mut self) -> Option<Self::Item> { + for line in self.lines.by_ref() { + let line: Line = line.into(); + + match line { + Line::Cd(up) => { + if up { + self.directory_stack[self.stack_top - 1] += + self.directory_stack[self.stack_top]; + self.stack_top -= 1; + return Some(self.directory_stack[self.stack_top + 1]); + } else { + self.stack_top += 1; + self.directory_stack[self.stack_top] = 0; + } + } + Line::FileSize(size) => { + self.directory_stack[self.stack_top] += size; + } + Line::Ls => (), + Line::Dir => (), + }; + } + + if self.stack_top > 0 { + self.directory_stack[self.stack_top - 1] += self.directory_stack[self.stack_top]; + self.stack_top -= 1; + Some(self.directory_stack[self.stack_top + 1]) + } else if self.directory_stack[0] != 0 { + Some(std::mem::replace(&mut self.directory_stack[0], 0)) + } else { + None + } + } +} + +#[derive(Debug, Clone, Copy)] +enum Line { + /// True = .., False = other + Cd(bool), + Ls, + FileSize(usize), + Dir, +} + +impl From<&str> for Line { + fn from(value: &str) -> Self { + if value.starts_with("$ cd ") { + let mut args = value.split("cd "); + args.next().unwrap(); + Line::Cd(args.next().unwrap() == "..") + } else if value.starts_with("$ ls") { + Line::Ls + } else if value.starts_with("dir") { + Line::Dir + } else { + let num_index = value.chars().take_while(|chr| chr.is_numeric()).count(); + Line::FileSize(value[..num_index].parse().unwrap()) + } + } +} diff --git a/2022/src/day08.rs b/2022/src/day08.rs new file mode 100644 index 0000000..514cb21 --- /dev/null +++ b/2022/src/day08.rs @@ -0,0 +1,110 @@ +mod utils; + +use utils::read_input; + +fn main() { + let input = read_input(); + + let mut grid: Vec<Vec<_>> = input + .lines() + .map(|x| { + x.chars() + .map(|x| (x.to_digit(10).unwrap() as u8, false)) + .collect() + }) + .collect(); + in_all_directions!(check_vis_along_run, grid); + + println!( + "Part 1: {}", + grid.iter() + .map(|x| x.iter().filter(|x| x.1).count()) + .sum::<usize>() + ); + + let mut grid: Vec<Vec<(u8, u32)>> = grid + .into_iter() + .map(|x| x.into_iter().map(|(x, _)| (x, 1)).collect()) + .collect(); + in_all_directions!(vis_score_along_run, grid); + + println!( + "Part 2: {}", + grid.iter() + .map(|row| row.iter().map(|(_, vis)| vis).max().unwrap()) + .max() + .unwrap() + ); +} + +fn check_vis_along_run( + mut run: impl Iterator<Item = (usize, usize)>, + grid: &mut [Vec<(u8, bool)>], +) { + let mut curr_height = { + let (row_idx, col_idx) = run.next().unwrap(); + + grid[row_idx][col_idx].1 = true; + + grid[row_idx][col_idx].0 + }; + for (row_idx, col_idx) in run { + let height = grid[row_idx][col_idx].0; + if height > curr_height { + curr_height = height; + grid[row_idx][col_idx].1 = true; + } + } +} + +fn vis_score_along_run(run: impl Iterator<Item = (usize, usize)>, grid: &mut [Vec<(u8, u32)>]) { + let mut next_values = [0; 10]; + for (row_idx, col_idx) in run { + let height = grid[row_idx][col_idx].0; + grid[row_idx][col_idx].1 *= next_values[height as usize]; + + for (val, score) in next_values.iter_mut().enumerate() { + *score = if *score == 0 || val > height as usize { + *score + 1 + } else { + 1 + }; + } + } +} + +#[macro_use] +mod codegen { + #[macro_export] + macro_rules! in_all_directions { + ($f:ident, $grid: ident) => { + let grid_rows = $grid.len(); + let grid_cols = $grid[0].len(); + + (0..grid_rows).for_each(|grid_row| { + $f( + (0..grid_cols).map(move |grid_col| (grid_row, grid_col)), + &mut $grid, + ); + $f( + (0..grid_cols) + .rev() + .map(move |grid_col| (grid_row, grid_col)), + &mut $grid, + ) + }); + (0..grid_cols).for_each(|grid_col| { + $f( + (0..grid_rows).map(move |grid_row| (grid_row, grid_col)), + &mut $grid, + ); + $f( + (0..grid_rows) + .rev() + .map(move |grid_row| (grid_row, grid_col)), + &mut $grid, + ) + }); + }; + } +} diff --git a/2022/src/day09.html b/2022/src/day09.html new file mode 100644 index 0000000..d3ed12b --- /dev/null +++ b/2022/src/day09.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> + +<head> + <link data-trunk rel="rust" data-wasm-opt="2" href="../Cargo.toml" data-bin="day9" /> + + <style> + html { + /* Remove touch delay: */ + touch-action: manipulation; + } + + html, + body { + overflow: hidden; + margin: 0 !important; + padding: 0 !important; + height: 100%; + width: 100%; + } + + canvas { + margin-right: auto; + margin-left: auto; + display: block; + position: absolute; + top: 0%; + left: 50%; + transform: translate(-50%, 0%); + } + </style> +</head> + +<body> + <canvas id="canvas"></canvas> +</body> + +</html> diff --git a/2022/src/day09.rs b/2022/src/day09.rs new file mode 100644 index 0000000..23b08b3 --- /dev/null +++ b/2022/src/day09.rs @@ -0,0 +1,238 @@ +mod utils; +use std::{ + cmp::min, + collections::{HashSet, VecDeque}, + time::Duration, +}; + +use egui::{Color32, Sense, Stroke}; +use utils::{iter_pair, read_input}; + +#[cfg(target_arch = "wasm32")] +fn main() { + console_error_panic_hook::set_once(); + + let input = include_str!("../fake_inputs/day9"); + let input = parse_moves(&input).collect(); + + let options = eframe::WebOptions::default(); + wasm_bindgen_futures::spawn_local(async { + eframe::start_web("canvas", options, Box::new(|_cc| Box::new(App::new(input)))) + .await + .expect("unable to start eframe"); + }); +} + +#[cfg(not(target_arch = "wasm32"))] +fn main() { + let input = read_input(); + + println!("Part 1: {}", visited_from_moves::<2>(parse_moves(&input))); + println!("Part 2: {}", visited_from_moves::<10>(parse_moves(&input))); +} + +type Pos = (i32, i32); +type Move = (Direction, u8); + +fn visited_from_moves<const N: usize>(moves: impl Iterator<Item = Move>) -> usize { + let mut tail_visited = HashSet::new(); + tail_visited.insert((0, 0)); + let mut knots = [(0, 0); N]; + for mv in moves { + make_move(mv, &mut knots, &mut tail_visited); + } + + tail_visited.len() +} + +fn make_move(mv: Move, knots: &mut [Pos], tail_visited: &mut HashSet<Pos>) { + let (dir, count) = mv; + + for _ in 0..count { + // Move head of rope + match dir { + Direction::Up => knots[0].1 += 1, + Direction::Down => knots[0].1 -= 1, + Direction::Right => knots[0].0 += 1, + Direction::Left => knots[0].0 -= 1, + }; + + for front_idx in 0..knots.len() - 1 { + let (fx, fy) = knots[front_idx]; + let (bx, by) = &mut knots[front_idx + 1]; + let (dx, dy) = (fx - *bx, fy - *by); + if (dx.abs() == 2 && dy == 0) || (dy.abs() == 2 && dx == 0) || (dy.abs() + dx.abs() > 2) + { + *bx += (dx.signum()) * min(dx.abs(), 1); + *by += (dy.signum()) * min(dy.abs(), 1); + } + } + tail_visited.insert(knots[knots.len() - 1]); + } +} + +fn parse_moves(input: &str) -> impl Iterator<Item = Move> + '_ { + input.lines().map(|x| { + let (dir, count) = iter_pair(x.split(' ')); + let count = count.parse().unwrap(); + + ( + match dir { + "L" => Direction::Left, + "R" => Direction::Right, + "U" => Direction::Up, + "D" => Direction::Down, + _ => panic!("invalid direction, {}", dir), + }, + count, + ) + }) +} + +#[derive(Debug, Clone, Copy)] +enum Direction { + Up, + Down, + Left, + Right, +} + +struct App { + orig_input: VecDeque<Move>, + input: VecDeque<Move>, + knots: Vec<Pos>, + tail_visited: HashSet<Pos>, + desired_knot_count: usize, + speed: usize, +} + +impl App { + fn new(input: VecDeque<Move>) -> Self { + App { + orig_input: input.clone(), + input, + knots: vec![], + tail_visited: HashSet::new(), + desired_knot_count: 2, + speed: 0, + } + } + + fn reset_knots(&mut self) { + self.input = self.orig_input.clone(); + self.knots = vec![(0, 0); self.desired_knot_count]; + self.tail_visited.clear(); + self.tail_visited.insert((0, 0)); + } + + fn step(&mut self) { + match self.input.pop_front() { + Some(mv) => make_move(mv, &mut self.knots, &mut self.tail_visited), + None => (), + } + } +} + +impl eframe::App for App { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + if self.desired_knot_count != self.knots.len() { + self.reset_knots() + } + egui::SidePanel::right("instructions").show(ctx, |ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + if ui.button("Step").clicked() { + self.step(); + } + if ui.button("Reset").clicked() { + self.reset_knots() + } + ui.label(format!("Tail Visited: {}", self.tail_visited.len())); + ui.add(egui::Slider::new(&mut self.desired_knot_count, 2..=20).text("Knot count")); + ui.add(egui::Slider::new(&mut self.speed, 0..=20).text("Speed")); + ui.heading("Instructions:"); + for (i, ins) in self.input.iter().take(20).enumerate() { + let arrow = match ins.0 { + Direction::Up => "⬆", + Direction::Down => "⬇", + Direction::Right => "➡", + Direction::Left => "⬅", + }; + if i == 0 { + ui.colored_label(Color32::YELLOW, arrow.repeat(ins.1 as _)); + } else { + ui.label(arrow.repeat(ins.1 as _)); + } + } + if self.input.len() > 20 { + ui.label(format!("+ {} more", self.input.len() - 20)); + } + }) + }); + + if self.speed > 0 && self.input.len() > 0 { + for _ in 0..self.speed { + self.step(); + } + ctx.request_repaint_after(Duration::from_millis(100)) + } + + egui::CentralPanel::default().show(ctx, |ui| { + let mut painter_size = ui.available_size_before_wrap(); + if !painter_size.is_finite() { + painter_size = egui::vec2(500.0, 500.0); + } + + const SIDE: f32 = 5.0; + + let (res, painter) = ui.allocate_painter(painter_size, Sense::hover()); + let center = res.rect.center().to_vec2(); + + let to_panel_pos = |pos: &Pos| { + (egui::vec2(pos.0 as f32 * SIDE, pos.1 as f32 * SIDE) + center).to_pos2() + }; + + let half_width = (painter_size.x / SIDE).floor() as i32; + let half_height = (painter_size.y / SIDE).floor() as i32; + + for x in -half_width..half_width { + for y in -half_height..half_height { + let dot = (x, y); + let color = if dot == (0, 0) { + Color32::WHITE + } else if self.tail_visited.contains(&dot) { + Color32::DARK_RED + } else { + continue; + }; + + let dot_pos = to_panel_pos(&dot); + painter.circle_stroke(dot_pos, 1.0, Stroke::new(1.0, color)); + } + } + + // paint the head + painter.circle_stroke( + to_panel_pos(&self.knots[0]), + SIDE / 2.0, + Stroke::new(SIDE / 2.0, Color32::GREEN), + ); + for knot in self.knots.iter().skip(1) { + painter.circle_stroke( + to_panel_pos(knot), + SIDE / 2.0, + Stroke::new(SIDE / 2.0, Color32::YELLOW), + ); + } + + for (a, b) in self.knots.iter().zip(self.knots.iter().skip(1)) { + let head_pos = to_panel_pos(a); + let tail_pos = to_panel_pos(b); + painter.arrow( + tail_pos, + head_pos - tail_pos, + Stroke::new(1.0, Color32::YELLOW), + ) + } + }); + } +} diff --git a/2022/src/day10.rs b/2022/src/day10.rs new file mode 100644 index 0000000..4c74444 --- /dev/null +++ b/2022/src/day10.rs @@ -0,0 +1,82 @@ +mod utils; +use utils::{parse_num, parse_static, read_input}; + +fn main() { + let input = read_input(); + + let mut cmds = input.lines().map(parse_cmd); + let mut state = State { + x: 1, + add_next: None, + add_next_start: None, + crt_pos: 0, + }; + + let mut acc = 0; + run_steps::<20>(&mut state, &mut cmds); + acc += state.x * 20; + for i in 1..=5 { + run_steps::<40>(&mut state, &mut cmds); + acc += state.x * (20 + (i * 40)); + } + + run_steps::<{ TOTAL_STEPS - 220 }>(&mut state, &mut cmds); + + println!("Part 1: {}", acc); +} + +struct State { + x: isize, + add_next: Option<isize>, + add_next_start: Option<isize>, + crt_pos: usize, +} + +const CRT_WIDTH: usize = 40; +const CRT_HEIGHT: usize = 6; +const TOTAL_STEPS: usize = CRT_WIDTH * CRT_HEIGHT; + +fn run_steps<const N: usize>(state: &mut State, cmd: &mut impl Iterator<Item = Command>) { + for _ in 0..N { + if let Some(add) = state.add_next_start.take() { + state.x += add; + } + + if (state.x - (state.crt_pos % CRT_WIDTH) as isize).abs() < 2 { + print!("█"); + } else { + print!(" "); + } + state.crt_pos += 1; + if (state.crt_pos % CRT_WIDTH) == 0 { + println!(); + } + + if let Some(add) = state.add_next.take() { + state.add_next_start = Some(add); + } else { + match cmd.next() { + Some(Command::NoOp) => (), + Some(Command::AddX(add)) => state.add_next = Some(add), + None => (), + } + } + } +} + +fn parse_cmd(cmd: &str) -> Command { + if cmd == "noop" { + Command::NoOp + } else { + let cmd = parse_static("addx ", cmd); + let (add, _) = parse_num(cmd); + + Command::AddX(add) + } +} + +#[derive(Debug)] +enum Command { + NoOp, + AddX(isize), +} diff --git a/2022/src/day11.rs b/2022/src/day11.rs new file mode 100644 index 0000000..aad0b48 --- /dev/null +++ b/2022/src/day11.rs @@ -0,0 +1,127 @@ +mod utils; +extern crate nom; +use nom::{ + branch::alt, + bytes::complete::{tag, take_until}, + character::complete::{digit0, u32}, + multi::separated_list0, + IResult, +}; +use utils::{max_n, read_input}; + +fn main() { + let input = read_input(); + + let (_, monkeys_items) = separated_list0(tag("\n\n"), Monkey::parse)(&input).unwrap(); + let (monkeys, items): (Vec<_>, Vec<_>) = monkeys_items.into_iter().unzip(); + + println!( + "Part 1: {}", + monkey_business::<3, 20>(&monkeys, items.clone(), usize::MAX), + ); + println!( + "Part 2: {}", + monkey_business::<1, 10000>(&monkeys, items, monkeys.iter().map(|x| x.test).product()) + ); +} + +fn monkey_business<const REDUCER: usize, const ROUNDS: usize>( + monkeys: &[Monkey], + mut items: Vec<Vec<usize>>, + additional_reducer: usize, +) -> usize { + let mut inspections = vec![0; monkeys.len()]; + for _ in 0..ROUNDS { + for monkey_idx in 0..monkeys.len() { + let curr_monkey = &monkeys[monkey_idx]; + while let Some(worry) = items[monkey_idx].pop() { + let new_worry = (curr_monkey.do_op(worry) / REDUCER) % additional_reducer; + inspections[monkey_idx] += 1; + let throw_to = curr_monkey.throw_to(new_worry); + + items[throw_to].push(new_worry); + } + } + } + + max_n(&mut inspections, 2).iter().product::<usize>() +} + +#[derive(Debug)] +struct Monkey { + op: Operation, + test: usize, + next_monkey: (usize, usize), +} + +impl Monkey { + fn parse(i: &str) -> IResult<&str, (Monkey, Vec<usize>)> { + let (i, _) = tag("Monkey ")(i)?; + let (i, _) = digit0(i)?; + let (i, _) = tag(":\n Starting items: ")(i)?; + let (i, items) = separated_list0(tag(", "), u32)(i)?; + let (i, _) = tag("\n Operation: new = ")(i)?; + let (i, op) = Operation::parse(i)?; + let (i, _) = take_until("\n")(i)?; + let (i, _) = tag("\n Test: divisible by ")(i)?; + let (i, test) = u32(i)?; + let (i, _) = tag("\n If true: throw to monkey ")(i)?; + let (i, monkey_t) = u32(i)?; + let (i, _) = tag("\n If false: throw to monkey ")(i)?; + let (i, monkey_f) = u32(i)?; + + Ok(( + i, + ( + Monkey { + op, + test: test as usize, + next_monkey: (monkey_t as usize, monkey_f as usize), + }, + items.into_iter().map(|x| x as usize).collect(), + ), + )) + } + + fn do_op(&self, x: usize) -> usize { + match self.op { + Operation::Square => x * x, + Operation::Add(c) => x + c, + Operation::Mult(c) => x * c, + } + } + fn throw_to(&self, x: usize) -> usize { + if (x % self.test) == 0 { + self.next_monkey.0 + } else { + self.next_monkey.1 + } + } +} + +#[derive(Debug)] +enum Operation { + Square, + Add(usize), + Mult(usize), +} + +impl Operation { + fn parse(i: &str) -> IResult<&str, Operation> { + let (i, _) = tag("old ")(i)?; + let (i, op) = alt((tag("* "), tag("+ ")))(i)?; + if let Ok((i, _)) = tag::<&str, &str, (&str, nom::error::ErrorKind)>("old")(i) { + Ok((i, Operation::Square)) + } else { + let (i, x) = u32(i)?; + Ok(( + i, + match op { + "* " => Operation::Mult(x as usize), + "+ " => Operation::Add(x as usize), + _ => unreachable!(), + }, + )) + } + } +} diff --git a/2022/src/day12.rs b/2022/src/day12.rs new file mode 100644 index 0000000..c446c74 --- /dev/null +++ b/2022/src/day12.rs @@ -0,0 +1,141 @@ +mod utils; +use std::collections::BinaryHeap; + +use utils::read_input; + +fn main() { + let input = read_input(); + + let width = input.lines().next().unwrap().len(); + let height = input.lines().count(); + let mut heights = vec![0; width * height]; + + let mut start = (0, 0); + let mut end = (0, 0); + + for (y, line) in input.lines().enumerate() { + for (x, c) in line.chars().enumerate() { + let val = if c == 'S' { + start = (x as isize, y as isize); + 0 + } else if c == 'E' { + end = (x as isize, y as isize); + 25 + } else { + c.to_ascii_lowercase() as isize - 97 + }; + heights[(y * width) + x] = val; + } + } + + let cost = pathfind(&heights, width, end); + + println!("Part 1: {:?}", cost[pos_to_index(&start, width)]); + println!( + "Part 2: {:?}", + (0..height as isize) + .map(|y| (0..width as isize) + .map(|x| pos_to_index(&(x, y), width)) + .filter(|idx| heights[*idx] == 0) + .map(|idx| cost[idx]) + .min() + .unwrap()) + .min() + .unwrap() + ) +} + +type HeightMap = Vec<isize>; +type Pos = (isize, isize); + +fn pathfind(heights: &HeightMap, width: usize, end: Pos) -> Vec<usize> { + let mut min_cost = vec![usize::MAX; heights.len()]; + let mut frontier: BinaryHeap<GraphPos> = BinaryHeap::new(); + frontier.push(GraphPos { pos: end, moves: 0 }); + while let Some(curr) = frontier.pop() { + let idx = pos_to_index(&curr.pos, width); + if curr.moves >= min_cost[idx] { + continue; + } else { + min_cost[idx] = curr.moves; + } + + for neighbour in CanReach::new(heights, width, curr.pos) { + frontier.push(GraphPos { + pos: neighbour, + moves: curr.moves + 1, + }) + } + } + + min_cost +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct GraphPos { + pos: Pos, + moves: usize, +} + +impl PartialOrd for GraphPos { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + self.moves.partial_cmp(&other.moves).map(|x| x.reverse()) + } +} + +impl Ord for GraphPos { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.moves.cmp(&other.moves).reverse() + } +} + +struct CanReach<'a> { + heights: &'a HeightMap, + curr: Pos, + step: usize, + width: usize, +} + +impl<'a> CanReach<'a> { + fn new(heights: &'a HeightMap, width: usize, pos: Pos) -> Self { + CanReach { + heights, + curr: pos, + step: 0, + width, + } + } +} +impl Iterator for CanReach<'_> { + type Item = Pos; + + fn next(&mut self) -> Option<Self::Item> { + let (curr_x, curr_y) = self.curr; + loop { + let (x, y) = match self.step { + 0 => (curr_x + 1, curr_y), + 1 => (curr_x - 1, curr_y), + 2 => (curr_x, curr_y + 1), + 3 => (curr_x, curr_y - 1), + _ => break, + }; + self.step += 1; + + if (x >= 0 + && x < self.width as isize + && y >= 0 + && y < (self.heights.len() / self.width) as isize) + && self.heights[pos_to_index(&(x, y), self.width)] + >= self.heights[pos_to_index(&(curr_x, curr_y), self.width)] - 1 + { + return Some((x, y)); + } + } + None + } +} + +#[inline(always)] +fn pos_to_index(pos: &Pos, width: usize) -> usize { + ((pos.1 * width as isize) + pos.0) as usize +} diff --git a/2022/src/day13.rs b/2022/src/day13.rs new file mode 100644 index 0000000..17b55eb --- /dev/null +++ b/2022/src/day13.rs @@ -0,0 +1,132 @@ +mod utils; +use std::{ + cmp::Ordering, + iter::{once, Peekable}, +}; + +extern crate nom; +use nom::{bytes::complete::tag, character::complete::u32}; +use utils::read_input; + +fn main() { + let input = read_input(); + + let mut full: Vec<_> = input.lines().filter(|x| !x.is_empty()).collect(); + + println!( + "Part 1: {}", + full.iter() + .step_by(2) + .zip(full.iter().skip(1).step_by(2)) + .map(|(left, right)| { compare(left, right) }) + .enumerate() + .filter_map(|(i, cmp)| match cmp { + Ordering::Greater => None, + _ => Some(i + 1), + }) + .sum::<usize>() + ); + + full.sort_by(|l, r| compare(l, r)); + + println!( + "Part 2: {}", + full.iter() + .enumerate() + .filter_map(|(i, val)| match *val { + "[[2]]" | "[[6]]" => Some(i + 1), + _ => None, + }) + .product::<usize>() + ); +} + +fn compare(left: &str, right: &str) -> Ordering { + let mut left = Packet(left).peekable(); + let mut right = Packet(right).peekable(); + assert_eq!(left.next(), Some(PacketContents::StartList)); + assert_eq!(right.next(), Some(PacketContents::StartList)); + compare_lists(&mut left, &mut right) +} + +fn compare_lists( + left: &mut Peekable<impl Iterator<Item = PacketContents>>, + right: &mut Peekable<impl Iterator<Item = PacketContents>>, +) -> Ordering { + loop { + match (left.next().unwrap(), right.next().unwrap()) { + (PacketContents::EndList, PacketContents::EndList) => { + return Ordering::Equal; + } + (PacketContents::EndList, _) => { + while !matches!(right.next(), Some(PacketContents::EndList)) {} + return Ordering::Less; + } + (_, PacketContents::EndList) => { + while !matches!(left.next(), Some(PacketContents::EndList)) {} + + return Ordering::Greater; + } + (PacketContents::StartList, PacketContents::StartList) => { + match compare_lists(left, right) { + Ordering::Equal => (), + x => return x, + } + } + (l @ PacketContents::Integer(_), PacketContents::StartList) => { + match compare_lists( + &mut once(l).chain(once(PacketContents::EndList)).peekable(), + right, + ) { + Ordering::Equal => (), + x => return x, + } + } + (PacketContents::StartList, r @ PacketContents::Integer(_)) => { + match compare_lists( + left, + &mut once(r).chain(once(PacketContents::EndList)).peekable(), + ) { + Ordering::Equal => (), + x => return x, + } + } + (PacketContents::Integer(l), PacketContents::Integer(r)) => match l.cmp(&r) { + Ordering::Equal => (), + x => return x, + }, + }; + } +} + +#[derive(Debug, PartialEq, Eq)] +enum PacketContents { + Integer(usize), + StartList, + EndList, +} + +struct Packet<'a>(&'a str); + +impl Iterator for Packet<'_> { + type Item = PacketContents; + + fn next(&mut self) -> Option<Self::Item> { + loop { + if let Ok((i, _)) = tag::<_, _, (&str, nom::error::ErrorKind)>("[")(self.0) { + self.0 = i; + return Some(PacketContents::StartList); + } else if let Ok((i, _)) = tag::<_, _, (&str, nom::error::ErrorKind)>("]")(self.0) { + self.0 = i; + return Some(PacketContents::EndList); + } else if let Ok((i, x)) = u32::<_, (&str, nom::error::ErrorKind)>(self.0) { + self.0 = i; + return Some(PacketContents::Integer(x as usize)); + } else if let Ok((i, _)) = tag::<_, _, (&str, nom::error::ErrorKind)>(",")(self.0) { + self.0 = i; + } else { + return None; + } + } + } +} diff --git a/2022/src/day14.html b/2022/src/day14.html new file mode 100644 index 0000000..c441303 --- /dev/null +++ b/2022/src/day14.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> + +<head> + <link data-trunk rel="rust" data-wasm-opt="2" href="../Cargo.toml" data-bin="day14" /> + + <style> + html { + /* Remove touch delay: */ + touch-action: manipulation; + } + + html, + body { + overflow: hidden; + margin: 0 !important; + padding: 0 !important; + height: 100%; + width: 100%; + } + + canvas { + margin-right: auto; + margin-left: auto; + display: block; + position: absolute; + top: 0%; + left: 50%; + transform: translate(-50%, 0%); + } + </style> +</head> + +<body> + <canvas id="canvas"></canvas> +</body> + +</html> diff --git a/2022/src/day14.rs b/2022/src/day14.rs new file mode 100644 index 0000000..903f919 --- /dev/null +++ b/2022/src/day14.rs @@ -0,0 +1,230 @@ +mod utils; +use std::{ + cmp::{max, min}, + collections::HashSet, + ops::Add, + time::Duration, +}; + +use egui::{Color32, Rect, Sense, Stroke}; +use nom::{bytes::complete::tag, character::complete::u32, multi::separated_list0, IResult}; + +fn main() { + console_error_panic_hook::set_once(); + + let input = include_str!("../fake_inputs/day14"); + + let options = eframe::WebOptions::default(); + wasm_bindgen_futures::spawn_local(async { + eframe::start_web("canvas", options, Box::new(|_cc| Box::new(App::new(input)))) + .await + .expect("unable to start eframe"); + }); +} + +struct App { + structures: Vec<Structure>, + sand_grains: HashSet<Pos>, + speed: usize, + finished: bool, +} + +impl App { + fn new(input: &str) -> Self { + let mut structures = separated_list0(tag("\n"), Structure::parse)(input) + .unwrap() + .1; + let floor_y = structures + .iter() + .map(|s| s.points.iter().map(|p| p.1).max().unwrap()) + .max() + .unwrap() + + 2; + structures.push(Structure::new(vec![ + Pos(isize::MIN, floor_y), + Pos(isize::MAX, floor_y), + ])); + + App { + structures, + sand_grains: HashSet::new(), + speed: 0, + finished: false, + } + } + + fn reset(&mut self) { + self.sand_grains.clear(); + } + + fn step(&mut self) { + let mut pos = Pos(500, 0); + if self.sand_grains.contains(&pos) { + self.finished = true; + return; + } + loop { + // TODO + pos = if !self.inhabited(pos + Pos(0, 1)) { + pos + Pos(0, 1) + } else if !self.inhabited(pos + Pos(-1, 1)) { + pos + Pos(-1, 1) + } else if !self.inhabited(pos + Pos(1, 1)) { + pos + Pos(1, 1) + } else { + self.sand_grains.insert(pos); + return; + }; + } + } + + fn inhabited(&self, pos: Pos) -> bool { + self.structures.iter().any(|s| s.contains(pos)) || self.sand_grains.contains(&pos) + } +} + +#[derive(Debug)] +struct Structure { + points: Vec<Pos>, + bbox_min: Pos, + bbox_max: Pos, +} + +impl Structure { + fn new(points: Vec<Pos>) -> Self { + let bbox_min = points.iter().fold(Pos(isize::MAX, isize::MAX), |acc, pos| { + Pos(min(acc.0, pos.0), min(acc.1, pos.1)) + }); + let bbox_max = points.iter().fold(Pos(isize::MAX, isize::MAX), |acc, pos| { + Pos(max(acc.0, pos.0), max(acc.1, pos.1)) + }); + + Structure { + points, + bbox_max, + bbox_min, + } + } + fn parse(i: &str) -> IResult<&str, Self> { + let (i, points) = separated_list0(tag(" -> "), Pos::parse)(i)?; + + Ok((i, Structure::new(points))) + } + + fn contains(&self, pos: Pos) -> bool { + if pos.0 < self.bbox_min.0 + || pos.0 > self.bbox_max.0 + || pos.1 < self.bbox_min.1 + || pos.1 > self.bbox_max.1 + { + return false; + } + for (a, b) in self.points.iter().zip(self.points.iter().skip(1)) { + if a.0 == b.0 { + if pos.0 == a.0 && pos.1 >= min(a.1, b.1) && pos.1 <= max(a.1, b.1) { + return true; + } + } else { + // a.1 == b.1 + if pos.1 == a.1 && pos.0 >= min(a.0, b.0) && pos.0 <= max(a.0, b.0) { + return true; + } + } + } + return false; + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct Pos(isize, isize); +impl Pos { + fn parse(i: &str) -> IResult<&str, Self> { + let (i, x) = u32(i)?; + let (i, _) = tag(",")(i)?; + let (i, y) = u32(i)?; + + Ok((i, Pos(x as _, y as _))) + } +} +impl From<Pos> for egui::Pos2 { + fn from(value: Pos) -> Self { + egui::Pos2 { + x: value.0 as _, + y: value.1 as _, + } + } +} + +impl Add for Pos { + type Output = Pos; + + fn add(self, rhs: Self) -> Self::Output { + Pos(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl eframe::App for App { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + egui::SidePanel::right("instructions").show(ctx, |ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + if ui.button("Step").clicked() && !self.finished { + self.step(); + } + if ui.button("Reset").clicked() { + self.reset() + } + ui.label(format!( + "{} grains, finished: {}", + self.sand_grains.len(), + self.finished + )); + ui.add(egui::Slider::new(&mut self.speed, 0..=100).text("Speed")); + }) + }); + + if self.speed > 0 { + for _ in 0..self.speed { + self.step(); + } + ctx.request_repaint_after(Duration::from_millis(100)) + } + + egui::CentralPanel::default().show(ctx, |ui| { + let mut painter_size = ui.available_size_before_wrap(); + if !painter_size.is_finite() { + painter_size = egui::vec2(500.0, 500.0); + } + + const RANGE: f32 = 200.0; + const SHIFT: egui::Pos2 = egui::pos2(400.0, 5.0); + let (_, painter) = ui.allocate_painter(painter_size, Sense::hover()); + + let transform_point = |p: &Pos| -> egui::Pos2 { + egui::pos2( + ((p.0 as f32 - SHIFT.x) / RANGE) * painter_size.x, + ((p.1 as f32 + SHIFT.y) / RANGE) * painter_size.y, + ) + }; + for structure in self.structures.iter() { + for (a, b) in structure.points.iter().zip(structure.points.iter().skip(1)) { + painter.line_segment( + [transform_point(a), transform_point(b)], + Stroke::new(1.0, Color32::RED), + ); + } + } + + for grain in self.sand_grains.iter() { + painter.rect( + Rect { + min: transform_point(grain), + max: transform_point(&Pos(grain.0 + 1, grain.1 + 1)), + }, + egui::Rounding::none(), + Color32::YELLOW, + Stroke::NONE, + ) + } + }); + } +} diff --git a/2022/src/utils.rs b/2022/src/utils.rs new file mode 100644 index 0000000..8230c69 --- /dev/null +++ b/2022/src/utils.rs @@ -0,0 +1,59 @@ +#![allow(unused)] +use std::{fs::File, io::Read, str::FromStr}; + +pub fn read_input() -> String { + let mut args = std::env::args(); + + let mut file = File::open( + args.nth(1) + .expect("first argument should be input filename"), + ) + .expect("could not open input"); + let mut out = String::new(); + file.read_to_string(&mut out) + .expect("could not read input file"); + + out +} + +pub fn max_n<T: Ord>(vec: &mut Vec<T>, n: usize) -> &[T] { + top_n_by(vec, |a, b| a > b, n) +} + +pub fn top_n_by<T, F: FnMut(&T, &T) -> bool>(vec: &mut Vec<T>, mut f: F, n: usize) -> &[T] { + for _ in 0..n { + for i in 0..vec.len() - 1 { + if f(&vec[i], &vec[i + 1]) { + vec.swap(i, i + 1); + } + } + } + + &vec[vec.len() - n..] +} + +pub fn iter_pair<T>(mut i: impl Iterator<Item = T>) -> (T, T) { + (i.next().unwrap(), i.next().unwrap()) +} + +pub fn iter_triple<T>(mut i: impl Iterator<Item = T>) -> (T, T, T) { + (i.next().unwrap(), i.next().unwrap(), i.next().unwrap()) +} + +#[inline(always)] +pub fn parse_num<T: FromStr>(string: &str) -> (T, &str) +where + <T as FromStr>::Err: std::fmt::Debug, +{ + let count = string + .chars() + .take_while(|x| x.is_numeric() || *x == '-') + .count(); + + (string[..count].parse().unwrap(), &string[count..]) +} + +#[inline(always)] +pub fn parse_static<'a>(exp: &'static str, source: &'a str) -> &'a str { + &source[exp.len()..] +} |