use itertools::Itertools as _;
use crate::{frequency, Analyze};
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub struct PossiblePlaintext(String);
impl PossiblePlaintext {
pub fn new(plaintext: &str) -> Self {
Self(plaintext.to_owned())
}
pub fn score(&self) -> f64 {
let ioc_score = 1. - (self.0.index_of_coincidence() - 0.0667).abs() / 0.9333;
let frequency_distribution_score = frequency::distribution_score(&self.0);
let frequency_character_score = frequency::character_score(&self.0);
3. * ioc_score + frequency_character_score + frequency_distribution_score / 5.
}
pub fn text(&self) -> &str {
&self.0
}
pub fn best<T: AsRef<str>>(plaintexts: &[T]) -> Option<String> {
plaintexts
.iter()
.map(|plaintext| Self(plaintext.as_ref().to_owned()))
.max()
.map(|plaintext| plaintext.text().to_owned())
}
pub fn best_n<T: AsRef<str>>(plaintexts: &[T], n: usize) -> anyhow::Result<Vec<String>> {
if n == 0 {
anyhow::bail!("Attempted to get the best 0 plaintexts; Use a natural number instead.");
}
if plaintexts.is_empty() {
anyhow::bail!("Attempted to get the best {n} plaintexts of an empty plaintext list.");
}
let sorted = plaintexts.iter().map(|plaintext| Self(plaintext.as_ref().to_owned())).sorted().rev().collect_vec();
sorted
.get(sorted.len() - n..sorted.len())
.ok_or_else(|| anyhow::anyhow!("Error getting best n plaintexts: Index {n} is out of range of {} plaintexts", plaintexts.len()))
.map(|ok| ok.iter().map(|plaintext| plaintext.text().to_owned()).collect())
}
}
impl std::cmp::PartialOrd for PossiblePlaintext {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Ord for PossiblePlaintext {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.score().total_cmp(&other.score())
}
}