diff --git a/2020/.gitignore b/2020/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/2020/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/2020/Cargo.toml b/2020/Cargo.toml new file mode 100644 index 0000000..31173e9 --- /dev/null +++ b/2020/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "aoc-2020" +version = "0.1.0" +authors = ["Bert Peters "] +edition = "2018" + +[dependencies] +clap = "3.0.0-beta.2" diff --git a/2020/README.md b/2020/README.md new file mode 100644 index 0000000..933968c --- /dev/null +++ b/2020/README.md @@ -0,0 +1,24 @@ +# Advent of Code 2020 + +This folder contains a runner script for Advent of Code 2020. This year, I'm +attempting to solve every problem once more in Rust. + +``` +aoc-2020 +Advent of Code 2020 runner + +USAGE: + aoc-2020 [FLAGS] [OPTIONS] + +ARGS: + Which day to run + +FLAGS: + -h, --help Prints help information + -2, --part2 Run part 2 instead of part 1 + -t, --time Print time taken + -V, --version Prints version information + +OPTIONS: + -i, --input Read input from the given file instead of stdin +``` diff --git a/2020/src/common.rs b/2020/src/common.rs new file mode 100644 index 0000000..17eb0b8 --- /dev/null +++ b/2020/src/common.rs @@ -0,0 +1,77 @@ +use std::collections::HashMap; +use std::fmt::Debug; +use std::hash::Hash; +use std::io::BufRead; +use std::io::BufReader; +use std::io::Read; +use std::iter::FromIterator; +use std::str::FromStr; + +/// Read input line by line and try to parse it into some collection. +pub fn from_lines(input: &mut dyn Read) -> T +where + I: FromStr, + E: Debug, + T: FromIterator, +{ + let reader = BufReader::new(input); + + reader + .lines() + .map(|line| line.unwrap().parse::().unwrap()) + .collect() +} + +/// Parse the entire input into a single variable +pub fn read_single_input(input: &mut dyn Read) -> T +where + T: FromStr, + ::Err: Debug, +{ + let mut buf = String::new(); + input.read_to_string(&mut buf).unwrap(); + + buf.trim().parse().unwrap() +} + +/// An interface to count elements in particular categories. +pub trait GroupingCount { + /// The type of the categories under inspection + type Type; + + /// Count the occurrence of all possible values. + /// + /// This method will return a map from a value to its occurrence rate. + fn grouping_count(&mut self) -> HashMap; +} + +impl GroupingCount for T +where + T: Iterator, + T::Item: Eq + Hash, +{ + type Type = T::Item; + + fn grouping_count(&mut self) -> HashMap { + let mut counts = HashMap::new(); + + for element in self { + *counts.entry(element).or_insert(0) += 1; + } + + counts + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_grouping_count() { + let result = [1, 1, 2, 2, 3, 1].iter().grouping_count(); + assert_eq!(3, result[&1]); + assert_eq!(2, result[&2]); + assert_eq!(1, result[&3]); + } +} diff --git a/2020/src/day01.rs b/2020/src/day01.rs new file mode 100644 index 0000000..8c65a2b --- /dev/null +++ b/2020/src/day01.rs @@ -0,0 +1,40 @@ +use std::io::Read; + +use crate::common::from_lines; +use crate::Solution; + +#[derive(Default)] +pub struct Day01; + +fn fuel_required(weight: u32) -> u32 { + (weight / 3).saturating_sub(2) +} + +fn recursive_fuel(mut weight: u32) -> u32 { + let mut required = 0; + + while weight > 0 { + weight = fuel_required(weight); + required += weight + } + + required +} + +impl Solution for Day01 { + fn part1(&mut self, input: &mut dyn Read) -> String { + let weights: Vec = from_lines(input); + + let fuel_required: u32 = weights.into_iter().map(fuel_required).sum(); + + fuel_required.to_string() + } + + fn part2(&mut self, input: &mut dyn Read) -> String { + let weights: Vec = from_lines(input); + + let fuel_required: u32 = weights.into_iter().map(recursive_fuel).sum(); + + fuel_required.to_string() + } +} diff --git a/2020/src/lib.rs b/2020/src/lib.rs new file mode 100644 index 0000000..bf5d827 --- /dev/null +++ b/2020/src/lib.rs @@ -0,0 +1,19 @@ +use std::io::Read; + +mod common; +mod day01; + +pub trait Solution { + fn part1(&mut self, input: &mut dyn Read) -> String; + + fn part2(&mut self, _input: &mut dyn Read) -> String { + unimplemented!("Still working on part 1"); + } +} + +pub fn get_implementation(day: usize) -> Box { + match day { + 1 => Box::new(day01::Day01::default()), + _ => panic!("Unsupported day {}", day), + } +} diff --git a/2020/src/main.rs b/2020/src/main.rs new file mode 100644 index 0000000..43f3e31 --- /dev/null +++ b/2020/src/main.rs @@ -0,0 +1,52 @@ +use std::fs::File; +use std::io::Read; +use std::num::NonZeroUsize; +use std::path::PathBuf; +use std::time::Instant; + +use clap::Clap; + +use aoc_2020::get_implementation; + +/// Advent of Code 2020 runner +#[derive(Clap)] +struct Opts { + /// Which day to run + day: NonZeroUsize, + + /// Print time taken + #[clap(short, long)] + time: bool, + + /// Run part 2 instead of part 1 + #[clap(short = '2', long)] + part2: bool, + + /// Read input from the given file instead of stdin + #[clap(short, long)] + input: Option, +} + +fn main() { + let opts: Opts = Opts::parse(); + + let mut implementation = get_implementation(opts.day.get()); + let mut input: Box = if let Some(input) = opts.input { + Box::new(File::open(&input).expect("Failed to open input")) + } else { + Box::new(std::io::stdin()) + }; + + let begin = Instant::now(); + let result = if opts.part2 { + implementation.part2(&mut input) + } else { + implementation.part1(&mut input) + }; + + if opts.time { + eprintln!("Execution time: {:?}", Instant::now().duration_since(begin)); + } + + println!("{}", result); +}