use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CharacteristicVector {
bits: Vec<bool>,
}
impl CharacteristicVector {
pub fn new(character: char, word: &str) -> Self {
let bits = word.chars().map(|c| c == character).collect();
Self { bits }
}
#[inline]
pub fn len(&self) -> usize {
self.bits.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.bits.is_empty()
}
#[inline]
pub fn is_match(&self, position: usize) -> bool {
self.bits[position]
}
pub fn iter(&self) -> impl Iterator<Item = bool> + '_ {
self.bits.iter().copied()
}
#[inline]
pub fn starts_with_one(&self) -> bool {
self.bits.first().copied().unwrap_or(false)
}
pub fn first_match(&self) -> Option<usize> {
self.bits.iter().position(|&bit| bit)
}
pub fn suffix(&self, start: usize) -> Self {
Self {
bits: self.bits[start..].to_vec(),
}
}
pub fn is_all_zeros(&self) -> bool {
self.bits.iter().all(|&bit| !bit)
}
pub fn starts_with(&self, pattern: &[bool]) -> bool {
if pattern.len() > self.bits.len() {
return false;
}
self.bits[..pattern.len()] == *pattern
}
}
impl fmt::Display for CharacteristicVector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for bit in &self.bits {
write!(f, "{}", if *bit { '1' } else { '0' })?;
}
Ok(())
}
}
pub fn characteristic_vector(character: char, word: &str) -> CharacteristicVector {
CharacteristicVector::new(character, word)
}
const PADDING_CHAR: char = '$';
fn relevant_subword(word: &str, position: usize, max_distance: u8) -> String {
let n = max_distance as i32;
let p = word.len() as i32;
let i = position as i32;
let start = i - n;
let end = i32::min(p, i + n);
let mut result = String::new();
for j in start..=end {
if j < 1 {
result.push(PADDING_CHAR);
} else if j <= p {
let chars: Vec<char> = word.chars().collect();
result.push(chars[(j - 1) as usize]);
}
}
result
}
pub fn encode_word_pair(
word: &str,
input: &str,
max_distance: u8,
) -> Option<Vec<CharacteristicVector>> {
let t = input.len();
let p = word.len();
let n = max_distance as usize;
if t > p + n {
return None;
}
let input_chars: Vec<char> = input.chars().collect();
let mut encoding = Vec::with_capacity(t);
for i in 1..=t {
let subword = relevant_subword(word, i, max_distance);
let character = input_chars[i - 1];
let cv = characteristic_vector(character, &subword);
encoding.push(cv);
}
Some(encoding)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_characteristic_vector_basic() {
let cv = CharacteristicVector::new('a', "banana");
assert_eq!(cv.len(), 6);
assert_eq!(cv.to_string(), "010101");
}
#[test]
fn test_characteristic_vector_no_matches() {
let cv = CharacteristicVector::new('x', "banana");
assert_eq!(cv.to_string(), "000000");
assert!(cv.is_all_zeros());
}
#[test]
fn test_characteristic_vector_all_matches() {
let cv = CharacteristicVector::new('a', "aaaa");
assert_eq!(cv.to_string(), "1111");
assert!(!cv.is_all_zeros());
}
#[test]
fn test_characteristic_vector_empty() {
let cv = CharacteristicVector::new('a', "");
assert_eq!(cv.len(), 0);
assert!(cv.is_empty());
assert_eq!(cv.to_string(), "");
}
#[test]
fn test_characteristic_vector_single_char() {
let cv1 = CharacteristicVector::new('a', "a");
assert_eq!(cv1.to_string(), "1");
let cv2 = CharacteristicVector::new('b', "a");
assert_eq!(cv2.to_string(), "0");
}
#[test]
fn test_is_match() {
let cv = CharacteristicVector::new('a', "banana");
assert_eq!(cv.is_match(0), false); assert_eq!(cv.is_match(1), true); assert_eq!(cv.is_match(2), false); assert_eq!(cv.is_match(3), true); assert_eq!(cv.is_match(4), false); assert_eq!(cv.is_match(5), true); }
#[test]
fn test_starts_with_one() {
let cv1 = CharacteristicVector::new('b', "banana");
assert_eq!(cv1.starts_with_one(), true);
let cv2 = CharacteristicVector::new('a', "apple");
assert_eq!(cv2.starts_with_one(), true);
let cv3 = CharacteristicVector::new('x', "");
assert_eq!(cv3.starts_with_one(), false);
let cv4 = CharacteristicVector::new('a', "banana");
assert_eq!(cv4.starts_with_one(), false); }
#[test]
fn test_first_match() {
let cv1 = CharacteristicVector::new('a', "banana");
assert_eq!(cv1.first_match(), Some(1));
let cv2 = CharacteristicVector::new('b', "banana");
assert_eq!(cv2.first_match(), Some(0));
let cv3 = CharacteristicVector::new('n', "banana");
assert_eq!(cv3.first_match(), Some(2));
let cv4 = CharacteristicVector::new('x', "banana");
assert_eq!(cv4.first_match(), None); }
#[test]
fn test_suffix() {
let cv = CharacteristicVector::new('a', "banana");
let suffix = cv.suffix(2);
assert_eq!(suffix.to_string(), "0101");
assert_eq!(suffix.len(), 4);
}
#[test]
fn test_starts_with_pattern() {
let cv = CharacteristicVector::new('a', "banana");
assert!(cv.starts_with(&[false]));
assert!(cv.starts_with(&[false, true]));
assert!(cv.starts_with(&[false, true, false]));
assert!(!cv.starts_with(&[true]));
assert!(!cv.starts_with(&[false, false]));
assert!(!cv.starts_with(&[true, true]));
}
#[test]
fn test_iter() {
let cv = CharacteristicVector::new('a', "aba");
let bits: Vec<bool> = cv.iter().collect();
assert_eq!(bits, vec![true, false, true]);
}
#[test]
fn test_characteristic_vector_function() {
let cv = characteristic_vector('a', "banana");
assert_eq!(cv.to_string(), "010101");
}
#[test]
fn test_relevant_subword_with_padding() {
let subword = relevant_subword("abc", 1, 2);
assert_eq!(subword, "$$abc");
}
#[test]
fn test_relevant_subword_middle() {
let subword = relevant_subword("abcdef", 3, 2);
assert_eq!(subword, "abcde");
}
#[test]
fn test_relevant_subword_end() {
let subword = relevant_subword("abc", 3, 2);
assert_eq!(subword, "abc");
}
#[test]
fn test_relevant_subword_all_padding() {
let subword = relevant_subword("a", 1, 3);
assert_eq!(subword, "$$$a");
}
#[test]
fn test_encode_word_pair_example_from_thesis() {
let encoding = encode_word_pair("abcabb", "dacab", 3)
.expect("doc/test fixture: encode_word_pair with valid args");
assert_eq!(encoding.len(), 5);
assert_eq!(encoding[0].to_string(), "0000000");
assert_eq!(encoding[1].to_string(), "0010010");
assert_eq!(encoding[2].to_string(), "0001000");
assert_eq!(encoding[3].to_string(), "100100");
assert_eq!(encoding[4].to_string(), "10011");
}
#[test]
fn test_encode_word_pair_simple() {
let encoding = encode_word_pair("ab", "ab", 1)
.expect("doc/test fixture: encode_word_pair with valid args");
assert_eq!(encoding.len(), 2);
assert_eq!(encoding[0].to_string(), "010");
assert_eq!(encoding[1].to_string(), "01");
}
#[test]
fn test_encode_word_pair_exact_match() {
let encoding = encode_word_pair("test", "test", 0)
.expect("doc/test fixture: encode_word_pair with valid args");
assert_eq!(encoding.len(), 4);
assert_eq!(encoding[0].to_string(), "1"); assert_eq!(encoding[1].to_string(), "1"); assert_eq!(encoding[2].to_string(), "1"); assert_eq!(encoding[3].to_string(), "1"); }
#[test]
fn test_encode_word_pair_too_long_input() {
let encoding = encode_word_pair("ab", "abcd", 1);
assert!(encoding.is_none());
}
#[test]
fn test_encode_word_pair_empty_input() {
let encoding = encode_word_pair("test", "", 2)
.expect("doc/test fixture: encode_word_pair with valid args");
assert_eq!(encoding.len(), 0);
}
#[test]
fn test_encode_word_pair_single_character() {
let encoding = encode_word_pair("a", "a", 1)
.expect("doc/test fixture: encode_word_pair with valid args");
assert_eq!(encoding.len(), 1);
assert_eq!(encoding[0].to_string(), "01");
}
#[test]
fn test_encode_word_pair_no_matches() {
let encoding = encode_word_pair("aaa", "bbb", 1)
.expect("doc/test fixture: encode_word_pair with valid args");
assert_eq!(encoding.len(), 3);
for cv in encoding {
assert!(cv.is_all_zeros());
}
}
#[test]
fn test_encode_word_pair_max_distance_zero() {
let encoding = encode_word_pair("abc", "xyz", 0)
.expect("doc/test fixture: encode_word_pair with valid args");
assert_eq!(encoding.len(), 3);
assert_eq!(encoding[0].to_string(), "0"); assert_eq!(encoding[1].to_string(), "0"); assert_eq!(encoding[2].to_string(), "0"); }
#[test]
fn test_characteristic_vector_equality() {
let cv1 = CharacteristicVector::new('a', "banana");
let cv2 = CharacteristicVector::new('a', "banana");
let cv3 = CharacteristicVector::new('b', "banana");
assert_eq!(cv1, cv2);
assert_ne!(cv1, cv3);
}
#[test]
fn test_characteristic_vector_clone() {
let cv = CharacteristicVector::new('a', "banana");
let cloned = cv.clone();
assert_eq!(cv, cloned);
assert_eq!(cv.to_string(), cloned.to_string());
}
}