1use std::collections::{BTreeSet, HashMap};
11use std::path::Path;
12
13use crate::error::{ForgeError, Result};
14use crate::tokenizer::Tokenizer;
15
16pub struct CharTokenizer {
17 itos: Vec<char>,
19 stoi: HashMap<char, u32>,
20}
21
22impl CharTokenizer {
23 pub fn from_corpus(text: &str) -> Self {
26 Self::from_chars(text.chars().collect::<BTreeSet<char>>())
27 }
28
29 fn from_chars(sorted: BTreeSet<char>) -> Self {
30 let itos: Vec<char> = sorted.into_iter().collect();
31 let stoi = itos
32 .iter()
33 .enumerate()
34 .map(|(i, &c)| (c, i as u32))
35 .collect();
36 CharTokenizer { itos, stoi }
37 }
38
39 pub fn chars(&self) -> &[char] {
41 &self.itos
42 }
43
44 pub fn unknown_chars(&self, text: &str) -> Vec<char> {
48 let mut seen = Vec::new();
49 for c in text.chars() {
50 if !self.stoi.contains_key(&c) && !seen.contains(&c) {
51 seen.push(c);
52 }
53 }
54 seen
55 }
56
57 pub fn encode_lossy(&self, text: &str) -> Vec<u32> {
61 text.chars()
62 .filter_map(|c| self.stoi.get(&c).copied())
63 .collect()
64 }
65
66 pub fn to_json(&self) -> String {
69 let map: HashMap<String, u32> = self
70 .stoi
71 .iter()
72 .map(|(&c, &i)| (c.to_string(), i))
73 .collect();
74 serde_json::to_string(&map).expect("char vocab serializes")
76 }
77
78 pub fn from_json(s: &str) -> Result<Self> {
79 let map: HashMap<String, u32> = serde_json::from_str(s)?;
80 let mut itos = vec!['\0'; map.len()];
81 let mut filled = vec![false; map.len()];
82 for (k, id) in &map {
83 let mut cs = k.chars();
84 let (Some(c), None) = (cs.next(), cs.next()) else {
85 return Err(ForgeError::Tokenizer(format!(
86 "char vocab entry {k:?} is not a single character"
87 )));
88 };
89 let idx = *id as usize;
90 if idx >= itos.len() {
91 return Err(ForgeError::Tokenizer(format!(
92 "char vocab id {id} out of range for a {}-entry vocab",
93 itos.len()
94 )));
95 }
96 if filled[idx] {
97 return Err(ForgeError::Tokenizer(format!(
98 "char vocab id {id} assigned twice"
99 )));
100 }
101 itos[idx] = c;
102 filled[idx] = true;
103 }
104 let stoi = itos
105 .iter()
106 .enumerate()
107 .map(|(i, &c)| (c, i as u32))
108 .collect();
109 Ok(CharTokenizer { itos, stoi })
110 }
111
112 pub fn save_json(&self, path: impl AsRef<Path>) -> Result<()> {
116 std::fs::write(path, self.to_json())?;
117 Ok(())
118 }
119
120 pub fn from_json_file(path: impl AsRef<Path>) -> Result<Self> {
121 Self::from_json(&std::fs::read_to_string(path)?)
122 }
123}
124
125impl Tokenizer for CharTokenizer {
126 fn encode(&self, text: &str) -> Result<Vec<u32>> {
130 text.chars()
131 .map(|c| {
132 self.stoi.get(&c).copied().ok_or_else(|| {
133 ForgeError::Tokenizer(format!(
134 "character {c:?} is outside the {}-token vocabulary",
135 self.itos.len()
136 ))
137 })
138 })
139 .collect()
140 }
141
142 fn decode(&self, ids: &[u32]) -> String {
143 ids.iter()
144 .filter_map(|&i| self.itos.get(i as usize))
145 .collect()
146 }
147
148 fn decode_bytes(&self, ids: &[u32]) -> Vec<u8> {
151 self.decode(ids).into_bytes()
152 }
153
154 fn vocab_size(&self) -> usize {
155 self.itos.len()
156 }
157}