Add day 3

Whew, they said it got harder on weekends but I do not remember day 3
ever being this difficult. I think, at this point, I'd struggle
recommending 2023 to someone who hadn't done much programming before
which is a real shame.

Today was effectively a parsing problem, with part 2 following on fine
from part one (but not in quite the way I'd hoped, so while my
modifications were small they were larger than I estimated). All in all
I didn't allocate enough time and ended up finishing at a few minutes
past-midnight; lesson learned I guess...

I'm not sure if I missed part of the challenge here, I've had a friend
who had a problem that worked on their input but failed on a mutual's,
and I've also had them tell me that the test case wasn't good enough to
catch their pitfall: clearly there's some mistake I avoided here,
although they also thought part 2 was a cakewalk which I did not.

My solution today isn't particularly beautiful, but it works and
includes both part 1 and part 2. I guess that's all I can hope for, for
quite a rushed solution to a harder problem than estimated. Here's to
hoping for a beautiful challenge in day 4!

Change-Id: I8f75e7b659b424c8afb2895546689cbff0885625
Reviewed-on: https://git.clicks.codes/c/Minion/AdventOfCode/2023/+/162
diff --git a/day3/Cargo.lock b/day3/Cargo.lock
new file mode 100644
index 0000000..4104d70
--- /dev/null
+++ b/day3/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "day3"
+version = "0.1.0"
diff --git a/day3/Cargo.toml b/day3/Cargo.toml
new file mode 100644
index 0000000..898e70d
--- /dev/null
+++ b/day3/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "day3"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/day3/src/main.rs b/day3/src/main.rs
new file mode 100644
index 0000000..8f5bdd8
--- /dev/null
+++ b/day3/src/main.rs
@@ -0,0 +1,193 @@
+use std::collections::HashMap;
+
+fn is_symbol(character: &char) -> bool {
+    return !character.is_digit(10) && *character != '.';
+}
+
+#[derive(Debug, Clone)]
+struct SchematicNumber {
+    number: u16,
+    surroundings: Vec<char>
+}
+
+impl SchematicNumber {
+    fn is_part_number(&self) -> bool {
+        self.surroundings.iter().any(is_symbol)
+    }
+}
+
+#[derive(Debug)]
+struct Gear {
+    surroundings: Vec<SchematicNumber>
+}
+
+impl Gear {
+    fn sum(&self) -> u64 {
+        let mut part_number_count = 0;
+        let mut part_number_mul: u64 = 1;
+
+        for schematic_number in self.surroundings.iter() {
+            if schematic_number.is_part_number() {
+                part_number_count += 1;
+                part_number_mul *= Into::<u64>::into(schematic_number.number);
+            }
+        }
+
+        if part_number_count != 2 {
+            return 0;
+        }
+
+        return part_number_mul;
+    }
+    fn add_number(&mut self, number: SchematicNumber) {
+        self.surroundings.push(number);
+    }
+}
+
+fn process_gear(number: &SchematicNumber, gears: &mut HashMap<(usize, usize), Gear>, row: usize, col: usize) {
+    let position = gears.get_mut(&(row, col));
+
+    if let Some(gear) = position {
+        gear.add_number(number.clone())
+    } else {
+        gears.insert((row, col), Gear { surroundings: vec![ number.clone() ] });
+    }
+}
+
+fn process_number(gears: &mut HashMap<(usize, usize), Gear>, input: &str, digits: String, row: usize, start_col: usize, end_col: usize) -> SchematicNumber {
+    let mut surroundings: Vec<char> = vec![];
+
+    let input_lines: Vec<&str> = input.split('\n').collect();
+    let prev_line: Vec<char> = (if row > 0 { input_lines[row - 1] } else { "" }).chars().collect();
+    let current_line: Vec<char> = input_lines[row].chars().collect();
+    let next_line: Vec<char> = (if row < input_lines.len() - 1 { input_lines[row + 1] } else { "" }).chars().collect();
+
+    let mut gear_positions: Vec<(usize, usize)> = vec![];
+
+    if start_col > 0 {
+        surroundings.push(current_line[start_col - 1]);
+        if current_line[start_col - 1] == '*' {
+            gear_positions.push((row, start_col - 1));
+        }
+    }
+
+    if end_col < current_line.len() - 1 {
+        surroundings.push(current_line[end_col + 1]);
+        if current_line[end_col + 1] == '*' {
+            gear_positions.push((row, end_col + 1));
+        }
+    }
+
+    if !prev_line.is_empty() {
+        for col in start_col..end_col+1 {
+            surroundings.push(prev_line[col]);
+            if prev_line[col] == '*' {
+                gear_positions.push((row - 1, col));
+            }
+        }
+        if start_col > 0 {
+            surroundings.push(prev_line[start_col - 1]);
+            if prev_line[start_col - 1] == '*' {
+                gear_positions.push((row - 1, start_col - 1));
+            }
+        }
+        if end_col < prev_line.len() - 1 {
+            surroundings.push(prev_line[end_col + 1]);
+            if prev_line[end_col + 1] == '*' {
+                gear_positions.push((row - 1, end_col + 1));
+            }
+        }
+    }
+
+    if !next_line.is_empty() {
+        for col in start_col..end_col+1{
+            surroundings.push(next_line[col]);
+            if next_line[col] == '*' {
+                gear_positions.push((row + 1, col));
+            }
+        }
+        if start_col > 0 {
+            surroundings.push(next_line[start_col - 1]);
+            if next_line[start_col - 1] == '*' {
+                gear_positions.push((row + 1, start_col - 1));
+            }
+        }
+        if end_col < next_line.len() - 1 {
+            surroundings.push(next_line[end_col + 1]);
+            if next_line[end_col + 1] == '*' {
+                gear_positions.push((row + 1, end_col + 1));
+            }
+        }
+    }
+
+    println!("Surroundings: {:?}, digits: {}", surroundings, digits);
+
+    let schematic_number = SchematicNumber {
+        number: digits.parse().unwrap(),
+        surroundings
+    };
+
+    println!("{:#?}: is_part_number: {}", schematic_number, schematic_number.is_part_number());
+
+    for pos in gear_positions {
+        process_gear(&schematic_number, gears, pos.0, pos.1);
+    }
+
+    schematic_number
+}
+
+fn main() {
+    let input = include_str!("../input.txt");
+
+    let mut numbers: Vec<SchematicNumber> = vec![];
+    let mut gears: HashMap<(usize, usize), Gear> = HashMap::new();
+
+    let mut number_start: Option<usize> = None;
+    let mut number_digits = "".to_owned();
+
+    // Find numbers and their surroundings
+    for (row, line) in input.split('\n').enumerate() {
+        for (col, character) in line.chars().enumerate() {
+            match (number_start, character.is_digit(10)) {
+                (Some(_), true) => {
+                    number_digits.push(character);
+                    println!("{} is still a digit. number_digits is now {}", character, number_digits);
+                },
+                (Some(start_col), false) => {
+                    numbers.push(process_number(&mut gears, input, number_digits, row, start_col, col - 1));
+                    number_digits = "".to_owned();
+                    number_start = None;
+                },
+                (None, true) => {
+                    number_digits.push(character);
+                    number_start = Some(col);
+                    println!("{} is a digit. Starting a new number at postition {} with number_digits {}", character, number_start.unwrap(), number_digits);
+                },
+                (None, false) => {}
+            }
+        }
+        if let Some(start_col) = number_start {
+            numbers.push(process_number(&mut gears, input, number_digits, row, start_col, line.len() - 1));
+            number_digits = "".to_owned();
+            number_start = None;
+        }
+    }
+
+    let mut numbers_total: u64 = 0;
+    let mut gears_total: u64 = 0;
+
+    for number in numbers {
+        println!("{:?}, part_no: {}", number, number.is_part_number());
+        if number.is_part_number() {
+            numbers_total += Into::<u64>::into(number.number);
+        }
+    }
+
+    for gear in gears.values() {
+        println!("{:?}, sum: {}", gear, gear.sum());
+        gears_total += gear.sum();
+    }
+
+    println!("{}", numbers_total);
+    println!("{}", gears_total);
+}