made a few improvements
This commit is contained in:
@@ -1 +1,2 @@
|
||||
/target
|
||||
state.txt
|
||||
|
||||
+62
-24
@@ -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
|
||||
|
||||
impl LetterCounts {
|
||||
fn add(&mut self, i : usize) {
|
||||
self.0[i] += 1;
|
||||
}
|
||||
|
||||
fn serialize_data(data: &[u32]) -> String {
|
||||
// todo: use serde?
|
||||
fn serialize(&self) -> String {
|
||||
println!("serializing...");
|
||||
let mut serialization = String::new();
|
||||
for (i, count) in data.iter().enumerate() {
|
||||
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;
|
||||
let s = format!("{} {}\n", c, count.to_string());
|
||||
serialization.push_str(&s)
|
||||
// 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
|
||||
}
|
||||
|
||||
fn deserialize(s: &str, data: &mut [u32]) {
|
||||
// todo: use serde?
|
||||
fn deserialize(&mut self, s: &str) -> Result<(), Vec<String>> {
|
||||
println!("deserializing...");
|
||||
// assumes no duplicate (and disgreeing) lines
|
||||
for line in s.split('\n') {
|
||||
|
||||
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 => {
|
||||
println!("ERROR: invalid line format, missing character");
|
||||
error_strings.push("ERROR: invalid line format, missing character.".to_string());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let index = (c as u32) - 97;
|
||||
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::<u32>() {
|
||||
Ok(i) => i,
|
||||
Err(_) => {
|
||||
println!("ERROR: invalid line format, missing count");
|
||||
Err(e) => {
|
||||
// todo: don't store strings, unnecessary allocation
|
||||
error_strings.push(format!("ERROR: invalid line format, missing count. ({})", e));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
data[index as usize] = count;
|
||||
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<R: Rng + ?Sized>(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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user