use std::{collections::HashMap, ops::Range};
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use crate::common::{Documents, WindowSize};
type Words<'a> = &'a [String];
pub struct CoOccurrence {
matrix: Vec<Vec<f32>>,
words: Vec<String>,
words_indexes: HashMap<String, usize>,
}
fn get_window_range(window_size: usize, index: usize, words_length: usize) -> Range<usize> {
let window_start = index.saturating_sub(window_size);
let window_end = (index + window_size + 1).min(words_length);
window_start..window_end
}
fn create_words_indexes(words: &[String]) -> HashMap<String, usize> {
#[cfg(feature = "parallel")]
{
words
.par_iter()
.enumerate()
.map(|(i, w)| (w.to_string(), i))
.collect::<HashMap<String, usize>>()
}
#[cfg(not(feature = "parallel"))]
{
words
.iter()
.enumerate()
.map(|(i, w)| (w.to_string(), i))
.collect::<HashMap<String, usize>>()
}
}
fn get_matrix(
documents: &[String],
words_indexes: &HashMap<String, usize>,
length: usize,
window_size: usize,
) -> Vec<Vec<f32>> {
let mut matrix = vec![vec![0.0_f32; length]; length];
let mut max = 0.0_f32;
documents.iter().for_each(|doc| {
let doc_words = doc.split_whitespace().collect::<Vec<&str>>();
doc_words
.iter()
.enumerate()
.filter_map(|(i, w)| words_indexes.get(*w).map(|first_index| (i, *first_index)))
.for_each(|(i, first_index)| {
get_window_range(window_size, i, doc_words.len())
.filter_map(|j| {
if i == j {
return None;
}
doc_words
.get(j)
.and_then(|other_word| words_indexes.get(*other_word))
})
.for_each(|other_index| {
matrix[first_index][*other_index] += 1.0;
let current = matrix[first_index][*other_index];
if current > max {
max = current;
}
});
});
});
#[cfg(feature = "parallel")]
matrix
.par_iter_mut()
.flat_map(|row| row.par_iter_mut())
.for_each(|value| *value /= max);
#[cfg(not(feature = "parallel"))]
matrix
.iter_mut()
.flat_map(|row| row.iter_mut())
.for_each(|value| *value /= max);
matrix
}
impl CoOccurrence {
pub fn new(documents: Documents, words: Words, window_size: WindowSize) -> Self {
let words_indexes = create_words_indexes(words);
let length = words.len();
Self {
matrix: get_matrix(documents, &words_indexes, length, window_size),
words: words.to_vec(),
words_indexes,
}
}
pub fn get_label(&self, word: &str) -> Option<usize> {
self.words_indexes.get(word).map(|w| w.to_owned())
}
pub fn get_word(&self, label: usize) -> Option<String> {
self.words.get(label).map(|w| w.to_owned())
}
pub fn get_matrix(&self) -> &Vec<Vec<f32>> {
&self.matrix
}
pub fn get_labels(&self) -> &HashMap<String, usize> {
&self.words_indexes
}
pub fn get_relations(&self, word: &str) -> Option<Vec<(String, f32)>> {
let label = match self.get_label(word) {
Some(l) => l,
None => return None,
};
#[cfg(feature = "parallel")]
{
Some(
self.matrix[label]
.par_iter()
.enumerate()
.filter_map(|(i, &v)| {
if v > 0.0 {
if let Some(w) = self.get_word(i) {
return Some((w, v));
}
}
None
})
.collect::<Vec<(String, f32)>>(),
)
}
#[cfg(not(feature = "parallel"))]
{
Some(
self.matrix[label]
.iter()
.enumerate()
.filter_map(|(i, &v)| {
if v > 0.0 {
if let Some(w) = self.get_word(i) {
return Some((w, v));
}
}
None
})
.collect::<Vec<(String, f32)>>(),
)
}
}
pub fn get_matrix_row(&self, word: &str) -> Option<Vec<f32>> {
let label = match self.get_label(word) {
Some(l) => l,
None => return None,
};
Some(self.matrix[label].to_owned())
}
pub fn get_relation(&self, word1: &str, word2: &str) -> Option<f32> {
let label1 = match self.get_label(word1) {
Some(l) => l,
None => return None,
};
let label2 = match self.get_label(word2) {
Some(l) => l,
None => return None,
};
Some(self.matrix[label1][label2])
}
}