initial
This commit is contained in:
commit
3cf5b5e652
4 changed files with 118 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
ciphertext.txt
|
||||
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "substitution-helper"
|
||||
version = "0.1.0"
|
||||
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "substitution-helper"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
103
src/main.rs
Normal file
103
src/main.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
io::{self, Read, Write},
|
||||
process,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut subst = HashMap::new();
|
||||
for pair in env::args().skip(1) {
|
||||
let &[from, to] = pair.as_bytes() else {
|
||||
eprintln!("Usage: substitution-helper AB CD EF ...");
|
||||
process::exit(1);
|
||||
};
|
||||
subst.insert(from.to_ascii_uppercase(), to.to_ascii_uppercase());
|
||||
}
|
||||
|
||||
let mut inp = String::new();
|
||||
io::stdin().read_to_string(&mut inp).unwrap();
|
||||
let mut out = io::stdout().lock();
|
||||
|
||||
let mut byte_frequencies: HashMap<[char; 1], usize> = HashMap::new();
|
||||
for c in inp.chars().filter(char::is_ascii_alphabetic) {
|
||||
*byte_frequencies
|
||||
.entry([c.to_ascii_uppercase()])
|
||||
.or_default() += 1;
|
||||
}
|
||||
|
||||
println!("Letters:");
|
||||
print_freqs(byte_frequencies, &subst);
|
||||
println!();
|
||||
|
||||
let mut digram_freqs: HashMap<[char; 2], usize> = HashMap::new();
|
||||
for slice in inp.as_bytes().windows(2) {
|
||||
let [a, b] = slice else {
|
||||
continue;
|
||||
};
|
||||
if !(a.is_ascii_alphabetic() && b.is_ascii_alphabetic()) {
|
||||
continue;
|
||||
}
|
||||
*digram_freqs.entry([*a as char, *b as char]).or_default() += 1;
|
||||
}
|
||||
|
||||
println!("Digrams:");
|
||||
print_freqs(digram_freqs, &subst);
|
||||
println!();
|
||||
|
||||
let mut trigram_freqs: HashMap<[char; 3], usize> = HashMap::new();
|
||||
for slice in inp.as_bytes().windows(3) {
|
||||
let [a, b, c] = slice else {
|
||||
continue;
|
||||
};
|
||||
if !(a.is_ascii_alphabetic() && b.is_ascii_alphabetic() && c.is_ascii_alphabetic()) {
|
||||
continue;
|
||||
}
|
||||
*trigram_freqs
|
||||
.entry([*a as char, *b as char, *c as char])
|
||||
.or_default() += 1;
|
||||
}
|
||||
|
||||
println!("Trigrams:");
|
||||
print_freqs(trigram_freqs, &subst);
|
||||
println!();
|
||||
|
||||
for byte in inp.bytes() {
|
||||
let new_byte = subst
|
||||
.get(&byte.to_ascii_uppercase())
|
||||
.copied()
|
||||
.unwrap_or(byte.to_ascii_lowercase());
|
||||
// .unwrap_or(b'.');
|
||||
out.write_all(&[new_byte]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn print_freqs<const N: usize>(freqs: HashMap<[char; N], usize>, subst: &HashMap<u8, u8>) {
|
||||
let total_freqs: usize = freqs.values().sum();
|
||||
|
||||
let mut freqs: Vec<_> = freqs
|
||||
.into_iter()
|
||||
.filter(|(_, n)| *n > 1 && (*n as f64 / total_freqs as f64) > 0.001)
|
||||
.collect();
|
||||
freqs.sort_by_key(|(f, count)| (*count, *f));
|
||||
|
||||
for (c, count) in freqs.iter().rev() {
|
||||
println!(
|
||||
"{:>3}: {:4.1}% ({count:4})",
|
||||
c.iter()
|
||||
.map(|c| {
|
||||
let Ok(b): Result<u8, _> = (*c).try_into() else {
|
||||
panic!()
|
||||
};
|
||||
|
||||
if let Some(new) = subst.get(&b) {
|
||||
(*new as char).to_ascii_uppercase()
|
||||
} else {
|
||||
(b as char).to_ascii_lowercase()
|
||||
}
|
||||
})
|
||||
.collect::<String>(),
|
||||
(*count as f32 / total_freqs as f32) * 100.
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue