use crate::transducer::Algorithm;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PatternPiece {
pub content: String,
pub start_offset: usize,
pub end_offset: usize,
pub piece_index: usize,
}
impl PatternPiece {
pub fn new(
content: String,
start_offset: usize,
end_offset: usize,
piece_index: usize,
) -> Self {
PatternPiece {
content,
start_offset,
end_offset,
piece_index,
}
}
#[inline]
pub fn len(&self) -> usize {
self.content.chars().count()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.content.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct PatternSplitter {
max_distance: usize,
algorithm: Algorithm,
}
impl PatternSplitter {
pub fn new(max_distance: usize, algorithm: Algorithm) -> Self {
PatternSplitter {
max_distance,
algorithm,
}
}
pub fn standard(max_distance: usize) -> Self {
Self::new(max_distance, Algorithm::Standard)
}
pub fn split(&self, query: &str) -> Vec<PatternPiece> {
let chars: Vec<char> = query.chars().collect();
let query_len = chars.len();
if query_len == 0 {
return Vec::new();
}
let num_pieces = self.num_pieces();
if query_len < num_pieces {
return chars
.iter()
.enumerate()
.map(|(i, &c)| PatternPiece::new(c.to_string(), i, i + 1, i))
.collect();
}
let base_size = query_len / num_pieces;
let remainder = query_len % num_pieces;
let mut pieces = Vec::with_capacity(num_pieces);
let mut start = 0;
for i in 0..num_pieces {
let piece_size = base_size + if i < remainder { 1 } else { 0 };
let end = start + piece_size;
let content: String = chars[start..end].iter().collect();
pieces.push(PatternPiece::new(content, start, end, i));
start = end;
}
pieces
}
#[inline]
pub fn num_pieces(&self) -> usize {
match self.algorithm {
Algorithm::Standard => self.max_distance + 1,
Algorithm::Transposition => 2 * self.max_distance + 1,
Algorithm::MergeAndSplit => 2 * self.max_distance + 1,
}
}
#[inline]
pub fn algorithm(&self) -> Algorithm {
self.algorithm
}
#[inline]
pub fn max_distance(&self) -> usize {
self.max_distance
}
#[inline]
pub fn min_piece_length(&self, query_len: usize) -> usize {
if query_len < self.num_pieces() {
1
} else {
query_len / self.num_pieces()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_split_even_standard() {
let splitter = PatternSplitter::standard(2);
let pieces = splitter.split("cathedral");
assert_eq!(pieces.len(), 3);
assert_eq!(pieces[0].content, "cat");
assert_eq!(pieces[1].content, "hed");
assert_eq!(pieces[2].content, "ral");
assert_eq!(pieces[0].start_offset, 0);
assert_eq!(pieces[0].end_offset, 3);
assert_eq!(pieces[1].start_offset, 3);
assert_eq!(pieces[1].end_offset, 6);
assert_eq!(pieces[2].start_offset, 6);
assert_eq!(pieces[2].end_offset, 9);
}
#[test]
fn test_split_uneven_standard() {
let splitter = PatternSplitter::standard(2);
let pieces = splitter.split("hello");
assert_eq!(pieces.len(), 3);
assert_eq!(pieces[0].content, "he"); assert_eq!(pieces[1].content, "ll"); assert_eq!(pieces[2].content, "o"); }
#[test]
fn test_split_short_query() {
let splitter = PatternSplitter::standard(5);
let pieces = splitter.split("abc");
assert_eq!(pieces.len(), 3);
assert_eq!(pieces[0].content, "a");
assert_eq!(pieces[1].content, "b");
assert_eq!(pieces[2].content, "c");
}
#[test]
fn test_split_empty() {
let splitter = PatternSplitter::standard(2);
let pieces = splitter.split("");
assert!(pieces.is_empty());
}
#[test]
fn test_split_single_char() {
let splitter = PatternSplitter::standard(0);
let pieces = splitter.split("x");
assert_eq!(pieces.len(), 1);
assert_eq!(pieces[0].content, "x");
}
#[test]
fn test_split_unicode() {
let splitter = PatternSplitter::standard(2);
let pieces = splitter.split("café🎉");
assert_eq!(pieces.len(), 3);
assert_eq!(pieces[0].content, "ca"); assert_eq!(pieces[1].content, "fé"); assert_eq!(pieces[2].content, "🎉"); }
#[test]
fn test_piece_indices() {
let splitter = PatternSplitter::standard(2);
let pieces = splitter.split("abcdef");
for (i, piece) in pieces.iter().enumerate() {
assert_eq!(piece.piece_index, i);
}
}
#[test]
fn test_min_piece_length_standard() {
let splitter = PatternSplitter::standard(2);
assert_eq!(splitter.min_piece_length(9), 3); assert_eq!(splitter.min_piece_length(10), 3); assert_eq!(splitter.min_piece_length(2), 1); }
#[test]
fn test_num_pieces_standard() {
assert_eq!(PatternSplitter::new(0, Algorithm::Standard).num_pieces(), 1);
assert_eq!(PatternSplitter::new(1, Algorithm::Standard).num_pieces(), 2);
assert_eq!(PatternSplitter::new(2, Algorithm::Standard).num_pieces(), 3);
assert_eq!(PatternSplitter::new(5, Algorithm::Standard).num_pieces(), 6);
}
#[test]
fn test_num_pieces_transposition() {
assert_eq!(
PatternSplitter::new(0, Algorithm::Transposition).num_pieces(),
1
);
assert_eq!(
PatternSplitter::new(1, Algorithm::Transposition).num_pieces(),
3
);
assert_eq!(
PatternSplitter::new(2, Algorithm::Transposition).num_pieces(),
5
);
assert_eq!(
PatternSplitter::new(5, Algorithm::Transposition).num_pieces(),
11
);
}
#[test]
fn test_num_pieces_merge_and_split() {
assert_eq!(
PatternSplitter::new(0, Algorithm::MergeAndSplit).num_pieces(),
1
);
assert_eq!(
PatternSplitter::new(1, Algorithm::MergeAndSplit).num_pieces(),
3
);
assert_eq!(
PatternSplitter::new(2, Algorithm::MergeAndSplit).num_pieces(),
5
);
assert_eq!(
PatternSplitter::new(5, Algorithm::MergeAndSplit).num_pieces(),
11
);
}
#[test]
fn test_split_transposition_more_pieces() {
let splitter = PatternSplitter::new(2, Algorithm::Transposition);
let pieces = splitter.split("cathedral");
assert_eq!(pieces.len(), 5);
assert_eq!(pieces[0].content, "ca"); assert_eq!(pieces[1].content, "th"); assert_eq!(pieces[2].content, "ed"); assert_eq!(pieces[3].content, "ra"); assert_eq!(pieces[4].content, "l"); }
#[test]
fn test_split_merge_and_split_more_pieces() {
let splitter = PatternSplitter::new(2, Algorithm::MergeAndSplit);
let pieces = splitter.split("cathedral");
assert_eq!(pieces.len(), 5);
assert_eq!(pieces[0].content, "ca");
assert_eq!(pieces[1].content, "th");
assert_eq!(pieces[2].content, "ed");
assert_eq!(pieces[3].content, "ra");
assert_eq!(pieces[4].content, "l");
}
#[test]
fn test_algorithm_getter() {
let standard = PatternSplitter::standard(2);
assert!(matches!(standard.algorithm(), Algorithm::Standard));
let transposition = PatternSplitter::new(2, Algorithm::Transposition);
assert!(matches!(
transposition.algorithm(),
Algorithm::Transposition
));
let merge_split = PatternSplitter::new(2, Algorithm::MergeAndSplit);
assert!(matches!(merge_split.algorithm(), Algorithm::MergeAndSplit));
}
#[test]
fn test_min_piece_length_transposition() {
let splitter = PatternSplitter::new(2, Algorithm::Transposition);
assert_eq!(splitter.min_piece_length(10), 2); assert_eq!(splitter.min_piece_length(15), 3); assert_eq!(splitter.min_piece_length(4), 1); }
}