use std::{
cmp::Ordering,
collections::HashSet,
fmt::{Display, Formatter, Write},
};
use itertools::Itertools;
use crate::{Bell, Block, PlaceNot, Stage};
#[allow(unused_imports)]
use crate::Method;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct FullClass {
is_jump: bool,
is_little: bool,
is_differential: bool,
class: Class,
}
impl FullClass {
pub const PRINCIPLE: Self = Self::new(false, false, false, Class::Principle);
pub const fn new(is_jump: bool, is_little: bool, is_differential: bool, class: Class) -> Self {
Self {
is_jump,
is_little,
is_differential,
class,
}
}
pub fn classify<A>(first_lead: &Block<A>) -> FullClass {
classify(first_lead) }
#[inline]
pub fn is_jump(self) -> bool {
self.is_jump
}
#[inline]
pub fn is_little(self) -> bool {
self.is_little
}
#[inline]
pub fn is_differential(self) -> bool {
self.is_differential
}
#[inline]
pub fn class(self) -> Class {
self.class
}
pub(super) fn fmt_name(&self, f: &mut impl Write, is_for_title: bool) -> std::fmt::Result {
#![allow(unused_assignments)]
let mut is_first_segment = true;
macro_rules! add_space {
() => {
if !is_first_segment {
write!(f, " ")?;
}
is_first_segment = false;
};
}
if self.is_jump {
add_space!();
write!(f, "Jump")?;
}
if self.is_differential {
add_space!();
write!(f, "Differential")?;
}
if is_for_title {
if let Some(name) = self.class.name_in_title() {
if self.is_little {
add_space!();
write!(f, "Little")?;
}
add_space!();
write!(f, "{}", name)?;
}
} else {
if self.is_little {
add_space!();
write!(f, "Little")?;
}
add_space!();
write!(f, "{}", self.class)?;
}
Ok(())
}
}
impl Display for FullClass {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt_name(f, false)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum HuntBellClass {
Plain,
TrebleDodging,
TreblePlace,
Alliance,
Hybrid,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[repr(u8)]
pub enum Class {
Principle,
Place,
Bob,
TrebleBob,
Delight,
Surprise,
TreblePlace,
Alliance,
Hybrid,
}
impl Class {
#[inline]
pub fn is_plain(self) -> bool {
matches!(self, Self::Place | Self::Bob)
}
#[inline]
pub fn is_treble_dodging(self) -> bool {
matches!(self, Self::TrebleBob | Self::Delight | Self::Surprise)
}
#[inline]
pub fn is_symmetric_hunter(self) -> bool {
self.is_plain()
|| self.is_treble_dodging()
|| matches!(self, Self::TreblePlace | Self::Alliance)
}
pub fn hunt_bell_class(self) -> Option<HuntBellClass> {
Some(match self {
Class::Bob | Class::Place => HuntBellClass::Plain,
Class::TrebleBob | Class::Delight | Class::Surprise => HuntBellClass::TrebleDodging,
Class::TreblePlace => HuntBellClass::TreblePlace,
Class::Alliance => HuntBellClass::Alliance,
Class::Hybrid => HuntBellClass::Hybrid,
Class::Principle => return None,
})
}
pub fn name_in_title(self) -> Option<&'static str> {
Some(match self {
Class::Principle => return None,
Class::Place => "Place",
Class::Bob => "Bob",
Class::TrebleBob => "Treble Bob",
Class::Delight => "Delight",
Class::Surprise => "Surprise",
Class::TreblePlace => "Treble Place",
Class::Alliance => "Alliance",
Class::Hybrid => return None,
})
}
pub fn name(self) -> &'static str {
match self {
Class::Principle => "Principle",
Class::Place => "Place",
Class::Bob => "Bob",
Class::TrebleBob => "Treble Bob",
Class::Delight => "Delight",
Class::Surprise => "Surprise",
Class::TreblePlace => "Treble Place",
Class::Alliance => "Alliance",
Class::Hybrid => "Hybrid",
}
}
}
impl Display for Class {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
fn classify<A>(first_lead: &Block<A>) -> FullClass {
let stage = first_lead.stage();
let cycles = Cycle::cycles_from_lead(first_lead);
let (hunt_cycles, working_cycles) = Cycle::partition_cycles(cycles);
let all_working_cycles_equal_length = working_cycles
.iter()
.tuple_windows()
.all(|(set1, set2)| set1.num_leads() == set2.num_leads());
let is_differential = !all_working_cycles_equal_length;
if hunt_cycles.is_empty() {
return FullClass {
is_jump: false,
is_little: false, is_differential,
class: Class::Principle,
};
}
let (mut best_is_little, mut best_hunt_bell_class) =
classify_hunt_cycle(&hunt_cycles[0], stage);
let mut hunt_cycles_in_best_class = vec![&hunt_cycles[0]];
for cycle in &hunt_cycles[1..] {
let (is_little, class) = classify_hunt_cycle(cycle, stage);
match class.cmp(&best_hunt_bell_class) {
Ordering::Less => {
best_is_little = is_little;
best_hunt_bell_class = class;
hunt_cycles_in_best_class.clear();
hunt_cycles_in_best_class.push(cycle);
}
Ordering::Equal => {
best_is_little &= is_little;
hunt_cycles_in_best_class.push(cycle);
}
Ordering::Greater => {}
}
}
let hunt_bells_in_best_class = hunt_cycles_in_best_class
.iter()
.map(|cycle| cycle.as_hunt_bell().unwrap())
.collect_vec();
let class = match best_hunt_bell_class {
HuntBellClass::Plain => {
let mut cycles = working_cycles.iter().collect_vec();
cycles.extend(hunt_cycles.iter().filter(|cycle| {
!hunt_bells_in_best_class.contains(&cycle.as_hunt_bell().unwrap())
}));
sub_classify_plain(cycles)
}
HuntBellClass::TrebleDodging => {
sub_classify_treble_dodging(first_lead, &hunt_cycles_in_best_class)
}
HuntBellClass::TreblePlace => Class::TreblePlace,
HuntBellClass::Alliance => Class::Alliance,
HuntBellClass::Hybrid => Class::Hybrid,
};
FullClass {
is_jump: false,
is_little: best_is_little,
is_differential,
class,
}
}
fn classify_hunt_cycle(cycle: &Cycle, stage: Stage) -> (bool, HuntBellClass) {
let mut num_rows_in_each_place = vec![0usize; stage.num_bells()];
for p in &cycle.full_path {
num_rows_in_each_place[*p] += 1;
}
let is_little = num_rows_in_each_place.contains(&0);
let is_stationary = num_rows_in_each_place.iter().filter(|v| **v > 0).count() == 1;
let are_places_visited_exactly_twice =
num_rows_in_each_place.iter().all(|&n| matches!(n, 0 | 2));
if is_stationary {
return (is_little, HuntBellClass::TreblePlace);
}
if are_places_visited_exactly_twice {
return (is_little, HuntBellClass::Plain);
}
let is_path_palindromic = is_palindromic(&cycle.full_path);
let num_places_made = cycle
.full_path
.iter()
.circular_tuple_windows()
.filter(|(a, b)| a == b)
.count();
let are_all_places_equally_covered = num_rows_in_each_place
.iter()
.filter(|v| **v != 0)
.tuple_windows()
.all(|(a, b)| a == b);
let class = match (
is_path_palindromic,
are_all_places_equally_covered,
num_places_made.cmp(&2),
) {
(true, true, Ordering::Greater) => HuntBellClass::TreblePlace,
(true, true, Ordering::Equal) => HuntBellClass::TrebleDodging,
(true, true, Ordering::Less) => HuntBellClass::Hybrid,
(true, false, _) => HuntBellClass::Alliance,
(false, _, _) => HuntBellClass::Hybrid,
};
(is_little, class)
}
fn sub_classify_treble_dodging<A>(first_lead: &Block<A>, hunt_cycles: &[&Cycle]) -> Class {
let mut cross_indices = HashSet::<usize>::new();
for &cycle in hunt_cycles {
let min_place = *cycle.full_path.iter().min().unwrap();
cross_indices.extend(
cycle
.full_path
.iter()
.circular_tuple_windows()
.positions(|(a, b)| (a - min_place) / 2 != (b - min_place) / 2),
);
}
let mut all_internal_places = true;
let mut all_no_internal_places = true;
for i in cross_indices {
let r1 = first_lead.get_row(i).unwrap();
let r2 = first_lead.get_row(i + 1).unwrap();
let has_internal_places = PlaceNot::pn_between(r1, r2).unwrap().has_internal_places();
if has_internal_places {
all_no_internal_places = false;
} else {
all_internal_places = false;
}
}
match (all_internal_places, all_no_internal_places) {
(false, true) => Class::TrebleBob,
(false, false) => Class::Delight,
(true, false) => Class::Surprise,
(true, true) => Class::TrebleBob,
}
}
fn sub_classify_plain(working_bell_cycles: Vec<&Cycle>) -> Class {
for cycle in working_bell_cycles {
for (a, b, c) in cycle.full_path.iter().copied().circular_tuple_windows() {
if a == c && (b + 1 == a || b == a + 1) {
return Class::Bob;
}
}
}
Class::Place
}
fn is_palindromic(path: &[usize]) -> bool {
for i in 0..path.len() {
let rotated_reversed_path = path.iter().rev().cycle().skip(i);
if rotated_reversed_path.zip(path).all(|(p1, p2)| p1 == p2) {
return true;
}
}
false
}
#[derive(Debug, Clone)]
struct Cycle {
place_bells: Vec<Bell>,
full_path: Vec<usize>, }
impl Cycle {
fn cycles_from_lead<A>(first_lead: &Block<A>) -> Vec<Self> {
assert!(first_lead.first_annot_row().unwrap().1.is_rounds());
let mut cycles = Vec::<Cycle>::new();
let mut bells_left = vec![true; first_lead.stage().num_bells()];
while let Some(starting_bell_idx) = bells_left.iter().position(|v| *v) {
let starting_bell = Bell::from_index(starting_bell_idx as u8);
let mut place_bells_in_cycle = Vec::new();
let mut full_path = Vec::new();
let mut place_bell = starting_bell;
loop {
bells_left[place_bell.index()] = false;
let (path, next_place_bell) = first_lead.path_of(place_bell);
place_bells_in_cycle.push(place_bell);
full_path.extend(path);
place_bell = Bell::from_index(next_place_bell as u8);
if place_bell == starting_bell {
break;
}
}
cycles.push(Cycle {
place_bells: place_bells_in_cycle,
full_path,
});
}
cycles
}
fn is_hunt(&self) -> bool {
self.place_bells.len() == 1
}
fn as_hunt_bell(&self) -> Option<Bell> {
match self.place_bells.len() {
1 => Some(self.place_bells[0]),
_ => None,
}
}
fn num_leads(&self) -> usize {
self.place_bells.len()
}
fn partition_cycles(cycles: Vec<Cycle>) -> (Vec<Cycle>, Vec<Cycle>) {
let mut hunt_cycles = Vec::new();
let mut working_cycles = Vec::new();
for cycle in cycles {
if cycle.is_hunt() {
hunt_cycles.push(cycle);
} else {
working_cycles.push(cycle);
}
}
(hunt_cycles, working_cycles)
}
}
#[cfg(test)]
mod tests {
use crate::{method::FullClass, Block, MethodLib};
#[test]
fn classification() {
let mut num_misclassifications = 0usize;
for (name, pn, class) in MethodLib::cc_lib().unwrap().all_pns_and_classes() {
let plain_lead: Block<()> = pn.to_block_from_rounds();
let computed_class = FullClass::classify(&plain_lead);
if computed_class != class {
println!(
"Misclassified {} as '{}' (should be '{}')",
name, computed_class, class
);
num_misclassifications += 1;
}
}
println!("{} methods misclassified", num_misclassifications);
if num_misclassifications > 0 {
panic!();
}
}
}