use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::BTreeMap;
#[derive(Clone, Debug, PartialEq)]
pub struct SparseEmbedding {
pub indices: Vec<u32>,
pub values: Vec<f32>,
}
impl SparseEmbedding {
pub fn from_pairs(pairs: impl IntoIterator<Item = (u32, f32)>) -> Self {
let mut folded: BTreeMap<u32, f32> = BTreeMap::new();
for (index, value) in pairs {
if value == 0.0 {
continue;
}
let kept = folded.entry(index).or_insert(value);
if value > *kept {
*kept = value;
}
}
let (indices, values): (Vec<u32>, Vec<f32>) = folded.into_iter().unzip();
Self { indices, values }
}
pub fn len(&self) -> usize {
self.indices.len()
}
pub fn is_empty(&self) -> bool {
self.indices.is_empty()
}
pub fn dot(&self, other: &Self) -> f32 {
let (a, b) = (self.ascending(), other.ascending());
let mut sum = 0.0;
let (mut i, mut j) = (0, 0);
while i < a.indices.len() && j < b.indices.len() {
match a.indices[i].cmp(&b.indices[j]) {
Ordering::Less => i += 1,
Ordering::Greater => j += 1,
Ordering::Equal => {
sum += a.values[i] * b.values[j];
i += 1;
j += 1;
}
}
}
sum
}
fn ascending(&self) -> Cow<'_, Self> {
if self.indices.len() == self.values.len() && self.indices.is_sorted_by(|a, b| a < b) {
Cow::Borrowed(self)
} else {
let pairs = self
.indices
.iter()
.copied()
.zip(self.values.iter().copied());
Cow::Owned(Self::from_pairs(pairs))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_pairs_folds_duplicates_by_maximum_and_drops_only_exact_zero() {
let s =
SparseEmbedding::from_pairs([(7u32, 0.5f32), (3, 0.25), (7, 1.5), (9, 0.0), (11, -0.5)]);
assert_eq!(s.indices, vec![3, 7, 11]);
assert_eq!(s.values, vec![0.25, 1.5, -0.5]);
assert!(SparseEmbedding::from_pairs([]).is_empty());
}
#[test]
fn dot_multiplies_the_shared_keys_only() {
let a = SparseEmbedding::from_pairs([(1u32, 2.0f32), (5, 3.0)]);
let b = SparseEmbedding::from_pairs([(5u32, 4.0f32), (9, 100.0)]);
assert_eq!(a.dot(&b), 12.0);
assert_eq!(a.dot(&SparseEmbedding::from_pairs([])), 0.0);
}
#[test]
fn dot_accepts_indices_in_any_order() {
let unsorted = SparseEmbedding {
indices: vec![5, 1],
values: vec![1.0, 2.0],
};
let sorted = SparseEmbedding::from_pairs([(1u32, 1.0f32), (5, 1.0)]);
assert_eq!(unsorted.dot(&sorted), 3.0);
assert_eq!(sorted.dot(&unsorted), 3.0);
}
#[test]
fn dot_ignores_an_index_without_a_value() {
let ragged = SparseEmbedding {
indices: vec![1, 5],
values: vec![1.0],
};
let sorted = SparseEmbedding::from_pairs([(1u32, 1.0f32), (5, 1.0)]);
assert_eq!(ragged.dot(&sorted), 1.0);
assert_eq!(sorted.dot(&ragged), 1.0);
}
#[test]
fn dot_folds_a_repeated_index_by_maximum() {
let repeated = SparseEmbedding {
indices: vec![1, 1],
values: vec![1.0, 2.0],
};
let one = SparseEmbedding::from_pairs([(1u32, 1.0f32)]);
assert_eq!(repeated.dot(&one), 2.0);
}
}