use crate::{
cipher::{Cipher, Decode, Encode},
common::Character,
};
use anyhow::{Context, Error};
use itertools::Itertools;
use std::collections::HashMap;
use thiserror::Error;
pub struct Plugboard {
cipher: Cipher,
}
#[derive(Debug)]
pub struct Plugs(Vec<Plug>);
#[derive(Debug)]
pub struct Plug(Character, Character);
#[derive(Error, Debug)]
pub enum PlugboardError {
#[error("Recieved {0} plugs, No more than 10 plugs may be used in the plugboard")]
TooMany(usize),
#[error("Can not map multiple plugs to the same character")]
Mapping,
#[error("Can not map a character to itself")]
Duplicate,
}
impl TryFrom<Plugs> for Plugboard {
type Error = Error;
fn try_from(value: Plugs) -> Result<Plugboard, self::Error> {
let passthrough = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
.chars()
.map(|c| Character::try_from(c).unwrap());
let alphabet: HashMap<Character, Character> = passthrough.clone().fold(
HashMap::new(),
|mut acc: HashMap<Character, Character>, next| {
acc.insert(next, next);
acc
},
);
let cipher = value.0.iter().fold(
alphabet,
|mut acc: HashMap<Character, Character>, next: &Plug| {
acc.insert(next.0, next.1);
acc.insert(next.1, next.0);
acc
},
);
let x: Vec<Character> = passthrough
.fold(Vec::new(), |mut acc, next| {
let substitution = cipher.get(&next).unwrap();
acc.push(substitution);
acc
})
.into_iter()
.cloned()
.collect();
let cipher: Cipher = Cipher::try_from(x)
.with_context(|| format!("Tried to construct a cipher for a plugboard"))?;
Ok(Plugboard { cipher: cipher })
}
}
impl TryFrom<(Character, Character)> for Plug {
type Error = PlugboardError;
fn try_from(value: (Character, Character)) -> Result<Self, Self::Error> {
match value.0 == value.1 {
true => Err(PlugboardError::Duplicate),
false => Ok(Plug(value.0, value.1)),
}
}
}
impl TryFrom<Vec<Plug>> for Plugs {
type Error = PlugboardError;
fn try_from(value: Vec<Plug>) -> Result<Self, Self::Error> {
let len = value.len();
match len {
0..=10 => (),
_ => return Err(PlugboardError::TooMany(value.len())),
}
let uniquechars = value
.iter()
.fold(Vec::new(), |mut acc: Vec<Character>, next| {
acc.push(next.0);
acc.push(next.1);
acc
})
.iter()
.unique()
.count();
if uniquechars != 2 * len {
return Err(PlugboardError::Mapping);
}
Ok(Plugs(value))
}
}
impl Encode for Plugboard {
fn encode(&self, c: Character) -> Character {
self.cipher.encode(c)
}
}
impl Decode for Plugboard {
fn decode(&self, c: Character) -> Character {
self.cipher.decode(c)
}
}
#[cfg(test)]
mod tests {
use crate::{cipher::Encode, common::Character};
use super::{Plug, Plugboard, Plugs};
#[test]
fn empty_vec_cons() {
let _ =
Plugs::try_from(Vec::new()).expect("Should be able to construct an empty plugboard");
}
#[test]
fn encode() {
let pb = Plugboard::try_from(Plugs::try_from(Vec::new()).unwrap())
.expect("Should be able to construct an empty plugboard");
('A'..='Z')
.into_iter()
.map(|c| Character::try_from(c).unwrap())
.for_each(|c| {
let ct = pb.encode(c);
assert_eq!(ct, c)
})
}
#[test]
fn reflect() {
let v: Vec<Plug> = [
('A', 'B'),
('C', 'Z'),
('X', 'L'),
('N', 'P'),
('Q', 'U'),
('M', 'O'),
('I', 'J'),
('S', 'V'),
('H', 'G'),
('D', 'R'),
]
.iter()
.map(|x| {
(
Character::try_from(x.0).unwrap(),
Character::try_from(x.1).unwrap(),
)
})
.map(|p| Plug::try_from(p).unwrap())
.collect();
let pb = Plugboard::try_from(Plugs::try_from(v).unwrap())
.expect("Should be able to construct an empty plugboard");
('A'..='Z')
.into_iter()
.map(|c| Character::try_from(c).unwrap())
.for_each(|c| {
let ciphertext = pb.encode(c);
let plaintext = pb.encode(ciphertext);
println!("{}: {},{}", c, ciphertext, plaintext);
assert_eq!(plaintext, c)
})
}
#[test]
fn too_many() {
let plugs: Vec<Plug> = [
('A', 'B'),
('C', 'Z'),
('X', 'L'),
('N', 'P'),
('Q', 'U'),
('M', 'O'),
('I', 'J'),
('S', 'V'),
('H', 'G'),
('D', 'R'),
('Y', 'F'),
]
.iter()
.map(|x| {
(
Character::try_from(x.0).unwrap(),
Character::try_from(x.1).unwrap(),
)
})
.map(|p| Plug::try_from(p).unwrap())
.collect();
match Plugs::try_from(plugs) {
Ok(_) => panic!("Should not be able to construct with more than 10 plugs"),
Err(_) => (),
}
}
#[test]
fn same_character() {
let c = Character::try_from('A').unwrap();
let x = Plug::try_from((c, c));
match x {
Ok(_) => {
panic!("Should not be able to construct a plug containing duplicate characters")
}
Err(_) => (),
}
}
#[test]
fn duplicate_mapping() {
let a = Character::try_from('A').unwrap();
let b = Character::try_from('B').unwrap();
let c = Character::try_from('C').unwrap();
let p1 = Plug::try_from((a, b)).unwrap();
let p2 = Plug::try_from((a, c)).unwrap();
match Plugs::try_from(vec![p1, p2]) {
Ok(plugs) => panic!("Recieved {plugs:?}: Should not be able to construct plugs that contain non-unique mappings"),
Err(_) => (),
}
}
}