This commit is contained in:
mehbark 2026-04-22 17:56:49 -04:00
commit 3cf5b5e652
Signed by: mbk
GPG key ID: E333EC1335FFCCDB
4 changed files with 118 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
ciphertext.txt

7
Cargo.lock generated Normal file
View 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
View file

@ -0,0 +1,6 @@
[package]
name = "substitution-helper"
version = "0.1.0"
edition = "2024"
[dependencies]

103
src/main.rs Normal file
View 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.
);
}
}