DEV Community

Discussion on: AoC Day 2: Inventory Management System

Collapse
 
rpalo profile image
Ryan Palo

Part 1

You can probably see my Python shining through as I implemented a custom Counter struct to help me out.

use std::collections::HashMap;

// Part 1

/// A histogram of the characters in a string.
struct Counter {
    letters: HashMap<char, usize>,
}

impl Counter {
    pub fn new(word: &str) -> Self {
        let mut letters = HashMap::new();
        for letter in word.chars() {
            let current_reference = letters.entry(letter).or_insert(0);
            *current_reference += 1;
        }
        Self { letters }
    }

    pub fn count_value(&self, number: usize) -> usize {
        self.letters.values().filter(|count| **count == number).count()
    }
}

/// Calculates a checksum for id strings.
/// 
/// The checksum is the number of id's with at least one set of exactly
/// two of a letter times the number of id's with at least one set of
/// exactly three of a letter.  If it has more than one
pub fn checksum(text: &str) -> usize {
    let mut twos = 0;
    let mut threes = 0;
    text.lines()
        .map(|id| Counter::new(id))
        .for_each(|counter| {
            if counter.count_value(2) != 0 {
                twos += 1;
            }
            if counter.count_value(3) != 0 {
                threes += 1;
            }
        });
    twos * threes
}

Part 2

Double for loop boooooooo! But it works and it's fast enough for now. I'm pretty happy with the mileage I got out of Iterators for this part.

// Part 2

/// Finds the letters that are shared between the two prototype fabric
/// box ids.
/// 
/// These ids are the only two that differ from each other by exactly
/// one letter.
pub fn prototype_ids_common_letters(text: &str) -> String {
    let ids: Vec<&str> = text.lines().collect();
    for (i, s1) in ids.iter().enumerate() {
        for s2 in ids.iter().skip(i) {
            if hamming_distance(s1, s2) == 1 {
                return common_letters(s1, s2);
            }
        }
    }
    String::new()
}

/// Calculates the "Hamming Distance" between two strings
/// 
/// Hamming distance is the number of characters who are different
/// between the two strings when the corresponding indices are compared
/// in each string
fn hamming_distance(s1: &str, s2: &str) -> usize {
    s1.chars().zip(s2.chars())
        .filter(|(c1, c2)| c1 != c2)
        .count()
}

/// Returns the letters that are the same (and in the same place)
/// between the two strings
fn common_letters(s1: &str, s2: &str) -> String {
    s1.chars().zip(s2.chars())
        .filter(|(c1, c2)| c1 == c2)
        .map(|(c1, _c2)| c1)
        .collect()
}
Collapse
 
ballpointcarrot profile image
Christopher Kruse

This is very well documented and clear, easy-to-read code. This also makes me want to jump into Rust again (I've only hobbied around with it here and there).

Collapse
 
rpalo profile image
Ryan Palo

Thanks! That really encouraging!

Collapse
 
shritesh profile image
Shritesh Bhattarai

I love how you've used enumerate and skip together in your nested for loop. I struggled to find a clean solution like this.

Collapse
 
rpalo profile image
Ryan Palo

Thanks! Yeah, I originally had a very manual nested for-loop set up, but after I got the tests passing, I decided to make an effort to do everything I could with iterators instead :) I've decided that the iterator module in Rust is where most of the magic that I'm missing from Python and Ruby lives.

Thread Thread
 
shritesh profile image
Shritesh Bhattarai

This was still bothering me, but I found the Itertools crate and the tuple_combinations method. Check out my updated solution in the thread. Itertools makes iterators even more powerful.