made a few improvements

This commit is contained in:
2026-04-26 22:09:38 -04:00
parent 4eccf5a9ec
commit 2fe8f1a287
3 changed files with 102 additions and 63 deletions
+1
View File
@@ -1 +1,2 @@
/target /target
state.txt
+81 -43
View File
@@ -1,73 +1,111 @@
use std::fs; use std::fs;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use rand::Rng; 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 { struct LetterCounts([u32; 26]); // tuple
rng.gen_range(0..25)
}
fn serialize_data(data: &[u32]) -> String { impl LetterCounts {
println!("serializing..."); fn add(&mut self, i : usize) {
let mut serialization = String::new(); self.0[i] += 1;
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)
} }
// only stack object (ptr, len, capacity) is copied on the way out
serialization
}
fn deserialize(s: &str, data: &mut [u32]) { // todo: use serde?
println!("deserializing..."); fn serialize(&self) -> String {
// assumes no duplicate (and disgreeing) lines println!("serializing...");
for line in s.split('\n') { let mut serialization = String::with_capacity(26*4); // pre-allocate
if let Some((letter, val)) = line.split_once(' ') { for (i, count) in self.0.iter().enumerate() {
let c = match letter.chars().next() { let c = (i as u8 + 97) as char;
Some(c) => c, // note that we write directly to serialization without unnecessary allocation
None => { // writeln expands to use format_args,
println!("ERROR: invalid line format, missing character"); // which uses a small struct (stack-only) to track the inputs before
continue; // 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 index = (c as u32) - 97; let _ = writeln!(serialization, "{} {}", c, count);
let count = match val.parse::<u32>() {
Ok(i) => i,
Err(_) => {
println!("ERROR: invalid line format, missing count");
continue;
}
};
data[index as usize] = 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<String>> {
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::<u32>() {
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<R: Rng + ?Sized>(rng: &mut R) -> usize {
rng.gen_range(0..26)
} }
fn main() { fn main() {
// considered using mutable static array for data // considered using mutable static array for data
// but that requires unsafe, so we just use non-static memory // 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 // deserialize file contents into data structure
if let Ok(contents) = fs::read_to_string(FILENAME) { 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 { } else {
println!("ERROR: failed to read file"); eprintln!("ERROR: failed to read file");
} }
// generate random char and give to data structure // generate random char and give to data structure
let mut rng = OsRng; let mut rng = OsRng;
let index = random_index(&mut rng); let index = random_index(&mut rng);
data[index] += 1; data.add(index);
println!("generated new char {}", (index + 97) as u8 as char); 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 // write data structure to file
let s_data = serialize_data(&data); let s_data = data.serialize();
println!("writing..."); println!("writing...");
if let Err(_) = fs::write(FILENAME, s_data) { // if write fails we may corrupt the original file,
println!("ERROR: Couldn't write to {}", FILENAME); // 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; return;
} }
} }
+20 -20
View File
@@ -1,26 +1,26 @@
a 1 a 0
b 2 b 3
c 1 c 2
d 0 d 1
e 1 e 2
f 2 f 3
g 2 g 3
h 1 h 3
i 1 i 1
j 0 j 0
k 1 k 1
l 1 l 2
m 4 m 5
n 2 n 2
o 3 o 4
p 1 p 3
q 3 q 4
r 1 r 2
s 2 s 3
t 3 t 4
u 0 u 1
v 1 v 1
w 4 w 6
x 0 x 0
y 1 y 2
z 0 z 1