diff --git a/.gitignore b/.gitignore index ea8c4bf..2fd3ecd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +state.txt diff --git a/src/main.rs b/src/main.rs index 600f762..b77fb4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,73 +1,111 @@ use std::fs; use rand::rngs::OsRng; use rand::Rng; +use std::fmt::Write; -const FILENAME : &'static str = "state.txt"; +const FILENAME : &str = "state.txt"; // 'static is implied, due to literal +const FILENAME_WIP : &str = "state.wip"; -fn random_index(rng: &mut OsRng) -> usize { - rng.gen_range(0..25) -} +struct LetterCounts([u32; 26]); // tuple -fn serialize_data(data: &[u32]) -> String { - println!("serializing..."); - let mut serialization = String::new(); - for (i, count) in data.iter().enumerate() { - let c = (i as u8 + 97) as char; - let s = format!("{} {}\n", c, count.to_string()); - serialization.push_str(&s) +impl LetterCounts { + fn add(&mut self, i : usize) { + self.0[i] += 1; } - // only stack object (ptr, len, capacity) is copied on the way out - serialization -} -fn deserialize(s: &str, data: &mut [u32]) { - println!("deserializing..."); - // assumes no duplicate (and disgreeing) lines - for line in s.split('\n') { - if let Some((letter, val)) = line.split_once(' ') { - let c = match letter.chars().next() { - Some(c) => c, - None => { - println!("ERROR: invalid line format, missing character"); - continue; - } - }; - let index = (c as u32) - 97; - let count = match val.parse::() { - Ok(i) => i, - Err(_) => { - println!("ERROR: invalid line format, missing count"); - continue; - } - }; - data[index as usize] = count; + // todo: use serde? + fn serialize(&self) -> String { + println!("serializing..."); + let mut serialization = String::with_capacity(26*4); // pre-allocate + for (i, count) in self.0.iter().enumerate() { + let c = (i as u8 + 97) as char; + // note that we write directly to serialization without unnecessary allocation + // writeln expands to use format_args, + // which uses a small struct (stack-only) to track the inputs before + // they are fed into the target String. + // So we've saved not only the copying itself but the overhead of also + // the use of malloc, which MAY do a syscall to request more memory + let _ = writeln!(serialization, "{} {}", c, count); } + // only stack object (ptr, len, capacity) is copied on the way out + serialization } + + // todo: use serde? + fn deserialize(&mut self, s: &str) -> Result<(), Vec> { + println!("deserializing..."); + + let mut error_strings = Vec::new(); + // todo: don't just assume no duplicate (and disagreeing) lines + for line in s.lines() { + if let Some((letter, val)) = line.split_once(' ') { + let c = match letter.chars().next() { + Some(c) => c, + None => { + error_strings.push("ERROR: invalid line format, missing character.".to_string()); + continue; + } + }; + if ('a'..='z').contains(&c) { // protect against non-lower alpha chars + let index = (c as u8 - b'a') as usize; + let count = match val.parse::() { + Ok(i) => i, + Err(e) => { + // todo: don't store strings, unnecessary allocation + error_strings.push(format!("ERROR: invalid line format, missing count. ({})", e)); + continue; + } + }; + self.0[index] = count; + } else { + error_strings.push("ERROR: encountered non-lowercase alphabetic char".to_string()); + continue; // we just drop the char/line + } + } + } + if !error_strings.is_empty() { + return Err(error_strings); + } + Ok(()) + } +} + +fn random_index(rng: &mut R) -> usize { + rng.gen_range(0..26) } fn main() { // considered using mutable static array for data // but that requires unsafe, so we just use non-static memory - let mut data : [u32; 26] = [0; 26]; + let mut data = LetterCounts([0; 26]); // deserialize file contents into data structure if let Ok(contents) = fs::read_to_string(FILENAME) { - deserialize(&contents, &mut data); + if let Err(v) = data.deserialize(&contents) { + v.iter().for_each(|s| eprintln!("{}",s)); + } } else { - println!("ERROR: failed to read file"); + eprintln!("ERROR: failed to read file"); } // generate random char and give to data structure let mut rng = OsRng; let index = random_index(&mut rng); - data[index] += 1; - println!("generated new char {}", (index + 97) as u8 as char); + data.add(index); + println!("generated new char {}", (index as u8 + b'a') as char); + // todo: way to only update a single line at a time? // write data structure to file - let s_data = serialize_data(&data); + let s_data = data.serialize(); println!("writing..."); - if let Err(_) = fs::write(FILENAME, s_data) { - println!("ERROR: Couldn't write to {}", FILENAME); + // if write fails we may corrupt the original file, + // so use a temp file and then rename + if let Err(e) = fs::write(FILENAME_WIP, s_data) { + eprintln!("ERROR: Couldn't write to {} ({})", FILENAME_WIP, e); + return; + } + if let Err(e) = fs::rename(FILENAME_WIP, FILENAME) { + eprintln!("ERROR: Couldn't replace {} with {} ({})", FILENAME, FILENAME_WIP, e); return; } } diff --git a/state.txt b/state.txt index f2b330a..cd8f02c 100644 --- a/state.txt +++ b/state.txt @@ -1,26 +1,26 @@ -a 1 -b 2 -c 1 -d 0 -e 1 -f 2 -g 2 -h 1 +a 0 +b 3 +c 2 +d 1 +e 2 +f 3 +g 3 +h 3 i 1 j 0 k 1 -l 1 -m 4 +l 2 +m 5 n 2 -o 3 -p 1 -q 3 -r 1 -s 2 -t 3 -u 0 +o 4 +p 3 +q 4 +r 2 +s 3 +t 4 +u 1 v 1 -w 4 +w 6 x 0 -y 1 -z 0 +y 2 +z 1