diff options
Diffstat (limited to 'srchr/src')
| -rw-r--r-- | srchr/src/config.rs | 47 | ||||
| -rw-r--r-- | srchr/src/corpus.rs | 123 | ||||
| -rw-r--r-- | srchr/src/evaluation.rs | 172 | ||||
| -rw-r--r-- | srchr/src/layout.rs | 156 | ||||
| -rw-r--r-- | srchr/src/main.rs | 23 | ||||
| -rw-r--r-- | srchr/src/output.rs | 51 |
6 files changed, 298 insertions, 274 deletions
diff --git a/srchr/src/config.rs b/srchr/src/config.rs new file mode 100644 index 0000000..99afa4c --- /dev/null +++ b/srchr/src/config.rs @@ -0,0 +1,47 @@ +// Search parameters +pub const CORPUS_FILE_NAME: &str = "corpus.txt"; + +pub const STARTING_LAYOUT_STRING: &str = " +Q W E R T Y U I O P +A S D F G H J K L ' +Z X C V B N M , . / +"; + +pub const NUM_CONTESTANTS: usize = 128; +pub const NUM_PERSIST: usize = 8; + +pub const DESIRED_INDEX_USAGE_PERCENT: f32 = 0.15; +pub const fn index_usage_fitness(index_threshold: u32, index_count: u32) -> u32 { + (index_threshold as i64 - index_count as i64).abs() as u32 / 64 +} + +// Do not change, code makes assumptions about these +pub const NUM_KEYS: usize = 30; +pub const ROW_LENGTH: usize = 10; + +pub const KEY_CHARS: [char; NUM_KEYS] = [ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '/', '.', ',', '\'', +]; + +pub const KEY_TO_FINGER: [usize; NUM_KEYS] = [ + 0, 1, 2, 3, 3, 4, 4, 5, 6, 7, 0, 1, 2, 3, 3, 4, 4, 5, 6, 7, 0, 1, 2, 3, 3, 4, 4, 5, 6, 7, +]; + +pub fn canonicalise(inp: char) -> Option<char> { + if inp.is_ascii_alphabetic() { + return Some(inp.to_ascii_uppercase()); + } else { + match inp { + '.' => Some('.'), + '>' => Some('.'), + ',' => Some(','), + '<' => Some(','), + '/' => Some('/'), + '?' => Some('/'), + '\'' => Some('\''), + '"' => Some('\''), + _ => None, + } + } +} diff --git a/srchr/src/corpus.rs b/srchr/src/corpus.rs index 8e4b437..eecb122 100644 --- a/srchr/src/corpus.rs +++ b/srchr/src/corpus.rs @@ -1,7 +1,7 @@ use std::fmt; use std::fs; -use crate::layout::*; +use crate::config::*; const fn build_lookup_table() -> [usize; 128] { let mut result = [0; 128]; @@ -34,6 +34,18 @@ impl Corpus { self.character_count[c as usize] } + pub fn get_bigram_count(&self, x: char, y: char) -> u32 { + self.bigram_count[pair_to_index(x, y)] + } + + pub fn get_index_threshold(&self) -> u32 { + self.index_threshold + } + + pub fn get_total_count(&self) -> u32 { + self.total_count + } + pub fn load(path: &str) -> Result<Corpus, std::io::Error> { let contents = fs::read_to_string(path)?; @@ -59,7 +71,7 @@ impl Corpus { } } - let index_threshold = (total_count as f32 * 0.15) as u32; + let index_threshold = (total_count as f32 * DESIRED_INDEX_USAGE_PERCENT) as u32; return Ok(Corpus { bigram_count, @@ -68,113 +80,6 @@ impl Corpus { index_threshold, }); } - - pub fn prelayout_fitness(&self, layout: &Prelayout) -> u32 { - let mut score: u32 = 0; - - for i in 0..6 { - let keys = layout.get_standard_column(i); - let k1 = keys[0]; - let k2 = keys[1]; - let k3 = keys[2]; - - score += self.bigram_count[pair_to_index(k1, k2)] - + self.bigram_count[pair_to_index(k1, k3)] - + self.bigram_count[pair_to_index(k2, k3)]; - } - - for i in 0..2 { - let keys = layout.get_index_column(i); - let k1 = keys[0]; - let k2 = keys[1]; - let k3 = keys[2]; - let k4 = keys[3]; - let k5 = keys[4]; - let k6 = keys[5]; - - // We want index usage! - let index_count: u32 = keys.iter().map(|&k| self.character_count[k as usize]).sum(); - score += (self.index_threshold as i64 - index_count as i64).abs() as u32 / 64; - - score += self.bigram_count[pair_to_index(k1, k2)] - + self.bigram_count[pair_to_index(k1, k3)] - + self.bigram_count[pair_to_index(k2, k3)] - + self.bigram_count[pair_to_index(k4, k5)] - + self.bigram_count[pair_to_index(k4, k6)] - + self.bigram_count[pair_to_index(k5, k6)] - + self.bigram_count[pair_to_index(k1, k4)] - + self.bigram_count[pair_to_index(k1, k5)] - + self.bigram_count[pair_to_index(k1, k6)] - + self.bigram_count[pair_to_index(k2, k4)] - + self.bigram_count[pair_to_index(k2, k5)] - + self.bigram_count[pair_to_index(k2, k6)] - + self.bigram_count[pair_to_index(k3, k4)] - + self.bigram_count[pair_to_index(k3, k5)] - + self.bigram_count[pair_to_index(k3, k6)]; - } - - score - } - - pub fn evaluate_layout(&self, layout: &Layout) -> Evaluation { - let mut keypress: [u32; NUM_KEYS] = [0; NUM_KEYS]; - let mut sfb: [u32; 8] = [0; 8]; - - for (i, &c) in self.character_count.iter().enumerate() { - if c > 0 { - keypress[layout.get_index((i as u8) as char)] = c; - } - } - - // TODO: We make assumptions about NUM_KEYS here - for i in 0..8 { - let ind = if i < 4 { i } else { i + 2 }; - - let k1 = layout.get_key(ind + 10 * 0); - let k2 = layout.get_key(ind + 10 * 1); - let k3 = layout.get_key(ind + 10 * 2); - - sfb[i] = self.bigram_count[pair_to_index(k1, k2)] - + self.bigram_count[pair_to_index(k1, k3)] - + self.bigram_count[pair_to_index(k2, k3)]; - - if i == 3 { - let k4 = layout.get_key(4 + 10 * 0); - let k5 = layout.get_key(4 + 10 * 1); - let k6 = layout.get_key(4 + 10 * 2); - sfb[i] += self.bigram_count[pair_to_index(k4, k5)] - + self.bigram_count[pair_to_index(k4, k6)] - + self.bigram_count[pair_to_index(k5, k6)] - + self.bigram_count[pair_to_index(k1, k4)] - + self.bigram_count[pair_to_index(k1, k5)] - + self.bigram_count[pair_to_index(k1, k6)] - + self.bigram_count[pair_to_index(k2, k4)] - + self.bigram_count[pair_to_index(k2, k5)] - + self.bigram_count[pair_to_index(k2, k6)] - + self.bigram_count[pair_to_index(k3, k4)] - + self.bigram_count[pair_to_index(k3, k5)] - + self.bigram_count[pair_to_index(k3, k6)]; - } else if i == 4 { - let k4 = layout.get_key(5 + 10 * 0); - let k5 = layout.get_key(5 + 10 * 1); - let k6 = layout.get_key(5 + 10 * 2); - sfb[i] += self.bigram_count[pair_to_index(k4, k5)] - + self.bigram_count[pair_to_index(k5, k6)] - + self.bigram_count[pair_to_index(k4, k6)] - + self.bigram_count[pair_to_index(k1, k4)] - + self.bigram_count[pair_to_index(k1, k5)] - + self.bigram_count[pair_to_index(k1, k6)] - + self.bigram_count[pair_to_index(k2, k4)] - + self.bigram_count[pair_to_index(k2, k5)] - + self.bigram_count[pair_to_index(k2, k6)] - + self.bigram_count[pair_to_index(k3, k4)] - + self.bigram_count[pair_to_index(k3, k5)] - + self.bigram_count[pair_to_index(k3, k6)]; - } - } - - return Evaluation::new(keypress, self.total_count, sfb); - } } fn dump_bigrams(corpus: &Corpus) -> Vec<(String, u32)> { diff --git a/srchr/src/evaluation.rs b/srchr/src/evaluation.rs new file mode 100644 index 0000000..9faeb88 --- /dev/null +++ b/srchr/src/evaluation.rs @@ -0,0 +1,172 @@ +use crate::config::*; +use crate::corpus::*; +use crate::layout::*; +use crate::output::*; + +use std::fmt; + +pub fn prelayout_fitness(corpus: &Corpus, layout: &Prelayout) -> u32 { + let mut score: u32 = 0; + + for i in 0..6 { + let keys = layout.get_standard_column(i); + let k1 = keys[0]; + let k2 = keys[1]; + let k3 = keys[2]; + + score += corpus.get_bigram_count(k1, k2) + + corpus.get_bigram_count(k1, k3) + + corpus.get_bigram_count(k2, k3); + } + + for i in 0..2 { + let keys = layout.get_index_column(i); + let k1 = keys[0]; + let k2 = keys[1]; + let k3 = keys[2]; + let k4 = keys[3]; + let k5 = keys[4]; + let k6 = keys[5]; + + // We want index usage! + let index_count: u32 = keys.iter().map(|&k| corpus.get_character_count(k)).sum(); + score += index_usage_fitness(corpus.get_index_threshold(), index_count); + + score += corpus.get_bigram_count(k1, k2) + + corpus.get_bigram_count(k1, k3) + + corpus.get_bigram_count(k2, k3) + + corpus.get_bigram_count(k4, k5) + + corpus.get_bigram_count(k4, k6) + + corpus.get_bigram_count(k5, k6) + + corpus.get_bigram_count(k1, k4) + + corpus.get_bigram_count(k1, k5) + + corpus.get_bigram_count(k1, k6) + + corpus.get_bigram_count(k2, k4) + + corpus.get_bigram_count(k2, k5) + + corpus.get_bigram_count(k2, k6) + + corpus.get_bigram_count(k3, k4) + + corpus.get_bigram_count(k3, k5) + + corpus.get_bigram_count(k3, k6); + } + + score +} + +#[derive(Copy, Clone)] +pub struct Evaluation { + keypress: [u32; NUM_KEYS], + total_keypress: u32, + sfb: [u32; 8], +} + +impl Evaluation { + pub fn evaluate_layout(corpus: &Corpus, layout: &Layout) -> Evaluation { + let mut keypress: [u32; NUM_KEYS] = [0; NUM_KEYS]; + let mut sfb: [u32; 8] = [0; 8]; + + for (i, &c) in layout.get_keys().iter().enumerate() { + keypress[i] = corpus.get_character_count(c); + } + + // TODO: We make assumptions about NUM_KEYS here + for i in 0..8 { + let ind = if i < 4 { i } else { i + 2 }; + + let k1 = layout.get_key(ind + 10 * 0); + let k2 = layout.get_key(ind + 10 * 1); + let k3 = layout.get_key(ind + 10 * 2); + + sfb[i] = corpus.get_bigram_count(k1, k2) + + corpus.get_bigram_count(k1, k3) + + corpus.get_bigram_count(k2, k3); + + if i == 3 { + let k4 = layout.get_key(4 + 10 * 0); + let k5 = layout.get_key(4 + 10 * 1); + let k6 = layout.get_key(4 + 10 * 2); + sfb[i] += corpus.get_bigram_count(k4, k5) + + corpus.get_bigram_count(k4, k6) + + corpus.get_bigram_count(k5, k6) + + corpus.get_bigram_count(k1, k4) + + corpus.get_bigram_count(k1, k5) + + corpus.get_bigram_count(k1, k6) + + corpus.get_bigram_count(k2, k4) + + corpus.get_bigram_count(k2, k5) + + corpus.get_bigram_count(k2, k6) + + corpus.get_bigram_count(k3, k4) + + corpus.get_bigram_count(k3, k5) + + corpus.get_bigram_count(k3, k6); + } else if i == 4 { + let k4 = layout.get_key(5 + 10 * 0); + let k5 = layout.get_key(5 + 10 * 1); + let k6 = layout.get_key(5 + 10 * 2); + sfb[i] += corpus.get_bigram_count(k4, k5) + + corpus.get_bigram_count(k5, k6) + + corpus.get_bigram_count(k4, k6) + + corpus.get_bigram_count(k1, k4) + + corpus.get_bigram_count(k1, k5) + + corpus.get_bigram_count(k1, k6) + + corpus.get_bigram_count(k2, k4) + + corpus.get_bigram_count(k2, k5) + + corpus.get_bigram_count(k2, k6) + + corpus.get_bigram_count(k3, k4) + + corpus.get_bigram_count(k3, k5) + + corpus.get_bigram_count(k3, k6); + } + } + + return Evaluation { + keypress, + total_keypress: corpus.get_total_count(), + sfb, + }; + } + + fn output_eval(&self) -> String { + let mut result = String::new(); + let tot = self.total_keypress as f32; + + result += &"Percent per key:\n"; + result += &format_block_output(self.keypress.into_iter().map(|k| 100.0 * k as f32 / tot)); + + let mut finger_usages = [0; 8]; + for (i, &c) in self.keypress.iter().enumerate() { + finger_usages[KEY_TO_FINGER[i]] += c; + } + + result += "\nFinger usage: "; + let mut lh: f32 = 0.0; + let mut rh: f32 = 0.0; + for (i, &u) in finger_usages.iter().enumerate() { + let f = u as f32 / tot * 100.0; + result += &format!("{:>5.2}%{}", f, if i < 7 { ", " } else { "" }); + if i % 10 < 4 { + lh += f; + } else { + rh += f; + } + } + + result += &format!("\nHand usage: {:.2}% vs {:.2}%", lh, rh); + + result += "\nSame finger bigrams: "; + for (i, &u) in self.sfb.iter().enumerate() { + result += &format!( + "{:>6.3}%{}", + u as f32 / tot * 100.0, + if i < 7 { ", " } else { "" } + ); + } + + let sfb = self.sfb.iter().sum::<u32>(); + result += &format!("\nTotal sfb: {:.2}% ({})", sfb as f32 / tot * 100.0, sfb); + + return result; + } +} + +impl fmt::Display for Evaluation { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(&self.output_eval()) + } +} diff --git a/srchr/src/layout.rs b/srchr/src/layout.rs index 9662b84..d30e38b 100644 --- a/srchr/src/layout.rs +++ b/srchr/src/layout.rs @@ -1,34 +1,9 @@ +use crate::config::*; use crate::corpus::*; - +use crate::output::*; use rand::prelude::*; use std::fmt; -pub const NUM_KEYS: usize = 30; -pub const ROW_LENGTH: usize = 10; - -pub const KEY_CHARS: [char; NUM_KEYS] = [ - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '/', '.', ',', '\'', -]; - -pub fn canonicalise(inp: char) -> Option<char> { - if inp.is_ascii_alphabetic() { - return Some(inp.to_ascii_uppercase()); - } else { - match inp { - '.' => Some('.'), - '>' => Some('.'), - ',' => Some(','), - '<' => Some(','), - '/' => Some('/'), - '?' => Some('/'), - '\'' => Some('\''), - '"' => Some('\''), - _ => None, - } - } -} - #[derive(Copy, Clone)] pub struct Prelayout { standard_columns: [[char; 3]; 6], @@ -123,10 +98,6 @@ impl Prelayout { } } -const KEY_TO_FINGER: [usize; NUM_KEYS] = [ - 0, 1, 2, 3, 3, 4, 4, 5, 6, 7, 0, 1, 2, 3, 3, 4, 4, 5, 6, 7, 0, 1, 2, 3, 3, 4, 4, 5, 6, 7, -]; - #[derive(Copy, Clone)] pub struct Layout { keys: [char; NUM_KEYS], @@ -202,12 +173,8 @@ impl Layout { Layout { keys } } - pub fn get_index(&self, c: char) -> usize { - let mut found_key = 0; - while self.keys[found_key] != c { - found_key += 1 - } - found_key + pub fn get_keys(&self) -> &[char] { + &self.keys } fn char_array_to_layout(keys: [char; NUM_KEYS]) -> Layout { @@ -246,118 +213,3 @@ impl fmt::Display for Layout { formatter.write_str(&format_block_output(self.keys.into_iter())) } } - -#[derive(Copy, Clone)] -pub struct Evaluation { - keypress: [u32; NUM_KEYS], - total_keypress: u32, - sfb: [u32; 8], -} - -impl Evaluation { - pub fn new(keypress: [u32; NUM_KEYS], total_keypress: u32, sfb: [u32; 8]) -> Evaluation { - Evaluation { - keypress, - sfb, - total_keypress, - } - } - - fn output_eval(&self) -> String { - let mut result = String::new(); - let tot = self.total_keypress as f32; - - result += &"Percent per key:\n"; - result += &format_block_output(self.keypress.into_iter().map(|k| 100.0 * k as f32 / tot)); - - let mut finger_usages = [0; 8]; - for (i, &c) in self.keypress.iter().enumerate() { - finger_usages[KEY_TO_FINGER[i]] += c; - } - - result += "\nFinger usage: "; - let mut lh: f32 = 0.0; - let mut rh: f32 = 0.0; - for (i, &u) in finger_usages.iter().enumerate() { - let f = u as f32 / tot * 100.0; - result += &format!("{:>5.2}%{}", f, if i < 7 { ", " } else { "" }); - if i % 10 < 4 { - lh += f; - } else { - rh += f; - } - } - - result += &format!("\nHand usage: {:.2}% vs {:.2}%", lh, rh); - - result += "\nSame finger bigrams: "; - for (i, &u) in self.sfb.iter().enumerate() { - result += &format!( - "{:>6.3}%{}", - u as f32 / tot * 100.0, - if i < 7 { ", " } else { "" } - ); - } - - let sfb = self.sfb.iter().sum::<u32>(); - result += &format!("\nTotal sfb: {:.2}% ({})", sfb as f32 / tot * 100.0, sfb); - - return result; - } -} - -impl fmt::Display for Evaluation { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str(&self.output_eval()) - } -} - -// This is a silly amount of work to genericise the below... -trait ToMyString { - const BLANK_STRING: &'static str; - fn to_my_string(&self) -> String; -} - -impl ToMyString for char { - const BLANK_STRING: &'static str = " "; - fn to_my_string(&self) -> String { - self.to_string() - } -} - -impl ToMyString for u32 { - const BLANK_STRING: &'static str = " "; - fn to_my_string(&self) -> String { - format!("{:5}", *self) - } -} - -impl ToMyString for f32 { - const BLANK_STRING: &'static str = " "; - fn to_my_string(&self) -> String { - format!("{:>4.1}", *self) - } -} - -fn format_block_output<T: Iterator<Item = S>, S: ToMyString>(things: T) -> String { - let mut result = String::new(); - for (i, t) in things.enumerate() { - result += &t.to_my_string(); - if i < NUM_KEYS - 1 { - result.push(' '); - } - if (i + 1) % 10 == 0 { - result.push('\n'); - if NUM_KEYS < 30 && i == 19 { - result += S::BLANK_STRING; - result.push(' ') - } - } else if (i < 20 && (i + 1) % 5 == 0) || (i == (NUM_KEYS - 20) / 2 + 19) { - result.push(' '); - } - if i + 1 >= NUM_KEYS { - break; - } - } - return result; -} diff --git a/srchr/src/main.rs b/srchr/src/main.rs index 2762646..46ec5d6 100644 --- a/srchr/src/main.rs +++ b/srchr/src/main.rs @@ -1,24 +1,21 @@ +mod config; mod corpus; +mod evaluation; mod layout; +mod output; use rayon::prelude::*; use rand::prelude::*; use rand_pcg::*; +use config::*; use corpus::*; +use evaluation::*; use layout::*; use std::time::Instant; -const NUM_CONTESTANTS: usize = 128; -const NUM_PERSIST: usize = 8; - -// TODO: command line arguments to start at a given layout string, maybe read -// from file? Might as well make num_contestants and survive_threshold -// configurable, and number of swaps when generating new layout, and index -// finger threshold. - struct Tournament<'a> { rngs: Vec<Pcg64>, top_prelayouts: Vec<(Prelayout, u32)>, @@ -33,7 +30,7 @@ impl<'a> Tournament<'a> { } let mut top_prelayouts: Vec<(Prelayout, u32)> = Vec::new(); - let score = corpus.prelayout_fitness(&seed_prelayout); + let score = prelayout_fitness(&corpus, &seed_prelayout); for _ in 0..NUM_PERSIST { top_prelayouts.push((seed_prelayout.clone(), score)); } @@ -55,7 +52,7 @@ impl<'a> Tournament<'a> { &self.top_prelayouts[rng.gen_range(0..NUM_PERSIST)].0, &mut rng, ); - return (layout, self.corpus.prelayout_fitness(&layout)); + return (layout, prelayout_fitness(&self.corpus, &layout)); }) .collect::<Vec<(Prelayout, u32)>>(); @@ -79,10 +76,10 @@ impl<'a> Tournament<'a> { } fn main() { - let corpus = Corpus::load("books.txt").unwrap(); + let corpus = Corpus::load(CORPUS_FILE_NAME).unwrap(); println!("{}", corpus); - let seed_prelayout = Layout::from_verbose("QWERTYUIOPASDFGHJKL'ZXCVBNM,./") + let seed_prelayout = Layout::from_verbose(STARTING_LAYOUT_STRING) .unwrap() .as_prelayout(); @@ -95,7 +92,7 @@ fn main() { if let Some(prelayout) = improvement { let layout = Layout::from_prelayout(&prelayout, &corpus); - let evl = corpus.evaluate_layout(&layout); + let evl = Evaluation::evaluate_layout(&corpus, &layout); println!(""); println!("================================================================================\n\n{}", layout diff --git a/srchr/src/output.rs b/srchr/src/output.rs new file mode 100644 index 0000000..b5af9e7 --- /dev/null +++ b/srchr/src/output.rs @@ -0,0 +1,51 @@ +use crate::config::*; + +// This is a silly amount of work to genericise the below... +pub trait ToMyString { + const BLANK_STRING: &'static str; + fn to_my_string(&self) -> String; +} + +impl ToMyString for char { + const BLANK_STRING: &'static str = " "; + fn to_my_string(&self) -> String { + self.to_string() + } +} + +impl ToMyString for u32 { + const BLANK_STRING: &'static str = " "; + fn to_my_string(&self) -> String { + format!("{:5}", *self) + } +} + +impl ToMyString for f32 { + const BLANK_STRING: &'static str = " "; + fn to_my_string(&self) -> String { + format!("{:>4.1}", *self) + } +} + +pub fn format_block_output<T: Iterator<Item = S>, S: ToMyString>(things: T) -> String { + let mut result = String::new(); + for (i, t) in things.enumerate() { + result += &t.to_my_string(); + if i < NUM_KEYS - 1 { + result.push(' '); + } + if (i + 1) % 10 == 0 { + result.push('\n'); + if NUM_KEYS < 30 && i == 19 { + result += S::BLANK_STRING; + result.push(' ') + } + } else if (i < 20 && (i + 1) % 5 == 0) || (i == (NUM_KEYS - 20) / 2 + 19) { + result.push(' '); + } + if i + 1 >= NUM_KEYS { + break; + } + } + return result; +} |
