use std::collections::{HashMap, HashSet};
pub const SEQUENCE_LEN: usize = 3;
pub const SEQUENCE_MIN_SESSIONS: usize = 3;
#[derive(Debug, Clone, PartialEq)]
pub struct SkillSequenceCandidate {
pub sequence: Vec<String>,
pub sessions: usize,
}
pub fn skill_sequence_candidates(
sessions: &[Vec<String>],
n: usize,
min_sessions: usize,
) -> Vec<SkillSequenceCandidate> {
if n == 0 {
return Vec::new();
}
let mut by_ngram: HashMap<Vec<String>, HashSet<usize>> = HashMap::new();
for (idx, seq) in sessions.iter().enumerate() {
if seq.len() < n {
continue;
}
let mut seen_here: HashSet<Vec<String>> = HashSet::new();
for window in seq.windows(n) {
if window.iter().all(|t| t == &window[0]) {
continue;
}
let ngram = window.to_vec();
if seen_here.insert(ngram.clone()) {
by_ngram.entry(ngram).or_default().insert(idx);
}
}
}
let mut out: Vec<SkillSequenceCandidate> = by_ngram
.into_iter()
.filter(|(_, sessions)| sessions.len() >= min_sessions)
.map(|(sequence, sessions)| SkillSequenceCandidate {
sequence,
sessions: sessions.len(),
})
.collect();
out.sort_by(|a, b| {
b.sessions
.cmp(&a.sessions)
.then_with(|| a.sequence.cmp(&b.sequence))
});
out
}
pub fn group_sessions(rows: &[(String, String)]) -> Vec<Vec<String>> {
let mut out: Vec<Vec<String>> = Vec::new();
let mut current_id: Option<&str> = None;
for (sid, tool) in rows {
if current_id != Some(sid.as_str()) {
out.push(Vec::new());
current_id = Some(sid.as_str());
}
out.last_mut().expect("pushed above").push(tool.clone());
}
out
}