__pycache__/
Cargo.lock
+target/
--- /dev/null
+[package]
+name = "cubes"
+version = "1.0.0"
+
+[dependencies]
--- /dev/null
+use std::str::FromStr;
+use round::Round;
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Game {
+ pub id: u32,
+ pub rounds: Vec<Round>,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct ParseGameError;
+
+impl FromStr for Game {
+ type Err = ParseGameError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ 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::<Vec<_>>();
+ 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);
+ }
+}
--- /dev/null
+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<dyn Error>> {
+ let args: Vec<String> = 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::<Vec<_>>();
+
+ 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::<Vec<_>>();
+ assert_eq!(result, expected);
+ }
+}
--- /dev/null
+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<Self, Self::Err> {
+
+ 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());
+ }
+}