From c4e7a97fd7efecdb85f362f04434ea8470a1927f Mon Sep 17 00:00:00 2001 From: tslil clingman Date: Tue, 27 Sep 2022 23:06:16 +0200 Subject: Better pre-layout -> layout --- src/config.rs | 43 -------------- src/corpus.rs | 18 +++++- src/layout.rs | 186 ++++++++++++++++++++++++++++++++++------------------------ src/main.rs | 12 ++-- 4 files changed, 129 insertions(+), 130 deletions(-) delete mode 100644 src/config.rs (limited to 'src') diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index bc86613..0000000 --- a/src/config.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Do not change -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 { - if inp.is_ascii_alphabetic() { - return Some(inp.to_ascii_uppercase()); - } else { - match inp { - '.' => Some('.'), - '>' => Some('.'), - ',' => Some(','), - '<' => Some(','), - '/' => Some('/'), - '?' => Some('/'), - '\'' => Some('\''), - '"' => Some('\''), - _ => None, - } - } -} - -const fn build_lookup_table() -> [usize; 128] { - let mut result = [0; 128]; - let mut i = 0; - - while i < NUM_KEYS { - result[KEY_CHARS[i] as usize] = i; - i += 1; - } - return result; -} - -pub const CHAR_TO_INDEX: [usize; 128] = build_lookup_table(); diff --git a/src/corpus.rs b/src/corpus.rs index 9b522e0..e1c903b 100644 --- a/src/corpus.rs +++ b/src/corpus.rs @@ -1,11 +1,21 @@ use std::fmt; use std::fs; -use crate::config::*; use crate::layout::*; -const NUM_BIGRAMS: usize = NUM_KEYS * NUM_KEYS; +const fn build_lookup_table() -> [usize; 128] { + let mut result = [0; 128]; + let mut i = 0; + + while i < NUM_KEYS { + result[KEY_CHARS[i] as usize] = i; + i += 1; + } + return result; +} +const CHAR_TO_INDEX: [usize; 128] = build_lookup_table(); +const NUM_BIGRAMS: usize = NUM_KEYS * NUM_KEYS; pub struct Corpus { bigram_count: [u32; NUM_BIGRAMS], character_count: [u32; 256], @@ -20,6 +30,10 @@ fn pair_to_index(x: char, y: char) -> usize { } impl Corpus { + pub fn get_character_count(&self, c: char) -> u32 { + self.character_count[c as usize] + } + pub fn load(path: &str) -> Result { let contents = fs::read_to_string(path)?; diff --git a/src/layout.rs b/src/layout.rs index bc476c8..9662b84 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,8 +1,34 @@ -use crate::config::*; +use crate::corpus::*; 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 { + 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], @@ -95,28 +121,12 @@ impl Prelayout { index_columns, } } - - fn to_char_array(&self) -> [char; NUM_KEYS] { - let mut result = ['x'; NUM_KEYS]; - - for i in 0..6 { - let ind = if i < 3 { i } else { i + 4 }; - for j in 0..3 { - result[ind + j * ROW_LENGTH] = self.standard_columns[i][j]; - } - } - - for j in 0..3 { - result[3 + j * ROW_LENGTH] = self.index_columns[0][j]; - result[6 + j * ROW_LENGTH] = self.index_columns[1][j]; - result[4 + j * ROW_LENGTH] = self.index_columns[0][j + 3]; - result[5 + j * ROW_LENGTH] = self.index_columns[1][j + 3]; - } - - result - } } +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], @@ -127,9 +137,69 @@ impl Layout { Prelayout::from_char_array(&self.keys) } - pub fn from_prelayout(pl: &Prelayout) -> Layout { - // TODO: something more clever about permuting colums, keys within columns etc - Layout::char_array_to_layout(pl.to_char_array()) + pub fn from_prelayout(pl: &Prelayout, corpus: &Corpus) -> Layout { + fn weight_function( + columns: &[[char; N]; M], + corpus: &Corpus, + ) -> Vec<(Vec, u32)> { + let mut result = columns + .iter() + .map(|col| { + let mut weight = 0; + let mut wcol: Vec<(char, u32)> = col + .iter() + .map(|&c| { + let w = corpus.get_character_count(c); + weight += w; + (c, w) + }) + .collect(); + wcol.sort_by(|(_, l), (_, r)| r.cmp(l)); + wcol.swap(0, 1); + if N == 6 { + wcol.swap(3, 4); + } + (wcol.into_iter().map(|(k, _)| k).collect(), weight) + }) + .collect::, u32)>>(); + result.sort_by(|(_, l), (_, r)| r.cmp(l)); + result + } + + let mut balance: i64 = 0; + let mut left_col: usize = 0; + let mut right_col: usize = 9; + let mut keys = ['x'; NUM_KEYS]; + + let mut w_standard_columns = weight_function(&pl.standard_columns, corpus); + while let Some((col, weight)) = w_standard_columns.pop() { + let left: bool = ((balance >= 0) && (left_col <= 2)) || (right_col <= 6); + let ind = if left { left_col } else { right_col }; + for i in 0..3 { + keys[ind + i * ROW_LENGTH] = col[i]; + } + if left { + left_col += 1; + } else { + right_col -= 1; + } + balance += if left { + -(weight as i64) + } else { + weight as i64 + }; + } + + let w_index_columns = weight_function(&pl.index_columns, corpus); + let (left_ind, right_ind) = if balance >= 0 { (0, 1) } else { (1, 0) }; + for j in 0..3 { + keys[3 + j * ROW_LENGTH] = w_index_columns[left_ind].0[j]; + keys[6 + j * ROW_LENGTH] = w_index_columns[right_ind].0[j]; + keys[4 + j * ROW_LENGTH] = w_index_columns[left_ind].0[j + 3]; + keys[5 + j * ROW_LENGTH] = w_index_columns[right_ind].0[j + 3]; + } + + Layout { keys } } pub fn get_index(&self, c: char) -> usize { @@ -169,21 +239,6 @@ impl Layout { return Some(Layout::char_array_to_layout(layout)); } - - // pub fn new_random_from(kbd: &Layout, rng: &mut R) -> Layout { - // let mut layout = kbd.keys; - // // layout.shuffle(rng); - // let mut count = rng.gen_range(1..=NUM_KEYS / 2); - // while count > 0 { - // let a = rng.gen_range(0..NUM_KEYS); - // let b = rng.gen_range(0..NUM_KEYS); - // let k = layout[b]; - // layout[b] = layout[a]; - // layout[a] = k; - // count -= 1; - // } - // Layout::char_array_to_layout(layout) - // } } impl fmt::Display for Layout { @@ -212,10 +267,7 @@ impl Evaluation { let mut result = String::new(); let tot = self.total_keypress as f32; - result += &format!( - "Total keypresses: {}\nPercent per key:\n", - self.total_keypress - ); + 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]; @@ -223,16 +275,22 @@ impl Evaluation { finger_usages[KEY_TO_FINGER[i]] += c; } - result += "finger usage: "; + result += "\nFinger usage: "; + let mut lh: f32 = 0.0; + let mut rh: f32 = 0.0; for (i, &u) in finger_usages.iter().enumerate() { - result += &format!( - "{:>5.2}%{}", - u as f32 / tot * 100.0, - if i < 7 { ", " } else { "" } - ); + 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 += "\nsame finger bigrams: "; + 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}%{}", @@ -242,36 +300,10 @@ impl Evaluation { } let sfb = self.sfb.iter().sum::(); - result += &format!("\ntotal sfb: {:.2}% ({})", sfb as f32 / tot * 100.0, sfb); + result += &format!("\nTotal sfb: {:.2}% ({})", sfb as f32 / tot * 100.0, sfb); return result; } - - // TODO - // pub fn fitness(&self) -> f32 { - // let mut finger_usages = [0; 8]; - // let tot = self.total_keypress as f32; - // for (i, &c) in self.keypress.iter().enumerate() { - // finger_usages[KEY_TO_FINGER[i]] += c; - // } - // const MAX_FINGER_USAGES: [f32; 8] = [8.0, 11.0, 21.0, 21.0, 21.0, 21.0, 11.0, 8.0]; - // const MAX_FINGER_USAGES: [f32; 8] = - // [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0]; - // if finger_usages - // .iter() - // .enumerate() - // .any(|(i, &c)| c as f32 / tot * 100.00 > MAX_FINGER_USAGES[i]) - // { - // std::u32::MAX - // } else { - // self.sfb - // .iter() - // .enumerate() - // .map(|(i, &s)| s as f32 / finger_usages[i] as f32 * 50.0) - // .sum() - // } - // self.sfb.iter().sum::() as f32 - // } } impl fmt::Display for Evaluation { diff --git a/src/main.rs b/src/main.rs index 041730d..4294484 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -mod config; mod corpus; mod layout; @@ -15,13 +14,10 @@ use std::time::Instant; const NUM_CONTESTANTS: usize = 128; const NUM_PERSIST: usize = 8; -// TODO: Layout is really pre-layout, have something that takes a layout and -// re-orders columns to balance hands, and keys per column (block really -// consider index fingers) to balance rows. - // 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 +// configurable, and number of swaps when generating new layout, and index +// finger threshold. struct Tournament<'a> { rngs: Vec, @@ -98,10 +94,10 @@ fn main() { let improvement = tournament.run_round(); if let Some(prelayout) = improvement { - let layout = Layout::from_prelayout(&prelayout); + let layout = Layout::from_prelayout(&prelayout, &corpus); let evl = corpus.evaluate_layout(&layout); println!(""); - println!("================================================================================\nLayout:\n{}", + println!("================================================================================\n\n{}", layout ); -- cgit v1.3.1