use std::fmt;
use std::collections::HashSet;
#[derive(Debug, Clone, Eq)]
pub struct TranslationalEquivalence {
pub pattern: Vec<(u32, u32)>,
pub translators: HashSet<(i32, i32)>,
pub sub_tecs: Vec<TranslationalEquivalence>,
}
impl PartialEq for TranslationalEquivalence {
fn eq(&self, other: &Self) -> bool {
self.pattern == other.pattern && self.translators == other.translators && self.sub_tecs == other.sub_tecs
}
}
impl TranslationalEquivalence {
pub fn new(
pattern: Vec<(u32, u32)>,
translators: HashSet<(i32, i32)>,
sub_tecs: Option<Vec<TranslationalEquivalence>>,
) -> Self {
let mut pattern_sorted = pattern;
pattern_sorted.sort();
TranslationalEquivalence {
pattern: pattern_sorted,
translators,
sub_tecs: sub_tecs.unwrap_or_else(Vec::new),
}
}
pub fn coverage(&self) -> HashSet<(u32, u32)> {
let mut sub_cov: HashSet<(u32, u32)> = self.pattern.iter().copied().collect();
for sub in &self.sub_tecs {
sub_cov.extend(sub.coverage());
}
let mut cov = sub_cov.clone();
for &(dx, dy) in &self.translators {
for &(x, y) in &sub_cov {
cov.insert(((x as i32 + dx) as u32, (y as i32 + dy) as u32));
}
}
cov
}
pub fn compression_ratio(&self) -> f64 {
fn total_encoding_units(tec: &TranslationalEquivalence) -> usize {
if tec.sub_tecs.is_empty() {
tec.pattern.len() + tec.translators.len()
} else {
let mut units = tec.sub_tecs.len();
for sub in &tec.sub_tecs {
units += total_encoding_units(sub);
}
units
}
}
let cov_len = self.coverage().len();
let total_units = total_encoding_units(&self);
return cov_len as f64 / total_units as f64;
}
pub fn compactness(&self, points: &HashSet<(u32, u32)>) -> f64 {
let pattern_set = self.coverage();
if pattern_set.is_empty() {
return 0.0;
}
let xs: Vec<u32> = pattern_set.iter().map(|p| p.0).collect();
let ys: Vec<u32> = pattern_set.iter().map(|p| p.1).collect();
let min_x = *xs.iter().min().unwrap();
let max_x = *xs.iter().max().unwrap();
let min_y = *ys.iter().min().unwrap();
let max_y = *ys.iter().max().unwrap();
let mut count = 0;
for &(x, y) in points {
if x >= min_x && x <= max_x && y >= min_y && y <= max_y {
count += 1;
}
}
if count == 0 {
0.0
} else {
self.pattern.len() as f64 / count as f64
}
}
pub fn summary(&self, indent: usize) -> String {
let mut lines: Vec<String> = Vec::new();
let spaces = " ".repeat(indent);
if self.pattern.is_empty() {
lines.push(format!("{}translators={:?}", spaces, self.translators))
} else {
lines.push(format!("{}pattern={:?}, translators={:?}", spaces, self.pattern, self.translators))
}
lines.push(format!("{} coverage count: {}", spaces, self.coverage().len()));
lines.push(format!("{} compression ratio: {}", spaces, self.compression_ratio()));
if !self.sub_tecs.is_empty() {
lines.push(format!("{} sub-tecs:", spaces));
for sub in &self.sub_tecs {
let sub_summary = sub.summary(indent + 2);
lines.push(sub_summary);
}
}
return lines.join("\n");
}
}
impl fmt::Display for TranslationalEquivalence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f, "TEC(pattern={:?}, translators={:?}, sub_tecs=[{}])",
self.pattern,
self.translators,
Vec::from_iter(self.sub_tecs.iter().map(|tec| format!("{}", tec))).join(", ")
)
}
}
pub(crate) struct SortKey {
cr: f64, comp: f64, cov_len: usize,
pat_len: usize,
width: u32,
area: u64
}
impl SortKey {
pub fn new(cr: f64, comp: f64, cov_len: usize, pat_len: usize, width: u32, area: u64) -> Self {
debug_assert!(cr.is_finite(), "compression ratio must be finite");
debug_assert!(comp.is_finite(), "compactness must be finite");
Self { cr, comp, cov_len, pat_len, width, area }
}
}
impl Eq for SortKey {}
impl PartialEq for SortKey {
fn eq(&self, other: &Self) -> bool {
self.cr == other.cr &&
self.comp == other.comp &&
self.cov_len == other.cov_len &&
self.pat_len == other.pat_len &&
self.width == other.width &&
self.area == other.area
}
}
impl PartialOrd for SortKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (-self.cr).partial_cmp(&(-other.cr)) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match (-self.comp).partial_cmp(&(-other.comp)) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match (-(self.cov_len as i64)).partial_cmp(&(-(other.cov_len as i64))) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match (-(self.pat_len as i64)).partial_cmp(&(-(other.pat_len as i64))) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.width.partial_cmp(&other.width) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
self.area.partial_cmp(&other.area)
}
}
impl Ord for SortKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
pub fn tec_sort_key(tec: &TranslationalEquivalence, dataset_points: &HashSet<(u32, u32)>) -> SortKey {
let cr = tec.compression_ratio();
let comp = tec.compactness(dataset_points);
let cov_len = tec.coverage().len();
let pat_len = tec.pattern.len();
let width = (
tec.pattern
.iter()
.map(|x| x.0 as i32)
.max().unwrap() -
tec.pattern
.iter()
.map(|x| x.0 as i32)
.min().unwrap()
) as u32;
let x_range = width;
let y_range = (
tec.pattern
.iter()
.map(|x| x.1 as i32)
.max().unwrap() -
tec.pattern
.iter()
.map(|x| x.1 as i32)
.min().unwrap()
) as u32;
let area = x_range as u64 * y_range as u64;
SortKey::new(cr, comp, cov_len, pat_len, width, area)
}