From 94356c23ad9dd0039e6e3ac12c8b62065d910e38 Mon Sep 17 00:00:00 2001 From: soup Date: Sat, 21 Dec 2024 01:16:59 -0500 Subject: [PATCH] [klout] initial commit --- flake.lock | 4 +- klout/Cargo.lock | 32 +++++++ klout/Cargo.toml | 7 ++ klout/src/main.rs | 233 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 klout/Cargo.lock create mode 100644 klout/Cargo.toml create mode 100644 klout/src/main.rs diff --git a/flake.lock b/flake.lock index cb0c87f..d62dc25 100644 --- a/flake.lock +++ b/flake.lock @@ -25,11 +25,11 @@ "locked": { "lastModified": 1, "narHash": "sha256-PVtFcvxh3Aqgel46BBFzxN0IvEVDzw/n/hWJ76mVThQ=", - "path": "/nix/store/3s2w82xwqrd3rr4wja52rv2ac42fkgfb-source/nix/deno-flake", + "path": "/nix/store/0d80mkh7c7904wghfbvy3rpd395lriny-source/nix/deno-flake", "type": "path" }, "original": { - "path": "/nix/store/3s2w82xwqrd3rr4wja52rv2ac42fkgfb-source/nix/deno-flake", + "path": "/nix/store/0d80mkh7c7904wghfbvy3rpd395lriny-source/nix/deno-flake", "type": "path" } }, diff --git a/klout/Cargo.lock b/klout/Cargo.lock new file mode 100644 index 0000000..79939a2 --- /dev/null +++ b/klout/Cargo.lock @@ -0,0 +1,32 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "klout" +version = "0.0.0" +dependencies = [ + "eyre", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" diff --git a/klout/Cargo.toml b/klout/Cargo.toml new file mode 100644 index 0000000..0c1f6cb --- /dev/null +++ b/klout/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "klout" +version = "0.0.0" +edition = "2024" + +[dependencies] +eyre = "0.6.12" \ No newline at end of file diff --git a/klout/src/main.rs b/klout/src/main.rs new file mode 100644 index 0000000..103a357 --- /dev/null +++ b/klout/src/main.rs @@ -0,0 +1,233 @@ +use std::collections::HashMap; + +#[derive(Copy, Clone, Eq, PartialEq)] +enum Hand { + Left, + Right, +} + +#[derive(Copy, Clone, Eq, PartialEq)] +enum Digit { + Pinky, + Ring, + Middle, + Index, +} + +#[derive(Copy, Clone, Eq, PartialEq)] +struct Finger { + hand: Hand, + digit: Digit, +} + +const MATRIX_COUNT: usize = 30; + +macro_rules! F { + ($h:ident, $d:ident) => { + Finger { + hand: Hand::$h, + digit: Digit::$d, + } + }; +} + +const LPINKY: Finger = F!(Left, Pinky); +const LRING: Finger = F!(Left, Ring); +const LMIDDLE: Finger = F!(Left, Middle); +const LINDEX: Finger = F!(Left, Index); +const RPINKY: Finger = F!(Right, Pinky); +const RRING: Finger = F!(Right, Ring); +const RMIDDLE: Finger = F!(Right, Middle); +const RINDEX: Finger = F!(Right, Index); + +#[rustfmt::skip] +const MATRIX_EFFORT: &[f64; MATRIX_COUNT] = &[ + 15., 3.0, 1.5, 3.0, 10., 10., 3.0, 1.5, 3.0, 15., + 8., 1., 0.5, 0.5, 4., 4., 0.5, 0.5, 1., 8., + 1.5, 2.0, 1., 1., 7., 5., 1., 2.0, 1.5, 10., +]; + +#[rustfmt::skip] +const MATRIX_FINGERS: &[Finger; MATRIX_COUNT] = &[ + LPINKY, LRING, LMIDDLE, LINDEX, LINDEX, RINDEX, RINDEX, RMIDDLE, RRING, RPINKY, + LPINKY, LRING, LMIDDLE, LINDEX, LINDEX, RINDEX, RINDEX, RMIDDLE, RRING, RPINKY, + LRING, LMIDDLE, LINDEX, LINDEX, LINDEX, RINDEX, RINDEX, RMIDDLE, RRING, RPINKY, +]; + +const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyz.,/'"; +const _: () = assert!(CHARACTERS.len() == MATRIX_COUNT); + +const fn char_data_max_index() -> usize { + let bytes = CHARACTERS.as_bytes(); + let mut max = 0; + let len = bytes.len(); + + let mut i = 0; + while i < len { + let b = bytes[i]; + max = if b < max { max } else { b }; + i += 1; + } + + max as usize + 1 +} + +// https://www3.nd.edu/~busiforc/handouts/cryptography/Letter%20Frequencies.html#Relative_frequencies_of_letters + +const CHAR_FREQ_LOOKUP: [f64; char_data_max_index()] = { + let mut data: [f64; char_data_max_index()] = [1.; char_data_max_index()]; + + macro_rules! d { + ($ch:expr, $val:expr) => {{ + data[$ch as usize] = $val * 100.; + }}; + } + + d!('a', 0.08167); + d!('b', 0.01492); + d!('c', 0.02782); + d!('d', 0.04253); + d!('e', 0.12702); + d!('f', 0.02228); + d!('g', 0.02015); + d!('h', 0.06094); + d!('i', 0.06966); + d!('j', 0.00153); + d!('k', 0.00772); + d!('l', 0.04025); + d!('m', 0.02406); + d!('n', 0.06749); + d!('o', 0.07507); + d!('p', 0.01929); + d!('q', 0.00095); + d!('r', 0.05987); + d!('s', 0.06327); + d!('t', 0.09056); + d!('u', 0.02758); + d!('v', 0.00978); + d!('w', 0.02360); + d!('x', 0.00150); + d!('y', 0.01974); + d!('z', 0.00074); + + data +}; + +struct Layout { + key_indices: [usize; char_data_max_index()], +} +impl Layout { + const fn from_key_matrix(m: &[char; MATRIX_COUNT]) -> Self { + let mut key_indices = [0; char_data_max_index()]; + + let mut i = 0; + while i < m.len() { + let ch = m[i]; + key_indices[ch as usize] = i; + i += 1; + } + + Self { key_indices } + } +} + +fn key_effort(ch: char, l: &Layout) -> f64 { + CHAR_FREQ_LOOKUP[ch as usize] * MATRIX_EFFORT[l.key_indices[ch as usize]] +} + +#[rustfmt::skip] +const INITIAL_LAYOUT: Layout = Layout::from_key_matrix(&[ + 'q', 'w', 'f', 'p', 'b', 'j', 'l', 'u', 'y', '\'', + 'a', 'r', 's', 't', 'g', 'm', 'n', 'e', 'i', 'o', + 'x', 'c', 'd', 'v', 'z', 'k', 'h', ',', '.', '/', +]); + +type NGramFreqs = HashMap<&'static str, f64>; + +static mut BIGRAM_FREQS: Option = None; +unsafe fn init_bigram_freqs() { + let freqs = [ + ("th", 0.03882543), + ("he", 0.03681), + ("in", 0.02283899), + ("er", 0.02178042), + ("an", 0.02140460), + ("re", 0.01749394), + ("nd", 0.01571977), + ("on", 0.01418244), + ("en", 0.01383239), + ("at", 0.01335523), + ("ou", 0.01285485), + ("ed", 0.01275779), + ("ha", 0.01274742), + ("to", 0.01169655), + ("or", 0.01151094), + ("it", 0.01134891), + ("is", 0.01109877), + ("hi", 0.01092302), + ("es", 0.01092301), + ("ng", 0.01053385), + ] + .into_iter() + .map(|(s, v)| (s, v * 100.)) + .collect::(); + + unsafe { BIGRAM_FREQS = Some(freqs) }; +} + +#[allow(static_mut_refs)] +fn bigram_effort(bigram: &str, l: &Layout) -> f64 { + let bigrams = unsafe { BIGRAM_FREQS.as_ref().unwrap_unchecked() }; + + let mut eff = bigrams.get(bigram).copied().unwrap_or(1.); + + let ch1 = bigram.as_bytes()[0]; + let ch2 = bigram.as_bytes()[1]; + + let finger1 = MATRIX_FINGERS[l.key_indices[ch1 as usize]]; + let finger2 = MATRIX_FINGERS[l.key_indices[ch2 as usize]]; + if finger1 == finger2 { + eff *= 10.; + } + + eff +} + +static mut ALL_BIGRAMS: Option> = None; +unsafe fn init_all_bigrams() { + let it = CHARACTERS.chars().flat_map(|ch1| { + CHARACTERS + .chars() + .map(move |ch2| [ch1 as u8, ch2 as u8]) + .map(|b| String::from_utf8(b.to_vec()).unwrap()) + }); + + unsafe { ALL_BIGRAMS = Some(it.collect()) } +} + +#[allow(static_mut_refs)] +fn all_bigrams() -> &'static [String] { + unsafe { ALL_BIGRAMS.as_ref().unwrap_unchecked() } +} + +fn main() { + unsafe { + init_bigram_freqs(); + init_all_bigrams(); + }; + + let eff: f64 = CHARACTERS + .chars() + .map(|v| { + let mut eff = key_effort(v, &INITIAL_LAYOUT); + eff += all_bigrams() + .iter() + .map(|b| bigram_effort(b, &INITIAL_LAYOUT)) + .sum::(); + + eff + }) + .sum(); + + println!("{eff}"); +}