use crate::core::{TimeRange, Timestamp};
use std::collections::BTreeMap;
#[derive(Debug)]
pub struct TemporalIndex<T> {
tree: BTreeMap<i64, Vec<usize>>,
items: Vec<T>,
timestamps: Vec<Timestamp>,
}
impl<T: Clone> TemporalIndex<T> {
pub fn new() -> Self {
Self {
tree: BTreeMap::new(),
items: Vec::new(),
timestamps: Vec::new(),
}
}
pub fn from_iter<I, F>(iter: I, timestamp_fn: F) -> Self
where
I: IntoIterator<Item = T>,
F: Fn(&T) -> &Timestamp,
{
let mut index = Self::new();
for item in iter {
let ts = timestamp_fn(&item).clone();
index.insert(item, &ts);
}
index
}
pub fn insert(&mut self, item: T, timestamp: &Timestamp) {
let idx = self.items.len();
let key = timestamp.to_unix_millis();
self.items.push(item);
self.timestamps.push(timestamp.clone());
self.tree.entry(key).or_default().push(idx);
}
pub fn query_range(&self, range: &TimeRange) -> Vec<&T> {
let start_key = range.start.to_unix_millis();
let end_key = range.end.to_unix_millis();
self.tree
.range(start_key..=end_key)
.flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
.collect()
}
pub fn before(&self, timestamp: &Timestamp) -> Vec<&T> {
let key = timestamp.to_unix_millis();
self.tree
.range(..key)
.flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
.collect()
}
pub fn after(&self, timestamp: &Timestamp) -> Vec<&T> {
let key = timestamp.to_unix_millis();
self.tree
.range((key + 1)..)
.flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
.collect()
}
pub fn at_or_before(&self, timestamp: &Timestamp) -> Vec<&T> {
let key = timestamp.to_unix_millis();
self.tree
.range(..=key)
.flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
.collect()
}
pub fn at_or_after(&self, timestamp: &Timestamp) -> Vec<&T> {
let key = timestamp.to_unix_millis();
self.tree
.range(key..)
.flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
.collect()
}
pub fn first(&self) -> Option<&T> {
self.tree
.iter()
.next()
.and_then(|(_, indices)| indices.first().map(|&i| &self.items[i]))
}
pub fn last(&self) -> Option<&T> {
self.tree
.iter()
.next_back()
.and_then(|(_, indices)| indices.last().map(|&i| &self.items[i]))
}
pub fn chronological(&self) -> Vec<&T> {
self.tree
.iter()
.flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
.collect()
}
pub fn reverse_chronological(&self) -> Vec<&T> {
self.tree
.iter()
.rev()
.flat_map(|(_, indices)| indices.iter().rev().map(|&i| &self.items[i]))
.collect()
}
pub fn time_range(&self) -> Option<TimeRange> {
let first_key = self.tree.keys().next()?;
let last_key = self.tree.keys().next_back()?;
let start = Timestamp::from_unix_millis(*first_key)?;
let end = Timestamp::from_unix_millis(*last_key)?;
Some(TimeRange::new(start, end))
}
pub fn sliding_window(&self, window_size: chrono::Duration) -> SlidingWindowIter<'_, T> {
SlidingWindowIter {
index: self,
window_millis: window_size.num_milliseconds(),
current_start: self.tree.keys().next().copied(),
}
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn items(&self) -> &[T] {
&self.items
}
}
impl<T: Clone> Default for TemporalIndex<T> {
fn default() -> Self {
Self::new()
}
}
pub struct SlidingWindowIter<'a, T> {
index: &'a TemporalIndex<T>,
window_millis: i64,
current_start: Option<i64>,
}
impl<'a, T: Clone> Iterator for SlidingWindowIter<'a, T> {
type Item = Vec<&'a T>;
fn next(&mut self) -> Option<Self::Item> {
let start = self.current_start?;
let end = start + self.window_millis;
self.current_start = self.index.tree.range((start + 1)..).next().map(|(k, _)| *k);
let items: Vec<_> = self
.index
.tree
.range(start..end)
.flat_map(|(_, indices)| indices.iter().map(|&i| &self.index.items[i]))
.collect();
if items.is_empty() && self.current_start.is_some() {
self.next() } else if items.is_empty() {
None
} else {
Some(items)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_timestamp(hour: u32) -> Timestamp {
Timestamp::parse(&format!("2024-01-01T{:02}:00:00Z", hour)).unwrap()
}
#[test]
fn test_temporal_index_new() {
let index: TemporalIndex<String> = TemporalIndex::new();
assert!(index.is_empty());
}
#[test]
fn test_temporal_index_insert() {
let mut index = TemporalIndex::new();
index.insert("Morning", &make_timestamp(9));
index.insert("Noon", &make_timestamp(12));
index.insert("Evening", &make_timestamp(18));
assert_eq!(index.len(), 3);
}
#[test]
fn test_temporal_index_query_range() {
let mut index = TemporalIndex::new();
index.insert("9am", &make_timestamp(9));
index.insert("12pm", &make_timestamp(12));
index.insert("3pm", &make_timestamp(15));
index.insert("6pm", &make_timestamp(18));
let range = TimeRange::new(make_timestamp(11), make_timestamp(16));
let results = index.query_range(&range);
assert_eq!(results.len(), 2);
}
#[test]
fn test_temporal_index_before_after() {
let mut index = TemporalIndex::new();
index.insert("9am", &make_timestamp(9));
index.insert("12pm", &make_timestamp(12));
index.insert("3pm", &make_timestamp(15));
let before = index.before(&make_timestamp(12));
assert_eq!(before.len(), 1);
assert_eq!(*before[0], "9am");
let after = index.after(&make_timestamp(12));
assert_eq!(after.len(), 1);
assert_eq!(*after[0], "3pm");
}
#[test]
fn test_temporal_index_first_last() {
let mut index = TemporalIndex::new();
index.insert("First", &make_timestamp(8));
index.insert("Middle", &make_timestamp(12));
index.insert("Last", &make_timestamp(20));
assert_eq!(index.first(), Some(&"First"));
assert_eq!(index.last(), Some(&"Last"));
}
#[test]
fn test_temporal_index_chronological() {
let mut index = TemporalIndex::new();
index.insert("C", &make_timestamp(15));
index.insert("A", &make_timestamp(9));
index.insert("B", &make_timestamp(12));
let ordered: Vec<_> = index.chronological();
assert_eq!(ordered, vec![&"A", &"B", &"C"]);
}
}