use crate::nine_key::KeyGrid;
#[cfg(test)]
pub const MAX_EXPANSIONS: usize = 16;
pub fn representative(group_code: u8) -> Option<u8> {
if group_code == b'z' {
return Some(b'x');
}
(KeyGrid::FourteenKey.code_of(group_code) == Some(group_code)).then_some(group_code)
}
pub fn representative_of(letter: u8) -> Option<u8> {
if letter == b'z' {
return None;
}
KeyGrid::FourteenKey
.code_of(letter)
.and_then(representative)
}
pub fn letters(representative: u8) -> &'static str {
match representative {
b'x' => "x",
b'z' => "",
code => KeyGrid::FourteenKey.letters(code),
}
}
pub fn code_matches(code: &str, representatives: &str) -> bool {
code.len() >= representatives.len()
&& code
.bytes()
.zip(representatives.bytes())
.all(|(letter, group)| representative_of(letter) == Some(group))
}
pub fn expand_into(representatives: &str, expansions: &mut Vec<String>) {
expansions.clear();
if representatives.is_empty() {
return;
}
expansions.push(String::with_capacity(representatives.len()));
for group in representatives.bytes() {
let group_letters = letters(group);
if group_letters.is_empty() {
expansions.clear();
return;
}
let previous = expansions.len();
for index in 0..previous {
for (offset, letter) in group_letters.bytes().enumerate() {
if offset + 1 == group_letters.len() {
expansions[index].push(char::from(letter));
} else {
let mut next = expansions[index].clone();
next.push(char::from(letter));
expansions.push(next);
}
}
}
}
expansions.sort_unstable();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_wubi_letter_has_exactly_one_group() {
for letter in b'a'..=b'y' {
let group = representative_of(letter).expect("五笔字母都在某个键上");
assert!(
letters(group).as_bytes().contains(&letter),
"{} 应该在 {} 组里",
char::from(letter),
char::from(group)
);
}
assert_eq!(representative_of(b'z'), None);
assert_eq!(representative_of(b';'), None);
}
#[test]
fn the_zx_key_stands_for_x_only() {
assert_eq!(representative(b'z'), Some(b'x'));
assert_eq!(representative_of(b'x'), Some(b'x'));
assert_eq!(letters(b'x'), "x");
assert_eq!(representative(b'x'), None);
assert_eq!(representative(b'w'), None);
assert_eq!(representative(b'q'), Some(b'q'));
}
#[test]
fn groups_follow_the_fourteen_key_face() {
let groups: Vec<&str> = b"qetuoadgjlxcbm"
.iter()
.map(|&code| letters(code))
.collect();
assert_eq!(
groups,
["qw", "er", "ty", "ui", "op", "as", "df", "gh", "jk", "l", "x", "cv", "bn", "m"]
);
}
#[test]
fn expansion_is_the_sorted_cartesian_product() {
let mut expansions = Vec::new();
expand_into("qq", &mut expansions);
assert_eq!(expansions, ["qq", "qw", "wq", "ww"]);
expand_into("lxm", &mut expansions);
assert_eq!(expansions, ["lxm"]);
expand_into("qetu", &mut expansions);
assert_eq!(expansions.len(), MAX_EXPANSIONS);
assert!(expansions.windows(2).all(|pair| pair[0] < pair[1]));
expand_into("qz", &mut expansions);
assert!(expansions.is_empty());
expand_into("", &mut expansions);
assert!(expansions.is_empty());
}
#[test]
fn a_code_matches_the_groups_it_was_typed_on() {
assert!(code_matches("wqiy", "qq"));
assert!(code_matches("wq", "qq"));
assert!(!code_matches("w", "qq"));
assert!(!code_matches("vb", "qq"));
assert!(code_matches("xx", "xx"));
}
}