day2: [rust] part 1
authorPat Thoyts <pat.thoyts@gmail.com>
Sun, 3 Dec 2023 19:28:09 +0000 (19:28 +0000)
committerPat Thoyts <pat.thoyts@gmail.com>
Sun, 3 Dec 2023 19:28:09 +0000 (19:28 +0000)
.gitignore
day2/Cargo.toml [new file with mode: 0644]
day2/src/game.rs [new file with mode: 0644]
day2/src/main.rs [new file with mode: 0644]
day2/src/round.rs [new file with mode: 0644]

index 88b2c3731841a18efa6cb61caba026c7d8532155..27a6e261f92a5e999428d04e32354f1c468d44d4 100644 (file)
@@ -1,2 +1,3 @@
 __pycache__/
 Cargo.lock
+target/
diff --git a/day2/Cargo.toml b/day2/Cargo.toml
new file mode 100644 (file)
index 0000000..49e0a47
--- /dev/null
@@ -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 (file)
index 0000000..11c427c
--- /dev/null
@@ -0,0 +1,60 @@
+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);
+    }
+}
diff --git a/day2/src/main.rs b/day2/src/main.rs
new file mode 100644 (file)
index 0000000..0b585a5
--- /dev/null
@@ -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<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);
+    }
+}
diff --git a/day2/src/round.rs b/day2/src/round.rs
new file mode 100644 (file)
index 0000000..0b01fc7
--- /dev/null
@@ -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<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());
+    }
+}