use std::cmp::Ordering;
use std::collections::BinaryHeap;
use crate::vocab::Vocab;
pub struct Span {
pub start: usize,
pub len: usize,
pub id: i32,
}
struct Symbol {
start: usize,
len: usize, prev: i64,
next: i64,
}
struct Pair {
score: f32,
left: usize,
right: usize,
size: usize, }
impl PartialEq for Pair {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Pair {}
impl PartialOrd for Pair {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Pair {
fn cmp(&self, other: &Self) -> Ordering {
match self.score.total_cmp(&other.score) {
Ordering::Equal => other.left.cmp(&self.left),
ord => ord,
}
}
}
pub fn encode(norm: &[u8], vocab: &Vocab) -> Vec<Span> {
if norm.is_empty() {
return Vec::new();
}
let mut symbols: Vec<Symbol> = Vec::new();
let mut i = 0;
while i < norm.len() {
let clen = utf8_len(norm[i]).min(norm.len() - i);
let idx = symbols.len() as i64;
symbols.push(Symbol {
start: i,
len: clen,
prev: idx - 1,
next: -1, });
i += clen;
}
let n = symbols.len();
for (k, sym) in symbols.iter_mut().enumerate() {
sym.next = if k + 1 < n { (k + 1) as i64 } else { -1 };
}
let mut agenda: BinaryHeap<Pair> = BinaryHeap::new();
let maybe_add = |symbols: &[Symbol], agenda: &mut BinaryHeap<Pair>, left: i64, right: i64| {
if left < 0 || right < 0 {
return;
}
let (l, r) = (left as usize, right as usize);
let start = symbols[l].start;
let size = symbols[l].len + symbols[r].len;
if let Some(id) = vocab.piece_to_id(&norm[start..start + size]) {
if id != vocab.unk_id {
agenda.push(Pair {
score: vocab.score(id),
left: l,
right: r,
size,
});
}
}
};
for k in 0..n.saturating_sub(1) {
maybe_add(&symbols, &mut agenda, k as i64, (k + 1) as i64);
}
while let Some(top) = agenda.pop() {
let (l, r) = (top.left, top.right);
if symbols[l].len == 0 || symbols[r].len == 0 {
continue;
}
if symbols[l].len + symbols[r].len != top.size || symbols[l].next != r as i64 {
continue;
}
symbols[l].len += symbols[r].len;
let r_next = symbols[r].next;
symbols[l].next = r_next;
if r_next >= 0 {
symbols[r_next as usize].prev = l as i64;
}
symbols[r].len = 0;
let l_prev = symbols[l].prev;
maybe_add(&symbols, &mut agenda, l_prev, l as i64);
maybe_add(&symbols, &mut agenda, l as i64, r_next);
}
let mut out = Vec::new();
let mut idx: i64 = 0;
while idx >= 0 {
let s = &symbols[idx as usize];
let bytes = &norm[s.start..s.start + s.len];
let id = vocab.piece_to_id(bytes).unwrap_or(vocab.unk_id);
out.push(Span {
start: s.start,
len: s.len,
id,
});
idx = s.next;
}
out
}
fn utf8_len(b: u8) -> usize {
match b {
0x00..=0x7f => 1,
0xc0..=0xdf => 2,
0xe0..=0xef => 3,
0xf0..=0xf7 => 4,
_ => 1, }
}