use itertools::Itertools;
use std::collections::HashMap;
use std::hash::Hash;
use super::locality_sensitive_hash::*;
struct HashBins<T, LSH, H> {
bins: HashMap<H, Vec<T>>,
hasher: LSH,
}
impl<T, LSH, H> HashBins<T, LSH, H>
where
H: Hash + Eq,
LSH: LocalitySensitiveHasher<Domain = T, Image = H>,
{
pub fn new(hasher: LSH) -> Self {
Self {
bins: Default::default(),
hasher,
}
}
pub fn insert(&mut self, value: T) {
let hash = self.hasher.hash(&value);
self.bins.entry(hash).or_default().push(value)
}
pub fn bin_content(&self, value: &T) -> impl Iterator<Item = &T> {
let hash = self.hasher.hash(value);
self.bins.get(&hash).into_iter().flat_map(|bin| bin.iter())
}
fn bins_sorted_by_distance(
&self,
value: &T,
) -> impl Iterator<Item = (LSH::ImageDistance, &Vec<T>)> {
let hash = self.hasher.hash(value);
let mut other_bins: Vec<_> = self
.bins
.iter()
.map(|(bin_hash, bin)| {
let dist = self.hasher.image_distance(bin_hash, &hash);
(dist, bin)
})
.collect();
other_bins.sort_by(|(h1, _), (h2, _)| h1.cmp(h2));
other_bins.into_iter()
}
pub fn bin_contents_sorted_by_distance(
&self,
value: &T,
) -> impl Iterator<Item = (LSH::ImageDistance, impl Iterator<Item = &T>)> {
self.bins_sorted_by_distance(value)
.map(|(dist, bin_content)| (dist, bin_content.iter()))
}
}
impl<T, LSH, H> HashBins<T, LSH, H>
where
H: Hash + Eq,
LSH: LocalitySensitiveHasher<Domain = T, Image = H>,
T: PartialEq,
{
pub fn remove(&mut self, value: &T) -> Option<T> {
let hash = self.hasher.hash(&value);
let removed = self.bins.get_mut(&hash).and_then(|bin| {
if let Some((idx, _)) = bin.iter().find_position(|x| *x == value) {
Some(bin.swap_remove(idx))
} else {
None
}
});
if let Some(bin) = self.bins.get(&hash) {
if bin.is_empty() {
self.bins.remove(&hash);
}
}
removed
}
}
trait Distance {
type D: Ord;
}
pub struct LSHNearestNeighbourSearch<T, LSH, H> {
hash_bins: HashBins<T, LSH, H>,
}
impl<T, LSH, H> LSHNearestNeighbourSearch<T, LSH, H>
where
H: Hash + Eq,
LSH: LocalitySensitiveHasher<Domain = T, Image = H>,
{
pub fn new(hasher: LSH) -> Self {
Self {
hash_bins: HashBins::new(hasher),
}
}
pub fn insert(&mut self, value: T) {
self.hash_bins.insert(value)
}
}
impl<T, LSH, H> LSHNearestNeighbourSearch<T, LSH, H>
where
T: PartialEq,
H: Hash + Eq,
LSH: LocalitySensitiveHasher<Domain = T, Image = H>,
{
pub fn remove(&mut self, value: &T) -> Option<T> {
self.hash_bins.remove(value)
}
}
impl<T, LSH, H, Dist> LSHNearestNeighbourSearch<T, LSH, H>
where
H: Hash + Eq,
LSH:
LocalitySensitiveHasher<Domain = T, Image = H, DomainDistance = Dist, ImageDistance = Dist>,
T: PartialEq,
Dist: Ord,
{
pub fn approx_nearest_neighbour(&self, value: &T) -> Option<&T> {
self.hash_bins
.bin_content(value)
.filter(|&x| x != value)
.min_by_key(|x| self.hash_bins.hasher.domain_distance(value, x))
}
pub fn exact_nearest_neighbour(&self, value: &T) -> Option<&T> {
let neigbhour_bins = self.hash_bins.bin_contents_sorted_by_distance(value);
let mut nearest_neighbour = None;
let mut nearest_neighbour_distance = None;
for (bin_distance, bin_content) in neigbhour_bins {
if let Some(best_distance) = &nearest_neighbour_distance {
if &bin_distance >= best_distance {
break;
}
}
let nearest_in_bin = bin_content
.filter(|&x| x != value)
.min_by_key(|x| self.hash_bins.hasher.domain_distance(value, x));
if let Some(n) = nearest_in_bin {
let dist = self.hash_bins.hasher.domain_distance(value, n);
let is_better = nearest_neighbour_distance
.as_ref()
.map(|d| &dist < d)
.unwrap_or(true);
if is_better {
nearest_neighbour = Some(n);
nearest_neighbour_distance = Some(dist);
}
}
}
nearest_neighbour
}
}
#[test]
fn test_nearest_neighbour_search() {
let points = [0b0000, 0b0001, 0b0010, 0b0011, 0b0100, 0b0110, 0b0111];
let lsh: HammingLSH<_, usize> = HammingLSH::new(1, &points);
let mut search = LSHNearestNeighbourSearch::new(lsh);
for p in points {
search.insert(p);
}
assert_eq!(search.exact_nearest_neighbour(&0b0000), Some(&0b0001));
assert_eq!(search.exact_nearest_neighbour(&0b0111), Some(&0b0110));
}