use crate::common::{NoScoreCutoff, SimilarityCutoff, WithScoreCutoff};
use crate::details::distance::MetricUsize;
use crate::distance::indel;
use crate::HashableChar;
#[must_use]
#[derive(Clone, Copy, Debug)]
pub struct Args<ResultType, CutoffType> {
score_cutoff: CutoffType,
score_hint: Option<ResultType>,
}
impl<ResultType> Default for Args<ResultType, NoScoreCutoff> {
fn default() -> Args<ResultType, NoScoreCutoff> {
Args {
score_cutoff: NoScoreCutoff,
score_hint: None,
}
}
}
impl<ResultType, CutoffType> Args<ResultType, CutoffType> {
pub fn score_hint(mut self, score_hint: ResultType) -> Self {
self.score_hint = Some(score_hint);
self
}
pub fn score_cutoff(
self,
score_cutoff: ResultType,
) -> Args<ResultType, WithScoreCutoff<ResultType>> {
Args {
score_hint: self.score_hint,
score_cutoff: WithScoreCutoff(score_cutoff),
}
}
}
pub fn ratio<Iter1, Iter2>(s1: Iter1, s2: Iter2) -> f64
where
Iter1: IntoIterator,
Iter1::IntoIter: DoubleEndedIterator + Clone,
Iter2: IntoIterator,
Iter2::IntoIter: DoubleEndedIterator + Clone,
Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
{
ratio_with_args(s1, s2, &Args::default())
}
pub fn ratio_with_args<Iter1, Iter2, CutoffType>(
s1: Iter1,
s2: Iter2,
args: &Args<f64, CutoffType>,
) -> CutoffType::Output
where
Iter1: IntoIterator,
Iter1::IntoIter: DoubleEndedIterator + Clone,
Iter2: IntoIterator,
Iter2::IntoIter: DoubleEndedIterator + Clone,
Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
CutoffType: SimilarityCutoff<f64>,
{
let s1_iter = s1.into_iter();
let s2_iter = s2.into_iter();
args.score_cutoff
.score(indel::IndividualComparator {}._normalized_similarity(
s1_iter.clone(),
s1_iter.count(),
s2_iter.clone(),
s2_iter.count(),
args.score_cutoff.cutoff(),
args.score_hint,
))
}
pub struct RatioBatchComparator<Elem1> {
scorer: indel::BatchComparator<Elem1>,
}
impl<Elem1> RatioBatchComparator<Elem1>
where
Elem1: HashableChar + Clone,
{
pub fn new<Iter1>(s1: Iter1) -> Self
where
Iter1: IntoIterator<Item = Elem1>,
Iter1::IntoIter: Clone,
{
Self {
scorer: indel::BatchComparator::new(s1),
}
}
pub fn similarity<Iter2>(&self, s2: Iter2) -> f64
where
Iter2: IntoIterator,
Iter2::IntoIter: DoubleEndedIterator + Clone,
Elem1: PartialEq<Iter2::Item> + HashableChar + Copy,
Iter2::Item: PartialEq<Elem1> + HashableChar + Copy,
{
self.similarity_with_args(s2, &Args::default())
}
pub fn similarity_with_args<Iter2, CutoffType>(
&self,
s2: Iter2,
args: &Args<f64, CutoffType>,
) -> CutoffType::Output
where
Iter2: IntoIterator,
Iter2::IntoIter: DoubleEndedIterator + Clone,
Elem1: PartialEq<Iter2::Item> + HashableChar + Copy,
Iter2::Item: PartialEq<Elem1> + HashableChar + Copy,
CutoffType: SimilarityCutoff<f64>,
{
let s2_iter = s2.into_iter();
args.score_cutoff
.score(self.scorer.scorer._normalized_similarity(
self.scorer.scorer.s1.iter().copied(),
self.scorer.scorer.s1.len(),
s2_iter.clone(),
s2_iter.count(),
args.score_cutoff.cutoff(),
args.score_hint,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
static S1: &str = "new york mets";
static S3: &str = "the wonderful new york mets";
static S8: &str = "{";
static S9: &str = "{a";
macro_rules! assert_delta {
($x:expr, $y:expr) => {
match ($x, $y) {
(None, None) => {}
(Some(val1), Some(val2)) => {
if (val1 - val2).abs() > 0.0001 {
panic!("{:?} != {:?}", $x, $y);
}
}
(_, _) => panic!("{:?} != {:?}", $x, $y),
}
};
}
#[test]
fn test_equal() {
assert_delta!(
Some(1.0),
Some(ratio_with_args(S1.chars(), S1.chars(), &Args::default()))
);
assert_delta!(
Some(1.0),
Some(ratio_with_args(
"test".chars(),
"test".chars(),
&Args::default()
))
);
assert_delta!(
Some(1.0),
Some(ratio_with_args(S8.chars(), S8.chars(), &Args::default()))
);
assert_delta!(
Some(1.0),
Some(ratio_with_args(S9.chars(), S9.chars(), &Args::default()))
);
}
#[test]
fn test_partial_ratio() {
assert_delta!(
Some(0.65),
Some(ratio_with_args(S1.chars(), S3.chars(), &Args::default()))
);
}
#[test]
fn two_empty_strings() {
assert_delta!(
Some(1.0),
Some(ratio_with_args("".chars(), "".chars(), &Args::default()))
);
}
#[test]
fn first_string_empty() {
assert_delta!(
Some(0.0),
Some(ratio_with_args(
"test".chars(),
"".chars(),
&Args::default()
))
);
}
#[test]
fn second_string_empty() {
assert_delta!(
Some(0.0),
Some(ratio_with_args(
"".chars(),
"test".chars(),
&Args::default()
))
);
}
#[test]
fn issue206() {
let str1 = "South Korea";
let str2 = "North Korea";
{
let score = ratio(str1.chars(), str2.chars());
assert_eq!(
None,
ratio_with_args(
str1.chars(),
str2.chars(),
&Args::default().score_cutoff(score + 0.0001)
)
);
assert_delta!(
Some(score),
ratio_with_args(
str1.chars(),
str2.chars(),
&Args::default().score_cutoff(score - 0.0001)
)
);
}
}
#[test]
fn issue210() {
let str1 = "bc";
let str2 = "bca";
{
let score = ratio(str1.chars(), str2.chars());
assert_eq!(
None,
ratio_with_args(
str1.chars(),
str2.chars(),
&Args::default().score_cutoff(score + 0.0001)
)
);
assert_delta!(
Some(score),
ratio_with_args(
str1.chars(),
str2.chars(),
&Args::default().score_cutoff(score - 0.0001)
)
);
}
}
}