Advent of Code Day 03 (Petros's adventures)

Hey hey folks. This is my attempt to solve Day 03 of this year's Advent of Code.

This is the first time I use Rust to solve something.

I feel this code must be the worst Rust code ever written, hahaha.

It finds the answers, but I am sure there must be hugely better ways to go about it.

If you don't mind the spoiler, I'd love any feedback at all. Even the smallest feedback.

Yeah, lots of room for improvement; in no particular order:

  • return at the end of functions is unnecessary. Rust is an expression language.
  • Lots of mutation and looping through collections could be replaced by iterator chains and collect() or the vec![] macro, etc.
  • &Vec<_> as a function argument is harmful. You should pass &[_] instead. A lot of types coerce to a slice, but not many coerce to a vector. Therefore, unless you already have a vector to begin with, you'll force the caller to allocate a Vec just for the sake of being able to pass a Vec.
  • The same applies to &String vs. &str, too.
  • (&s[..middle], &s[middle..]) is in std under the name str::split_at() (same for slices).
  • Using enumerate() and then explicitly indexing into the slice being iterated over seems weird. Use .chunks() instead.
  • You are using String all over the place. In many cases, a char would suffice.
  • You are cloning a u32 in get_priority(). That's useless.
  • You are using u32 for counting. Don't do that. Use usize instead.

Overall, this is the cleaned up code, with the following modifications (~20% shorter):

diff --git a/src/day03.rs b/src/day03.rs
index 46561ca..74b733b 100644
--- a/src/day03.rs
+++ b/src/day03.rs
@@ -2,147 +2,116 @@ use crate::utils::load_from_file;
 use std::collections::BTreeMap;
 
 struct Racksuck {
-    priority: u32,
+    priority: usize,
 }
 
 // https://adventofcode.com/2022/day/3
 pub fn solve_day_03() {
     println!("--- Day 3: Rucksack Reorganization ---");
-    let priorities_table: BTreeMap<String, u32> = build_priorities_table();
+    let priorities_table = build_priorities_table();
     test_example(&priorities_table);
     let racksucks = build_racksucks(&priorities_table, &get_input());
     assert_eq!(racksucks.len(), 300);
     println!(
         "  The sum of the priorities of all duplicate item types is {}",
-        calculate_priority_sum(racksucks)
+        calculate_priority_sum(&racksucks)
     );
     println!("  --- Part Two ---");
     test_example_p2(&priorities_table);
     let racksucks = build_racksucks_p2(&priorities_table, &get_input());
     println!(
         "  The sum of the priorities of those types is {}",
-        calculate_priority_sum(racksucks)
+        calculate_priority_sum(&racksucks)
     );
 }
 
-fn test_example(priorities_table: &BTreeMap<String, u32>) {
+fn test_example(priorities_table: &BTreeMap<char, usize>) {
     let racksucks = build_racksucks(&priorities_table, &get_example_input());
-    assert_eq!(calculate_priority_sum(racksucks), 157);
+    assert_eq!(calculate_priority_sum(&racksucks), 157);
 }
 
-fn test_example_p2(priorities_table: &BTreeMap<String, u32>) {
+fn test_example_p2(priorities_table: &BTreeMap<char, usize>) {
     let racksucks = build_racksucks_p2(&priorities_table, &get_example_input());
-    assert_eq!(calculate_priority_sum(racksucks), 70);
+    assert_eq!(calculate_priority_sum(&racksucks), 70);
 }
 
-fn get_priority(priorities_table: &BTreeMap<String, u32>, duplicate_item_type: &String) -> u32 {
-    let priority: u32 = match priorities_table.get(duplicate_item_type) {
-        Some(&priority) => priority,
-        _ => 0,
-    };
-    return priority.clone();
+fn get_priority(priorities_table: &BTreeMap<char, usize>, duplicate_item_type: char) -> usize {
+    priorities_table.get(&duplicate_item_type).copied().unwrap_or(0)
 }
 
-fn build_priorities_table() -> BTreeMap<String, u32> {
-    let mut table: BTreeMap<String, u32> = BTreeMap::new();
-    let mut counter = 0;
-    for chr in 'a'..='z' {
-        counter += 1;
-        table.insert(chr.to_string(), counter);
-    }
-    for chr in 'A'..='Z' {
-        counter += 1;
-        table.insert(chr.to_string(), counter);
-    }
-    return table;
+fn build_priorities_table() -> BTreeMap<char, usize> {
+    ('a'..='z')
+        .chain('A'..='Z')
+        .enumerate()
+        .map(|(i, c)| (c, i + 1))
+        .collect()
 }
 
 fn get_example_input() -> Vec<String> {
-    let mut result: Vec<String> = Vec::new();
-    result.push(String::from("vJrwpWtwJgWrhcsFMMfFFhFp"));
-    result.push(String::from("jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL"));
-    result.push(String::from("PmmdzqPrVvPwwTWBwg"));
-    result.push(String::from("wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn"));
-    result.push(String::from("ttgJtRGJQctTZtZT"));
-    result.push(String::from("CrZsJsPPZsGzwwsLwLmpwMDw"));
-    return result;
+    vec![
+        String::from("vJrwpWtwJgWrhcsFMMfFFhFp"),
+        String::from("jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL"),
+        String::from("PmmdzqPrVvPwwTWBwg"),
+        String::from("wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn"),
+        String::from("ttgJtRGJQctTZtZT"),
+        String::from("CrZsJsPPZsGzwwsLwLmpwMDw"),
+    ]
 }
 
 fn get_input() -> Vec<String> {
-    return load_from_file("src/puzzle_inputs/day03.txt");
+    load_from_file("src/puzzle_inputs/day03.txt")
 }
 
-fn build_racksucks(priorities_table: &BTreeMap<String, u32>, input: &Vec<String>) -> Vec<Racksuck> {
-    let mut result: Vec<Racksuck> = Vec::new();
-    for line in input {
-        let length = line.len();
-        let start = 0;
-        let middle = length / 2;
-        let end = length;
-
-        let compartment_one: String = line[start..middle].to_string();
-        let compartment_two: String = line[middle..end].to_string();
-        result.push(build_racksuck(
-            priorities_table,
-            &compartment_one,
-            &compartment_two,
-        ));
-    }
-    return result;
+fn build_racksucks(priorities_table: &BTreeMap<char, usize>, input: &[String]) -> Vec<Racksuck> {
+    input
+        .iter()
+        .map(|line| {
+            let (head, tail) = line.split_at(line.len() / 2);
+            build_racksuck(priorities_table, head, tail)
+        })
+        .collect()
 }
 
 fn build_racksucks_p2(
-    priorities_table: &BTreeMap<String, u32>,
-    input: &Vec<String>,
+    priorities_table: &BTreeMap<char, usize>,
+    input: &[String],
 ) -> Vec<Racksuck> {
-    let mut result: Vec<Racksuck> = Vec::new();
-    for (pos, line) in input.iter().enumerate().step_by(3) {
-        let group_line1 = line;
-        let group_line2 = &input[pos + 1];
-        let group_line3 = &input[pos + 2];
-
-        for i in group_line1.chars() {
-            if group_line2.contains(i) && group_line3.contains(i) {
-                result.push(build_racksuck_p2(priorities_table, &i.to_string()));
-                break;
-            }
-        }
-    }
-    return result;
+    input
+        .chunks(3)
+        .filter_map(|lines| {
+            let [group_line1, group_line2, group_line3] = <&[_; 3]>::try_from(lines).unwrap();
+
+            group_line1
+                .chars()
+                .find(|&c| group_line2.contains(c) && group_line3.contains(c))
+                .map(|c| build_racksuck_p2(priorities_table, c))
+        })
+        .collect()
 }
 
 fn build_racksuck(
-    priorities_table: &BTreeMap<String, u32>,
-    compartment_one: &String,
-    compartment_two: &String,
+    priorities_table: &BTreeMap<char, usize>,
+    compartment_one: &str,
+    compartment_two: &str,
 ) -> Racksuck {
-    let duplicate_item_type: String = get_duplicate_item_type(&compartment_one, &compartment_two);
-    let priority = get_priority(priorities_table, &duplicate_item_type);
+    let priority = match get_duplicate_item_type(&compartment_one, &compartment_two) {
+        Some(duplicate_item_type) => {
+            get_priority(priorities_table, duplicate_item_type)
+        }
+        None => 0
+    };
     Racksuck { priority }
 }
 
-fn build_racksuck_p2(priorities_table: &BTreeMap<String, u32>, badge: &String) -> Racksuck {
-    let priority = get_priority(priorities_table, &badge);
+fn build_racksuck_p2(priorities_table: &BTreeMap<char, usize>, badge: char) -> Racksuck {
+    let priority = get_priority(priorities_table, badge);
     Racksuck { priority }
 }
 
-fn get_duplicate_item_type(compartment_one: &String, compartment_two: &String) -> String {
-    let mut result: String = String::new();
-    for i in compartment_one.chars() {
-        for y in compartment_two.chars() {
-            if i == y {
-                result = i.to_string();
-                break;
-            }
-        }
-    }
-    return result;
+fn get_duplicate_item_type(compartment_one: &str, compartment_two: &str) -> Option<char> {
+    compartment_one.chars().find(|&c| compartment_two.contains(c))
 }
 
-fn calculate_priority_sum(racksucks: Vec<Racksuck>) -> u32 {
-    let mut result: u32 = 0;
-    for racksuck in racksucks {
-        result += racksuck.priority;
-    }
-    return result;
+fn calculate_priority_sum(racksucks: &[Racksuck]) -> usize {
+    racksucks.iter().map(|r| r.priority).sum()
 }
4 Likes

Oh my Zeus!! Thank you so much @H2CO3! Choosing when to use & and when not is confusing me a lot. And I have to learn iterators and how to express myself using those instead of "manually" looping all over the place. Indeed. Thank you so much for spending the time on this code. I appreciate it.

It's simple. Rust references are just like pointers in other languages, except with compile-time validity checks. So use references when you need indirection, and don't use them when you don't need indirection.

2 Likes