use crate::indexer::schema::PostingEntry;
pub fn min_span(posting_lists: &[Vec<PostingEntry>], doc_id: u32) -> Option<u32> {
if posting_lists.is_empty() {
return None;
}
let mut all_positions: Vec<&Vec<u32>> = Vec::new();
for list in posting_lists {
let positions = list
.iter()
.find(|e| e.doc_id == doc_id)
.map(|e| &e.positions);
match positions {
Some(pos) if !pos.is_empty() => all_positions.push(pos),
_ => return None, }
}
if all_positions.len() == 1 {
return Some(0);
}
minimum_window_span(&all_positions)
}
fn minimum_window_span(lists: &[&Vec<u32>]) -> Option<u32> {
let k = lists.len();
let mut indices = vec![0usize; k];
let mut min_span = u32::MAX;
loop {
let positions: Vec<u32> = (0..k).map(|i| lists[i][indices[i]]).collect();
let lo = *positions.iter().min().unwrap();
let hi = *positions.iter().max().unwrap();
let span = hi - lo;
if span < min_span {
min_span = span;
}
let min_idx = (0..k).min_by_key(|&i| positions[i]).unwrap();
indices[min_idx] += 1;
if indices[min_idx] >= lists[min_idx].len() {
break;
}
}
if min_span == u32::MAX {
None
} else {
Some(min_span)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indexer::schema::PostingEntry;
#[test]
fn test_min_span_single_term() {
let list = vec![PostingEntry {
doc_id: 0,
positions: vec![5, 20, 50],
}];
let result = min_span(&[list], 0);
assert_eq!(result, Some(0));
}
#[test]
fn test_min_span_close_terms() {
let list1 = vec![PostingEntry {
doc_id: 0,
positions: vec![10, 100],
}];
let list2 = vec![PostingEntry {
doc_id: 0,
positions: vec![13, 200],
}];
let result = min_span(&[list1, list2], 0);
assert_eq!(result, Some(3));
}
#[test]
fn test_min_span_missing_doc() {
let list = vec![PostingEntry {
doc_id: 1, positions: vec![5],
}];
let result = min_span(&[list], 0);
assert_eq!(result, None);
}
}