chai/
data.rs

1//! 数据结构的定义
2
3use crate::{
4    config::{Mapped, MappedKey, Regularization, Scheme, ShortCodeConfig, 配置},
5    encoders::简码配置,
6    objectives::metric::指法标记,
7    错误,
8};
9use regex::Regex;
10use rustc_hash::FxHashMap;
11use serde::{Deserialize, Serialize};
12use std::{cmp::Reverse, collections::HashMap};
13
14/// 只考虑长度为 1 到 10 的词
15pub const 最大词长: usize = 10;
16
17/// 只对低于最大按键组合长度的编码预先计算当量
18pub const 最大按键组合长度: usize = 4;
19
20/// 从配置文件中读取的原始可编码对象
21#[derive(Debug, Serialize, Deserialize, Clone)]
22pub struct 原始可编码对象 {
23    pub name: String,
24    pub sequence: String,
25    pub frequency: u64,
26    #[serde(default = "原始可编码对象::默认级别")]
27    pub level: u64,
28}
29
30impl 原始可编码对象 {
31    const fn 默认级别() -> u64 {
32        u64::MAX
33    }
34}
35
36pub type 原始键位分布信息 = HashMap<char, 键位分布损失函数>;
37pub type 键位分布信息 = Vec<键位分布损失函数>;
38pub type 原始当量信息 = HashMap<String, f64>;
39pub type 当量信息 = Vec<f64>;
40
41/// 键位分布的理想值和惩罚值
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct 键位分布损失函数 {
44    pub ideal: f64,
45    pub lt_penalty: f64,
46    pub gt_penalty: f64,
47}
48
49/// 元素用一个无符号整数表示
50pub type 元素 = usize;
51
52/// 可编码对象的序列
53pub type 元素序列 = Vec<元素>;
54
55/// 编码用无符号整数表示
56pub type 编码 = u64;
57
58/// 包含词、词长、元素序列、频率等信息
59#[derive(Debug, Clone)]
60pub struct 可编码对象 {
61    pub 名称: String,
62    pub 词长: usize,
63    pub 元素序列: 元素序列,
64    pub 频率: u64,
65    pub 简码等级: u64,
66    pub 原始顺序: usize,
67}
68
69/// 全码或简码的编码信息
70#[derive(Clone, Debug, Copy, Default)]
71pub struct 部分编码信息 {
72    pub 原始编码: 编码,       // 原始编码
73    pub 原始编码候选位置: u8, // 原始编码上的选重位置
74    pub 实际编码: 编码,       // 实际编码
75    pub 选重标记: bool,       // 实际编码是否算作重码
76    pub 上一个实际编码: 编码, // 前一个实际编码
77    pub 上一个选重标记: bool, // 前一个实际编码是否算作重码
78    pub 有变化: bool,         // 编码是否发生了变化
79}
80
81impl 部分编码信息 {
82    #[inline(always)]
83    pub fn 更新(&mut self, 编码: 编码, 选重标记: bool) {
84        if self.实际编码 == 编码 && self.选重标记 == 选重标记 {
85            return;
86        }
87        self.有变化 = true;
88        self.上一个实际编码 = self.实际编码;
89        self.上一个选重标记 = self.选重标记;
90        self.实际编码 = 编码;
91        self.选重标记 = 选重标记;
92    }
93}
94
95/// 包含长度、频率、全码和简码,用于传给目标函数来统计
96#[derive(Clone, Debug)]
97pub struct 编码信息 {
98    pub 词长: usize,
99    pub 频率: u64,
100    pub 全码: 部分编码信息,
101    pub 简码: 部分编码信息,
102}
103
104impl 编码信息 {
105    pub fn new(词: &可编码对象) -> Self {
106        Self {
107            词长: 词.词长,
108            频率: 词.频率,
109            全码: 部分编码信息::default(),
110            简码: 部分编码信息::default(),
111        }
112    }
113}
114
115/// 按键用无符号整数表示
116pub type 键 = u64;
117
118/// 元素映射用一个数组表示,下标是元素
119pub type 元素映射 = Vec<键>;
120
121/// 用指标记
122pub type 指法向量 = [u8; 8];
123
124/// 自动上屏判断数组
125pub type 自动上屏 = Vec<bool>;
126
127/// 用于输出为文本码表,包含了名称、全码、简码、全码排名和简码排名
128#[derive(Debug, Serialize)]
129pub struct 码表项 {
130    pub name: String,
131    pub full: String,
132    pub full_rank: u8,
133    pub short: String,
134    pub short_rank: u8,
135}
136
137pub type 正则化 = FxHashMap<元素, Vec<(元素, f64)>>;
138
139/// 将用户提供的输入转换为内部数据结构,并提供了一些实用的方法
140#[derive(Debug, Clone)]
141pub struct 数据 {
142    pub 配置: 配置,
143    pub 词列表: Vec<可编码对象>,
144    pub 键位分布信息: 键位分布信息,
145    pub 当量信息: 当量信息,
146    pub 初始映射: 元素映射,
147    pub 正则化: 正则化,
148    pub 进制: u64,
149    pub 选择键: Vec<键>,
150    pub 键转数字: FxHashMap<char, 键>,
151    pub 数字转键: FxHashMap<键, char>,
152    pub 元素转数字: FxHashMap<String, 元素>,
153    pub 数字转元素: FxHashMap<元素, String>,
154}
155
156impl Mapped {
157    pub fn length(&self) -> usize {
158        match self {
159            Mapped::Basic(s) => s.len(),
160            Mapped::Advanced(v) => v.len(),
161        }
162    }
163
164    pub fn normalize(&self) -> Vec<MappedKey> {
165        match self {
166            Mapped::Advanced(vector) => vector.clone(),
167            Mapped::Basic(string) => string.chars().map(MappedKey::Ascii).collect(),
168        }
169    }
170}
171
172type 字母表信息 = (u64, Vec<键>, FxHashMap<char, 键>, FxHashMap<键, char>);
173type 映射信息 = (元素映射, FxHashMap<String, 元素>, FxHashMap<元素, String>);
174
175impl 数据 {
176    pub fn 新建(
177        配置: 配置,
178        原始词列表: Vec<原始可编码对象>,
179        原始键位分布信息: 原始键位分布信息,
180        原始当量信息: 原始当量信息,
181    ) -> Result<Self, 错误> {
182        let (进制, 选择键, 键转数字, 数字转键) = Self::预处理字母表(&配置)?;
183        let (初始映射, 元素转数字, 数字转元素) = Self::预处理映射(&配置, &键转数字, 进制)?;
184        let 最大码长 = 配置.encoder.max_length;
185        let 词列表 = Self::预处理词列表(原始词列表, 最大码长, &元素转数字)?;
186        let 组合长度 = 最大码长.min(最大按键组合长度);
187        let 编码空间大小 = 进制.pow(组合长度 as u32) as usize;
188        let 键位分布信息 = Self::预处理键位分布信息(&原始键位分布信息, 进制, &数字转键);
189        let 当量信息 = Self::预处理当量信息(&原始当量信息, 编码空间大小, 进制, &数字转键);
190        let 正则化 = if let Some(正则化配置) = 配置
191            .optimization
192            .clone()
193            .and_then(|x| Some(x.objective))
194            .and_then(|x| Some(x.regularization))
195            .flatten()
196        {
197            Self::预处理正则化(&正则化配置, &元素转数字)?
198        } else {
199            FxHashMap::default()
200        };
201        let repr = Self {
202            配置,
203            词列表,
204            键位分布信息,
205            当量信息,
206            初始映射,
207            元素转数字,
208            数字转元素,
209            键转数字,
210            数字转键,
211            进制,
212            选择键,
213            正则化,
214        };
215        Ok(repr)
216    }
217
218    /// 读取字母表和选择键列表,然后分别对它们的每一个按键转换成无符号整数
219    /// 1, ... n = 所有常规编码键
220    /// n + 1, ..., m = 所有选择键
221    pub fn 预处理字母表(config: &配置) -> Result<字母表信息, 错误> {
222        let mut 键转数字: FxHashMap<char, 键> = FxHashMap::default();
223        let mut 数字转键: FxHashMap<键, char> = FxHashMap::default();
224        let mut index = 1;
225        for key in config.form.alphabet.chars() {
226            if 键转数字.contains_key(&key) {
227                return Err("编码键有重复!".into());
228            };
229            键转数字.insert(key, index);
230            数字转键.insert(index, key);
231            index += 1;
232        }
233        let default_select_keys = vec!['_'];
234        let select_keys = config
235            .encoder
236            .select_keys
237            .as_ref()
238            .unwrap_or(&default_select_keys);
239        if select_keys.is_empty() {
240            return Err("选择键不能为空!".into());
241        }
242        let mut parsed_select_keys: Vec<键> = vec![];
243        for key in select_keys {
244            if 键转数字.contains_key(key) {
245                return Err("编码键有重复!".into());
246            };
247            键转数字.insert(*key, index);
248            数字转键.insert(index, *key);
249            parsed_select_keys.push(index);
250            index += 1;
251        }
252        let radix = index;
253        Ok((radix, parsed_select_keys, 键转数字, 数字转键))
254    }
255
256    /// 读取元素映射,然后把每一个元素转换成无符号整数,从而可以用向量来表示一个元素布局,向量的下标就是元素对应的数
257    pub fn 预处理映射(
258        配置: &配置,
259        键转数字: &FxHashMap<char, 键>,
260        进制: u64,
261    ) -> Result<映射信息, 错误> {
262        let mut 元素映射: 元素映射 = (0..进制).collect();
263        let mut 元素转数字: FxHashMap<String, 元素> = FxHashMap::default();
264        let mut 数字转元素: FxHashMap<元素, String> = FxHashMap::default();
265        for (键字符, 键) in 键转数字 {
266            元素转数字.insert(键字符.to_string(), *as usize);
267            数字转元素.insert(*as usize, 键字符.to_string());
268        }
269        for (元素, 映射值) in &配置.form.mapping {
270            let 映射值 = 映射值.normalize();
271            for (序号, 映射键) in 映射值.iter().enumerate() {
272                if let MappedKey::Ascii(x) = 映射键 {
273                    if let Some(键) = 键转数字.get(x) {
274                        let 元素名 = Self::序列化(元素, 序号);
275                        元素转数字.insert(元素名.clone(), 元素映射.len());
276                        数字转元素.insert(元素映射.len(), 元素名.clone());
277                        元素映射.push(*键);
278                    } else {
279                        return Err(format!("元素 {元素} 的编码中的字符 {x} 并不在字母表中").into());
280                    }
281                }
282            }
283        }
284        Ok((元素映射, 元素转数字, 数字转元素))
285    }
286
287    pub fn 预处理正则化(
288        正则化: &Regularization,
289        元素转数字: &FxHashMap<String, 元素>,
290    ) -> Result<FxHashMap<元素, Vec<(元素, f64)>>, 错误> {
291        let mut result = FxHashMap::default();
292        if let Some(列表) = &正则化.element_affinities {
293            for 规则 in 列表 {
294                let 元素名称 = Self::序列化(&规则.from.element, 规则.from.index);
295                let 元素 = 元素转数字
296                    .get(&元素名称)
297                    .ok_or(format!("元素 {元素名称} 不存在"))?;
298                let mut 亲和度列表 = Vec::new();
299                for 目标 in 规则.to.iter() {
300                    let 目标元素名称 = Self::序列化(&目标.element.element, 目标.element.index);
301                    let 目标元素 = 元素转数字
302                        .get(&目标元素名称)
303                        .ok_or(format!("目标元素 {目标元素名称} 不存在"))?;
304                    亲和度列表.push((*目标元素, 目标.affinity));
305                }
306                result.insert(*元素, 亲和度列表);
307            }
308        }
309        if let Some(列表) = &正则化.key_affinities {
310            for 规则 in 列表 {
311                let 元素名称 = Self::序列化(&规则.from.element, 规则.from.index);
312                let 元素 = 元素转数字
313                    .get(&元素名称)
314                    .ok_or(format!("元素 {元素名称} 不存在"))?;
315                let mut 亲和度列表 = Vec::new();
316                for 目标 in 规则.to.iter() {
317                    let 目标键位 = 元素转数字
318                        .get(&目标.key.to_string())
319                        .ok_or(format!("目标键位不存在"))?;
320                    亲和度列表.push((*目标键位, 目标.affinity));
321                }
322                result.insert(*元素, 亲和度列表);
323            }
324        }
325        Ok(result)
326    }
327    pub fn 序列化(element: &String, index: usize) -> String {
328        if index == 0 {
329            element.to_string()
330        } else {
331            format!("{}.{}", element, index)
332        }
333    }
334
335    /// 读取拆分表,将拆分序列中的每一个元素按照先前确定的元素 -> 整数映射来转换为整数向量
336    pub fn 预处理词列表(
337        raw_encodables: Vec<原始可编码对象>,
338        max_length: usize,
339        element_repr: &FxHashMap<String, 元素>,
340    ) -> Result<Vec<可编码对象>, 错误> {
341        let mut encodables = Vec::new();
342        for (index, assemble) in raw_encodables.into_iter().enumerate() {
343            let 原始可编码对象 {
344                name,
345                frequency,
346                level,
347                sequence,
348            } = assemble;
349            let raw_sequence: Vec<_> = sequence.split(' ').collect();
350            let mut sequence = 元素序列::new();
351            let length = raw_sequence.len();
352            if length > max_length {
353                return Err(format!(
354                    "编码对象「{name}」包含的元素数量为 {length},超过了最大码长 {max_length}"
355                )
356                .into());
357            }
358            for element in raw_sequence {
359                if let Some(number) = element_repr.get(element) {
360                    sequence.push(*number);
361                } else {
362                    return Err(format!(
363                        "编码对象「{name}」包含的元素「{element}」无法在键盘映射中找到"
364                    )
365                    .into());
366                }
367            }
368            encodables.push(可编码对象 {
369                名称: name.clone(),
370                词长: name.chars().count(),
371                元素序列: sequence,
372                频率: frequency,
373                简码等级: level,
374                原始顺序: index,
375            });
376        }
377
378        encodables.sort_by_key(|x| Reverse(x.频率));
379        Ok(encodables)
380    }
381
382    pub fn 生成码表(&self, buffer: &[编码信息]) -> Vec<码表项> {
383        let mut entries: Vec<(usize, 码表项)> = Vec::new();
384        let encodables = &self.词列表;
385        let recover = |code: 编码| {
386            Self::数字转编码(code, self.进制, &self.数字转键)
387                .iter()
388                .collect()
389        };
390        for (index, encodable) in encodables.iter().enumerate() {
391            let entry = 码表项 {
392                name: encodable.名称.clone(),
393                full: recover(buffer[index].全码.原始编码),
394                full_rank: buffer[index].全码.原始编码候选位置,
395                short: recover(buffer[index].简码.原始编码),
396                short_rank: buffer[index].简码.原始编码候选位置,
397            };
398            entries.push((encodable.原始顺序, entry));
399        }
400        entries.sort_by_key(|x| x.0);
401        entries.into_iter().map(|x| x.1).collect()
402    }
403
404    /// 根据一个计算中得到的元素布局来生成一份新的配置文件,其余内容不变直接复制过来
405    pub fn 更新配置(&self, candidate: &元素映射) -> 配置 {
406        let mut new_config = self.配置.clone();
407        let lookup = |element: &String| {
408            let number = *self.元素转数字.get(element).unwrap(); // 输入的时候已经检查过一遍,不需要再次检查
409            let current_mapped = &candidate[number];
410            *self.数字转键.get(current_mapped).unwrap() // 同上
411        };
412        for (element, mapped) in &self.配置.form.mapping {
413            let new_element = element.clone();
414            let new_mapped = match mapped {
415                Mapped::Basic(string) => {
416                    let mut all_codes = String::new();
417                    for index in 0..string.len() {
418                        let name = Self::序列化(element, index);
419                        all_codes.push(lookup(&name));
420                    }
421                    Mapped::Basic(all_codes)
422                }
423                Mapped::Advanced(vector) => {
424                    let all_codes: Vec<MappedKey> = vector
425                        .iter()
426                        .enumerate()
427                        .map(|(index, mapped_key)| match mapped_key {
428                            MappedKey::Ascii(_) => {
429                                MappedKey::Ascii(lookup(&Self::序列化(element, index)))
430                            }
431                            other => other.clone(),
432                        })
433                        .collect();
434                    Mapped::Advanced(all_codes)
435                }
436            };
437            new_config.form.mapping.insert(new_element, new_mapped);
438        }
439        new_config
440    }
441
442    /// 如前所述,建立了一个按键到整数的映射之后,可以将字符串看成具有某个进制的数。所以,给定一个数,也可以把它转化为字符串
443    pub fn 数字转编码(
444        code: 编码, 进制: u64, repr_key: &FxHashMap<键, char>
445    ) -> Vec<char> {
446        let mut chars = Vec::new();
447        let mut remainder = code;
448        while remainder > 0 {
449            let k = remainder % 进制;
450            remainder /= 进制;
451            if k == 0 {
452                continue;
453            }
454            let char = repr_key.get(&k).unwrap(); // 从内部表示转换为字符,不需要检查
455            chars.push(*char);
456        }
457        chars
458    }
459
460    /// 根据编码字符和未归一化的键位分布,生成一个理想的键位分布
461    pub fn 预处理键位分布信息(
462        原始键位分布信息: &原始键位分布信息,
463        进制: u64,
464        数字转键: &FxHashMap<键, char>,
465    ) -> Vec<键位分布损失函数> {
466        let default_loss = 键位分布损失函数 {
467            ideal: 0.0,
468            lt_penalty: 0.0,
469            gt_penalty: 1.0,
470        };
471        let mut 键位分布信息: Vec<键位分布损失函数> = (0..进制)
472            .map(|键| {
473                // 0 只是为了占位,不需要统计
474                if 键 == 0 {
475                    default_loss.clone()
476                } else {
477                    let 键名称 = 数字转键[&键];
478                    原始键位分布信息
479                        .get(&键名称)
480                        .unwrap_or(&default_loss)
481                        .clone()
482                }
483            })
484            .collect();
485        键位分布信息.iter_mut().for_each(|x| {
486            x.ideal /= 100.0;
487        });
488        键位分布信息
489    }
490
491    /// 将编码空间内所有的编码组合预先计算好速度当量
492    /// 按照这个字符串所对应的整数为下标,存储到一个大数组中
493    pub fn 预处理当量信息(
494        原始当量信息: &原始当量信息,
495        space: usize,
496        进制: u64,
497        数字转键: &FxHashMap<键, char>,
498    ) -> Vec<f64> {
499        let mut result: Vec<f64> = vec![0.0; space];
500        for (index, equivalence) in result.iter_mut().enumerate() {
501            let chars = Self::数字转编码(index as u64, 进制, 数字转键);
502            for correlation_length in [2, 3, 4] {
503                if chars.len() < correlation_length {
504                    break;
505                }
506                // N 键当量
507                for i in 0..=(chars.len() - correlation_length) {
508                    let substr: String = chars[i..(i + correlation_length)].iter().collect();
509                    *equivalence += 原始当量信息.get(&substr).unwrap_or(&0.0);
510                }
511            }
512        }
513        result
514    }
515
516    /// 将编码空间内所有的编码组合预先计算好差指法标记
517    /// 标记压缩到一个 64 位整数中,每四位表示一个字符的差指法标记
518    /// 从低位到高位,依次是:同手、同指大跨排、同指小跨排、小指干扰、错手、三连击
519    /// 按照这个字符串所对应的整数为下标,存储到一个大数组中
520    pub fn 预处理指法标记(&self) -> Vec<指法向量> {
521        let 指法标记 = 指法标记::new();
522        let mut result: Vec<指法向量> = Vec::with_capacity(self.get_space());
523        for code in 0..self.get_space() {
524            let chars = Self::数字转编码(code as u64, self.进制, &self.数字转键);
525            if chars.len() < 2 {
526                result.push(指法向量::default());
527                continue;
528            }
529            let mut 指法向量 = 指法向量::default();
530            for i in 0..(chars.len() - 1) {
531                let pair = (chars[i], chars[i + 1]);
532                if 指法标记.同手.contains(&pair) {
533                    指法向量[0] += 1;
534                }
535                if 指法标记.同指大跨排.contains(&pair) {
536                    指法向量[1] += 1;
537                }
538                if 指法标记.同指小跨排.contains(&pair) {
539                    指法向量[2] += 1;
540                }
541                if 指法标记.小指干扰.contains(&pair) {
542                    指法向量[3] += 1;
543                }
544                if 指法标记.错手.contains(&pair) {
545                    指法向量[4] += 1;
546                }
547            }
548            for i in 0..(chars.len() - 2) {
549                let triple = (chars[i], chars[i + 1], chars[i + 2]);
550                if triple.0 == triple.1 && triple.1 == triple.2 {
551                    指法向量[5] += 1;
552                }
553            }
554            result.push(指法向量);
555        }
556        result
557    }
558
559    /// 将编码空间内所有的编码组合预先计算好是否能自动上屏
560    /// 按照这个字符串所对应的整数为下标,存储到一个大数组中
561    pub fn 预处理自动上屏(&self) -> Result<Vec<bool>, 错误> {
562        let mut result: Vec<bool> = vec![];
563        let encoder = &self.配置.encoder;
564        let mut re: Option<Regex> = None;
565        if let Some(pattern) = &encoder.auto_select_pattern {
566            let re_or_error = Regex::new(pattern);
567            if let Ok(regex) = re_or_error {
568                re = Some(regex);
569            } else {
570                return Err(format!("正则表达式 {pattern} 无法解析").into());
571            }
572        }
573        for code in 0..self.get_space() {
574            let chars = Self::数字转编码(code as u64, self.进制, &self.数字转键);
575            let string: String = chars.iter().collect();
576            let is_matched = if let Some(re) = &re {
577                re.is_match(&string)
578            } else if let Some(length) = encoder.auto_select_length {
579                chars.len() >= length
580            } else {
581                true
582            };
583            let is_max_length = chars.len() == encoder.max_length;
584            result.push(is_matched || is_max_length);
585        }
586        Ok(result)
587    }
588
589    pub fn 预处理简码规则(
590        &self,
591        schemes: &Vec<Scheme>,
592    ) -> Result<Vec<简码配置>, 错误> {
593        let mut compiled_schemes = Vec::new();
594        for scheme in schemes {
595            let prefix = scheme.prefix;
596            let count = scheme.count.unwrap_or(1);
597            let select_keys = if let Some(keys) = &scheme.select_keys {
598                let mut transformed_keys = Vec::new();
599                for key in keys {
600                    let transformed_key = self
601                        .键转数字
602                        .get(key)
603                        .ok_or(format!("简码的选择键 {key} 不在全局选择键中"))?;
604                    transformed_keys.push(*transformed_key);
605                }
606                transformed_keys
607            } else {
608                self.选择键.clone()
609            };
610            if count > select_keys.len() {
611                return Err("选重数量不能高于选择键数量".into());
612            }
613            compiled_schemes.push(简码配置 {
614                prefix,
615                select_keys: select_keys[..count].to_vec(),
616            });
617        }
618        Ok(compiled_schemes)
619    }
620
621    pub fn 预处理简码配置(
622        &self,
623        configs: Vec<ShortCodeConfig>,
624    ) -> Result<[Vec<简码配置>; 最大词长], 错误> {
625        let mut short_code: [Vec<简码配置>; 最大词长] = Default::default();
626        for config in configs {
627            match config {
628                ShortCodeConfig::Equal {
629                    length_equal,
630                    schemes,
631                } => {
632                    short_code[length_equal - 1].extend(self.预处理简码规则(&schemes)?);
633                }
634                ShortCodeConfig::Range {
635                    length_in_range: (from, to),
636                    schemes,
637                } => {
638                    for length in from..=to {
639                        short_code[length - 1].extend(self.预处理简码规则(&schemes)?);
640                    }
641                }
642            }
643        }
644        Ok(short_code)
645    }
646
647    pub fn get_space(&self) -> usize {
648        let max_length = self.配置.encoder.max_length.min(最大按键组合长度);
649        self.进制.pow(max_length as u32) as usize
650    }
651}