use crate::store::{Store, TokenId};
pub trait ContextExtractor {
fn extract(&self, store: &Store, sequence: &[TokenId]) -> Vec<(TokenId, TokenId, u64)>;
}
pub fn filter_by_min_level(
store: &Store,
events: Vec<(TokenId, TokenId, u64)>,
min_level: u32,
) -> Vec<(TokenId, TokenId, u64)> {
events
.into_iter()
.filter(|&(token, context, _)| {
store.level_of(token) >= min_level && store.level_of(context) >= min_level
})
.collect()
}
pub struct Neighbor;
impl ContextExtractor for Neighbor {
fn extract(&self, _store: &Store, sequence: &[TokenId]) -> Vec<(TokenId, TokenId, u64)> {
let mut out = Vec::with_capacity(sequence.len().saturating_sub(1) * 2);
for w in sequence.windows(2) {
let (a, b) = (w[0], w[1]);
out.push((a, b, 1));
out.push((b, a, 1));
}
out
}
}
pub struct Parent;
impl ContextExtractor for Parent {
fn extract(&self, store: &Store, _sequence: &[TokenId]) -> Vec<(TokenId, TokenId, u64)> {
let mut out = Vec::new();
for id in 0..store.len() as TokenId {
let (level, children) = store.get(id);
if level == 0 {
continue;
}
let weight = store.count_of(id);
if weight == 0 {
continue;
}
for &c in children {
out.push((c, id, weight));
}
}
out
}
}
pub struct Window {
pub min: usize,
pub max: usize,
}
impl Window {
pub fn new(n: usize) -> Self {
Self::range(1, n)
}
pub fn range(min: usize, max: usize) -> Self {
assert!(min >= 1, "дистанция 0 — это сама позиция, не контекст");
assert!(max >= min, "max обязан быть не меньше min");
Window { min, max }
}
}
impl ContextExtractor for Window {
fn extract(&self, _store: &Store, sequence: &[TokenId]) -> Vec<(TokenId, TokenId, u64)> {
let len = sequence.len();
let mut out = Vec::new();
for i in 0..len {
let lo = i.saturating_sub(self.max);
let hi = (i + self.max + 1).min(len);
for j in lo..hi {
if j == i {
continue;
}
let distance = i.abs_diff(j);
if distance >= self.min && distance <= self.max {
out.push((sequence[i], sequence[j], 1));
}
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn neighbor_emits_both_directions_weight_one() {
let store = Store::new();
let seq = vec![1u32, 2, 3];
let mut got = Neighbor.extract(&store, &seq);
got.sort();
let mut want = vec![(1, 2, 1), (2, 1, 1), (2, 3, 1), (3, 2, 1)];
want.sort();
assert_eq!(got, want);
}
#[test]
fn neighbor_on_short_sequence_emits_nothing() {
let store = Store::new();
assert!(Neighbor.extract(&store, &[]).is_empty());
assert!(Neighbor.extract(&store, &[7]).is_empty());
}
#[test]
fn window_2_matches_hand_computed_pairs() {
let store = Store::new();
let seq = vec![10u32, 20, 30, 40];
let mut got = Window::new(2).extract(&store, &seq);
got.sort();
let mut want = vec![
(10, 20, 1),
(10, 30, 1),
(20, 10, 1),
(20, 30, 1),
(20, 40, 1),
(30, 10, 1),
(30, 20, 1),
(30, 40, 1),
(40, 20, 1),
(40, 30, 1),
];
want.sort();
assert_eq!(got, want);
}
#[test]
fn window_1_matches_neighbor() {
let store = Store::new();
let seq = vec![1u32, 2, 3, 4];
let mut a = Neighbor.extract(&store, &seq);
let mut b = Window::new(1).extract(&store, &seq);
a.sort();
b.sort();
assert_eq!(a, b);
}
#[test]
fn window_range_2_5_excludes_distance_1() {
let store = Store::new();
let seq: Vec<u32> = vec![10, 20, 30, 40, 50, 60, 70];
let got = Window::range(2, 5).extract(&store, &seq);
assert!(
!got.contains(&(40, 30, 1)) && !got.contains(&(40, 50, 1)),
"дистанция 1 обязана быть исключена из range(2,5)"
);
assert!(got.contains(&(40, 20, 1)));
assert!(got.contains(&(40, 60, 1)));
assert!(got.contains(&(40, 10, 1)));
assert!(got.contains(&(40, 70, 1)));
}
#[test]
fn window_range_min_bound_is_inclusive_max_bound_is_inclusive() {
let store = Store::new();
let seq: Vec<u32> = (0..10).collect();
let w = Window::range(2, 5);
let got = w.extract(&store, &seq);
assert!(got.contains(&(5, 3, 1)), "дистанция 2 (нижняя граница) обязана войти");
assert!(got.contains(&(5, 7, 1)), "дистанция 2 (нижняя граница) обязана войти");
assert!(got.contains(&(5, 0, 1)), "дистанция 5 (верхняя граница) обязана войти");
assert!(!got.contains(&(5, 4, 1)), "дистанция 1 обязана быть исключена");
assert!(!got.contains(&(5, 6, 1)), "дистанция 1 обязана быть исключена");
}
#[test]
fn filter_by_min_level_keeps_only_pairs_where_both_sides_qualify() {
let mut store = Store::new();
store.init_factory();
let ab = store.intern(1, &[b'a' as u32, b'b' as u32]); let cd = store.intern(1, &[b'c' as u32, b'd' as u32]); let abcd = store.intern(2, &[ab, cd]);
let events = vec![
(abcd, abcd, 1), (abcd, ab, 1), (ab, cd, 1), (b'a' as u32, abcd, 1), ];
let filtered = filter_by_min_level(&store, events, 2);
assert_eq!(filtered, vec![(abcd, abcd, 1)]);
}
#[test]
fn parent_extracts_children_weighted_by_parent_count() {
let mut store = Store::new();
store.init_factory();
let ab = store.intern(1, &[b'a' as u32, b'b' as u32]);
store.touch(ab);
store.touch(ab);
store.touch(ab);
let cd = store.intern(1, &[b'c' as u32, b'd' as u32]);
let mut got = Parent.extract(&store, &[]);
got.sort();
assert!(got.contains(&(b'a' as u32, ab, 3)));
assert!(got.contains(&(b'b' as u32, ab, 3)));
assert!(!got.iter().any(|&(_, parent, _)| parent == cd));
assert_eq!(got.len(), 2, "уровень 0 и непосещённый cd не должны попасть в вывод");
}
#[test]
fn parent_ignores_level_zero_factory_tokens() {
let mut store = Store::new();
store.init_factory();
for b in 0..256u32 {
store.touch(b);
}
let got = Parent.extract(&store, &[]);
assert!(got.is_empty(), "уровень 0 не имеет родителя, только сам является ребёнком");
}
}