From 850da3545db725af4ccfb8803f12a70464c2eda6 Mon Sep 17 00:00:00 2001 From: Pat Thoyts Date: Sun, 3 Dec 2023 19:28:09 +0000 Subject: [PATCH] day2: [rust] part 1 --- .gitignore | 1 + day2/Cargo.toml | 5 ++++ day2/src/game.rs | 60 +++++++++++++++++++++++++++++++++++++++ day2/src/main.rs | 72 +++++++++++++++++++++++++++++++++++++++++++++++ day2/src/round.rs | 67 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 205 insertions(+) create mode 100644 day2/Cargo.toml create mode 100644 day2/src/game.rs create mode 100644 day2/src/main.rs create mode 100644 day2/src/round.rs diff --git a/.gitignore b/.gitignore index 88b2c37..27a6e26 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__/ Cargo.lock +target/ diff --git a/day2/Cargo.toml b/day2/Cargo.toml new file mode 100644 index 0000000..49e0a47 --- /dev/null +++ b/day2/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "cubes" +version = "1.0.0" + +[dependencies] diff --git a/day2/src/game.rs b/day2/src/game.rs new file mode 100644 index 0000000..11c427c --- /dev/null +++ b/day2/src/game.rs @@ -0,0 +1,60 @@ +use std::str::FromStr; +use round::Round; + +#[derive(Debug, PartialEq, Eq)] +pub struct Game { + pub id: u32, + pub rounds: Vec, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ParseGameError; + +impl FromStr for Game { + type Err = ParseGameError; + + fn from_str(s: &str) -> Result { + let game = match s.trim().split_once(':') { + Some((idstr, rest)) => { + let id: u32 = match idstr.trim().split_once(' ') { + Some((_prefix, num)) => { + num.parse().unwrap() + } + None => { + return Err(ParseGameError); + } + }; + let rounds = rest + .trim() + .split(';') + .map(|x| Round::from_str(x).unwrap()) + .collect::>(); + Game { id: id, rounds: rounds } + } + None => { + return Err(ParseGameError); + } + }; + Ok(game) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_game_parse_one_round() { + let expected = Ok(Game { id: 1, rounds: vec![Round { red: 20, green: 1, blue: 2}]}); + assert_eq!(Game::from_str("Game 1: 20 red, 1 green, 2 blue"), expected); + } + + #[test] + fn test_game_parse_two_round() { + let expected = Ok(Game { id: 1, rounds: vec![ + Round { red: 20, green: 1, blue: 2}, + Round { red: 0, green:2, blue: 0}, + ]}); + assert_eq!(Game::from_str("Game 1: 20 red, 1 green, 2 blue; 2 green\n"), expected); + } +} diff --git a/day2/src/main.rs b/day2/src/main.rs new file mode 100644 index 0000000..0b585a5 --- /dev/null +++ b/day2/src/main.rs @@ -0,0 +1,72 @@ +pub mod round; +pub mod game; + +use round::Round; +use game::Game; +use std::env; +use std::error::Error; +use std::str::FromStr; +use std::fs::File; +use std::io::{BufReader, BufRead}; //, Error}; + +fn valid_round(round: &Round, limits: &Round) -> bool { + return round.red <= limits.red + && round.green <= limits.green + && round.blue <= limits.blue; +} + +fn valid_game(game: &Game, limits: &Round) -> bool { + return game.rounds.iter().all(|x| valid_round(x, limits)) +} + +#[allow(dead_code)] +fn print_game(game: &Game) { + print!("Game {}: ", game.id); + for round in game.rounds.iter() { + print!("{} red, {} green, {} blue;", round.red, round.green, round.blue); + } + println!(""); +} + +fn main() -> Result<(), Box> { + let args: Vec = env::args().collect(); + if args.len() > 1 { + let filename: &String = &args[1]; + let input = File::open(filename).expect("failed to open file"); + let buffered = BufReader::new(input); + + let games = buffered.lines() + .map(|x| String::from(x.expect("x").trim())) + .filter(|x| !x.is_empty()) + .map(|x| Game::from_str(&x).unwrap()) + .collect::>(); + + let limits = Round { red: 12, green: 13, blue: 14 }; + let sum_ids: u32= games.into_iter() + .filter(|g| valid_game(g, &limits)) + // .inspect(|g| print_game(g)) + .map(|g| g.id) + .sum(); + println!("part 1: {}", sum_ids); + } + Ok(()) +} + +#[cfg(tests)] +mod tests { + use super::*; + + #[test] + fn test_game_parse_multiple() { + let input = "Game 1: 2 green; 3 red\nGame 2: 1 green, 1 red, 1 blue\n"; + let expected = vec![ + Game { id: 1, rounds: vec![Round { red: 2, green: 3, blue: 0 }] }, + Game { id: 2, rounds: vec![Round { red: 1, green: 1, blue: 1 }] }, + ]; + + let result = input.split('\n') + .map(|x| Game::from_str(x).unwrap()) + .collect::>(); + assert_eq!(result, expected); + } +} diff --git a/day2/src/round.rs b/day2/src/round.rs new file mode 100644 index 0000000..0b01fc7 --- /dev/null +++ b/day2/src/round.rs @@ -0,0 +1,67 @@ +use std::str::FromStr; + +#[derive(Debug, PartialEq, Eq)] +pub struct Round { + pub red: u32, + pub green: u32, + pub blue: u32 +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ParseRoundError; + +impl FromStr for Round { + type Err = ParseRoundError; + + fn from_str(s: &str) -> Result { + + let mut tmp: Round = Round { red: 0, green: 0, blue: 0 }; + let parts = s.trim().split(','); + for part in parts { + match part.trim().split_once(' ') { + Some((number, name)) => { + let value: u32 = number.trim().parse().unwrap(); + match name { + "red" => tmp.red = value, + "green" => tmp.green = value, + "blue" => tmp.blue = value, + _ => return Err(ParseRoundError), + } + } + None => { + return Err(ParseRoundError); + } + } + } + + Ok(tmp) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_full() { + let expected = Ok(Round { red: 20, green: 1, blue: 2}); + assert_eq!(Round::from_str("20 red, 1 green, 2 blue"), expected); + assert_eq!(Round::from_str("1 green, 20 red, 2 blue"), expected); + } + + #[test] + fn test_parse_partial() { + let expected = Ok(Round {red: 5, green: 0, blue: 0}); + assert_eq!(Round::from_str("5 red"), expected); + } + + #[test] + fn test_parse_invalid_color() { + assert!(Round::from_str("1 black").is_err()); + } + + #[test] + fn test_parse_invalid_line() { + assert!(Round::from_str("").is_err()); + } +} -- 2.23.0