Trying to do advent of code in Rust. I am very new to rust so please help if you see me doing something stupid!!

added comments bc i am sure many people want to read this amazing feat of engineering

Signed-off-by: Business Goose <did:plc:hsqwcidfez66lwm3gxhfv5in>

Changed files
+41 -19
day5
src
+41 -19
day5/src/main.rs
··· 2 2 use std::fmt::Debug; 3 3 use std::{cmp, fs}; 4 4 5 + #[derive(Debug)] 5 6 struct Ingredient { 6 7 id: u64, 7 8 } 8 9 10 + // Describes a range of ingredient ids 9 11 struct IngredientRange { 10 12 start: Ingredient, 11 13 end: Ingredient, 12 14 } 13 15 16 + // Used to store and manage IngredientRanges 14 17 struct IngredientDB { 15 18 ranges: Vec<IngredientRange>, 16 19 } 17 20 18 - #[derive(Debug, Clone, Copy, Eq)] 21 + // #[derive(Debug, Clone, Copy, Eq)] 22 + // don't think i need to do this deriving stuff 23 + // i find it a bit scary tbh 19 24 enum RangeCap { 20 25 Start(u64), 21 26 End(u64), 22 27 } 23 28 29 + // If both elements have the same type and value, they are equal 24 30 impl cmp::PartialEq for RangeCap { 25 31 fn eq(&self, other: &Self) -> bool { 26 32 match (self, other) { ··· 31 37 } 32 38 } 33 39 40 + // Implementing an ordering for these Range Caps (better name? terminal?) 34 41 impl cmp::PartialOrd for RangeCap { 35 42 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { 36 43 match (self, other) { 44 + // if both values are Starts or both are Ends, compare values normally 37 45 (RangeCap::Start(a), RangeCap::Start(b)) => a.partial_cmp(b), 46 + (RangeCap::End(a), RangeCap::End(b)) => a.partial_cmp(b), 47 + // If they are equal, make sure Starts always go before Ends 38 48 (RangeCap::Start(a), RangeCap::End(b)) => { 39 49 let result = a.partial_cmp(b); 40 50 if result == Some(Ordering::Equal) { ··· 51 61 result 52 62 } 53 63 } 54 - (RangeCap::End(a), RangeCap::End(b)) => a.partial_cmp(b), 55 64 } 56 65 } 57 66 } 58 67 59 68 impl IngredientDB { 69 + // Initializing an IngredientDB is basically parsing an input string and initializing the ranges vector. 60 70 fn new(range_list: &str) -> IngredientDB { 61 71 let mut ranges = Vec::new(); 62 72 for l in range_list.lines() { ··· 73 83 IngredientDB { ranges } 74 84 } 75 85 86 + // We can check if an ingredient is fresh by comparing it to our ranges and seeing if it falls within any of them. 76 87 fn is_fresh(&self, ingredient: Ingredient) -> bool { 77 88 self.ranges 78 89 .iter() 79 90 .any(|range| range.start.id <= ingredient.id && ingredient.id <= range.end.id) 80 91 } 81 92 93 + // This function is called clean_ranges_stack bc i initially tried to do something else and it was a nightmare 94 + // Basically we initialize a list of RangeCaps, then sort them by value 95 + // Then we go through and push to the stack when we find a Start, pop when we find an End 96 + // If the end we find leaves our stack empty, we push a new range to our ranges vector 97 + // Finally, we replace the internal ranges of this IngredientDB with our new, optimized ranges. 82 98 fn clean_ranges_stack(&mut self) { 83 99 let mut range_list = Vec::<RangeCap>::new(); 84 100 for range in &self.ranges { 85 101 range_list.push(RangeCap::Start(range.start.id)); 86 102 range_list.push(RangeCap::End(range.end.id)); 87 103 } 88 - range_list.sort_by(|a, b| a.partial_cmp(b).unwrap()); 104 + // Sort range list by Partial Ordering 105 + // this shouldn't fail or something's wrong 106 + range_list.sort_by(|a, b| { 107 + a.partial_cmp(b) 108 + .expect("RangeCaps should be able to be compared") 109 + }); 89 110 111 + // initialize our stack and output range vectors 90 112 let mut range_stack = Vec::<u64>::new(); 91 113 let mut new_ranges = Vec::<IngredientRange>::new(); 92 114 93 115 for range in &range_list { 94 116 match *range { 117 + // always push Starts to the stack 95 118 RangeCap::Start(id) => range_stack.push(id), 96 119 RangeCap::End(id) => { 120 + // if stack has more than 1 element, pop the top and discard 121 + // otherwise construct an IngredientRange with the final element and push it to the new_ranges vector 97 122 if range_stack.len() > 1 { 98 123 range_stack.pop(); 99 124 } else { ··· 111 136 self.ranges = new_ranges; 112 137 } 113 138 139 + // Simple math function that subtracts ends of all the ranges from the starts and adds 1 to be **inclusive** 114 140 fn count_fresh(&self) -> u64 { 115 141 let mut fresh_count = 0u64; 116 142 for range in &self.ranges { 117 - if range.start.id == 0 && range.end.id == 0 { 118 - continue; 119 - } 120 143 fresh_count += range.end.id.saturating_sub(range.start.id) + 1; 121 144 } 122 145 fresh_count 123 146 } 124 147 } 125 148 126 - impl Debug for Ingredient { 127 - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 128 - write!(f, "Ingredient {{ id: {} }}", self.id) 129 - } 130 - } 131 - 132 149 impl Debug for IngredientRange { 133 150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 134 151 write!( ··· 149 166 150 167 fn main() { 151 168 let in_file = fs::read_to_string("input.txt").expect("Failed to read file"); 152 - let file_sections = in_file.split("\n\n").collect::<Vec<&str>>(); 153 169 170 + // Split our input file into two parts based on newlines and make sure there are exactly 2 parts 171 + let file_sections = in_file.split("\n\n").collect::<Vec<&str>>(); 154 172 assert!(file_sections.len() == 2); 155 173 174 + // Parse first part of the file and initialize an IngredientDB 156 175 let mut ingredient_db = IngredientDB::new(file_sections[0]); 157 - let mut fresh_count = 0u64; 158 176 177 + // before range cleanup 178 + // println!("Ingredient Database: {:?}", ingredient_db); 179 + // cleaning our ranges up here gives us better performance for the first part! since there are less ranges to check in. 180 + ingredient_db.clean_ranges_stack(); 181 + // after range cleanup 182 + // println!("Ingredient Database: {:?}", ingredient_db); 183 + 184 + // to get our first star, we check each ingredient in the second section against our initialized DB and increment fresh_count 185 + let mut fresh_count = 0u64; 159 186 for test_ingredient in file_sections[1].lines() { 160 187 if ingredient_db.is_fresh(Ingredient { 161 188 id: test_ingredient ··· 166 193 } 167 194 } 168 195 169 - // before range cleanup 170 - // println!("Ingredient Database: {:?}", ingredient_db); 171 - ingredient_db.clean_ranges_stack(); 172 - // after range cleanup 173 - // println!("Ingredient Database: {:?}", ingredient_db); 174 196 println!("Fresh Ingredients in Pantry: {}", fresh_count); 175 197 println!( 176 198 "Fresh Ingredients in Database: {}",