在密码学的历史中,有许多巧妙的加密方法被用来保护信息。密码方块(Crypto Square)是一种经典的文本加密技术,它通过将文本重新排列成矩形网格,然后按列读取来实现加密。在 Exercism 的 "crypto-square" 练习中,我们将实现这种加密算法,这不仅能帮助我们掌握文本处理和矩阵操作技巧,还能深入学习 Rust 中的字符串处理和算法实现。
什么是密码方块?
密码方块加密算法的工作原理如下:
- 规范化输入:将输入文本转换为小写,并移除所有非字母数字字符
- 计算尺寸:确定矩形网格的行数和列数
- 填充网格:将规范化后的文本填充到网格中
- 按列读取:按列的顺序读取字符,形成加密结果
例如,对于输入 "If man was meant to stay on the ground, god would have given us roots.":
-
规范化后:"ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots"
-
文本长度为 54,因此矩形尺寸为 7×8(行×列)
-
填充网格:
ifmanwas meanttos tayontheg roundgodw ouldhave givenusr oots -
按列读取:"imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau "
让我们先看看练习提供的函数签名:
rust
pub fn encrypt(input: &str) -> String {
unimplemented!("Encrypt {:?} using a square code", input)
}
我们需要实现这个函数,它应该:
- 接收任意字符串输入
- 按照密码方块算法进行加密
- 返回加密后的字符串
算法实现
1. 基础实现
rust
pub fn encrypt(input: &str) -> String {
// 步骤1: 规范化输入
let normalized: String = input
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
if normalized.is_empty() {
return String::new();
}
// 步骤2: 计算矩形尺寸
let len = normalized.len();
let (rows, cols) = calculate_dimensions(len);
// 步骤3: 填充网格
let mut grid: Vec<Vec<char>> = Vec::with_capacity(rows);
let chars: Vec<char> = normalized.chars().collect();
for i in 0..rows {
let mut row: Vec<char> = Vec::with_capacity(cols);
for j in 0..cols {
let index = i * cols + j;
if index < len {
row.push(chars[index]);
} else {
row.push(' '); // 用空格填充
}
}
grid.push(row);
}
// 步骤4: 按列读取
let mut result = String::new();
for j in 0..cols {
if j > 0 {
result.push(' '); // 列之间添加空格
}
for i in 0..rows {
result.push(grid[i][j]);
}
}
result
}
fn calculate_dimensions(len: usize) -> (usize, usize) {
let sqrt = (len as f64).sqrt();
let cols = sqrt.ceil() as usize;
let rows = ((len as f64) / (cols as f64)).ceil() as usize;
(rows, cols)
}
2. 优化实现
rust
pub fn encrypt(input: &str) -> String {
// 步骤1: 规范化输入
let normalized: Vec<char> = input
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
let len = normalized.len();
if len == 0 {
return String::new();
}
// 步骤2: 计算矩形尺寸
let (rows, cols) = calculate_dimensions(len);
// 步骤3 & 4: 直接按列构建结果,避免显式网格
let mut result = String::with_capacity(rows * (cols + 1)); // +1 用于列间空格
for col in 0..cols {
if col > 0 {
result.push(' ');
}
for row in 0..rows {
let index = row * cols + col;
if index < len {
result.push(normalized[index]);
} else {
result.push(' ');
}
}
}
result
}
fn calculate_dimensions(len: usize) -> (usize, usize) {
let sqrt = (len as f64).sqrt();
let cols = sqrt.ceil() as usize;
let rows = ((len as f64) / (cols as f64)).ceil() as usize;
(rows, cols)
}
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
rust
#[test]
fn test_empty_input() {
test("", "")
}
空输入应返回空字符串。
rust
#[test]
fn test_encrypt_also_decrypts_square() {
// note that you only get the exact input back if:
// 1. no punctuation
// 2. even spacing
// 3. all lowercase
// 4. square input
let example = "lime anda coco anut";
assert_eq!(example, &encrypt(&encrypt(example)));
}
对于特定格式的输入,两次加密应该能还原原始输入。
rust
#[test]
fn test_example() {
test(
"If man was meant to stay on the ground, god would have given us roots.",
"imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ",
)
}
经典示例的加密结果。
rust
#[test]
fn test_spaces_are_reorganized() {
test("abet", "ae bt");
test("a bet", "ae bt");
test(" a b e t ", "ae bt");
}
空格和标点符号应被忽略,只保留字母数字字符。
完整实现
考虑所有边界情况的完整实现:
rust
pub fn encrypt(input: &str) -> String {
// 步骤1: 规范化输入 - 移除非字母数字字符并转为小写
let normalized: Vec<char> = input
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
let len = normalized.len();
if len == 0 {
return String::new();
}
// 步骤2: 计算矩形尺寸
let (rows, cols) = calculate_dimensions(len);
// 步骤3 & 4: 直接按列构建结果
let mut result = String::with_capacity(rows * (cols + 1));
for col in 0..cols {
if col > 0 {
result.push(' ');
}
for row in 0..rows {
let index = row * cols + col;
if index < len {
result.push(normalized[index]);
} else {
result.push(' '); // 用空格填充不足的部分
}
}
}
result
}
fn calculate_dimensions(len: usize) -> (usize, usize) {
let sqrt = (len as f64).sqrt();
let cols = sqrt.ceil() as usize;
let rows = ((len as f64) / (cols as f64)).ceil() as usize;
(rows, cols)
}
性能优化版本
考虑性能的优化实现:
rust
pub fn encrypt(input: &str) -> String {
// 预先计算规范化后的长度
let normalized_len = input
.chars()
.filter(|c| c.is_alphanumeric())
.count();
if normalized_len == 0 {
return String::new();
}
// 计算矩形尺寸
let (rows, cols) = calculate_dimensions(normalized_len);
// 预先分配结果字符串
let mut result = String::with_capacity(rows * (cols + 1) - 1);
// 创建规范化字符的迭代器
let normalized_chars = input
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase());
// 收集到 Vec 以便随机访问
let chars: Vec<char> = normalized_chars.collect();
// 按列构建结果
for col in 0..cols {
if col > 0 {
result.push(' ');
}
for row in 0..rows {
let index = row * cols + col;
if index < chars.len() {
result.push(chars[index]);
} else {
result.push(' ');
}
}
}
result
}
fn calculate_dimensions(len: usize) -> (usize, usize) {
if len == 0 {
return (0, 0);
}
let sqrt = (len as f64).sqrt();
let cols = sqrt.ceil() as usize;
let rows = ((len as f64) / (cols as f64)).ceil() as usize;
(rows, cols)
}
错误处理和边界情况
考虑更多边界情况的实现:
rust
pub fn encrypt(input: &str) -> String {
// 处理空输入
if input.is_empty() {
return String::new();
}
// 规范化输入
let normalized: Vec<char> = input
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
let len = normalized.len();
if len == 0 {
return String::new();
}
// 计算矩形尺寸
let (rows, cols) = calculate_dimensions(len);
// 构建结果
let mut result = String::with_capacity(rows * (cols + 1));
for col in 0..cols {
if col > 0 {
result.push(' ');
}
for row in 0..rows {
let index = row * cols + col;
if index < len {
result.push(normalized[index]);
} else {
result.push(' ');
}
}
}
result
}
fn calculate_dimensions(len: usize) -> (usize, usize) {
debug_assert!(len > 0);
let sqrt = (len as f64).sqrt();
let cols = sqrt.ceil() as usize;
let rows = ((len as f64) / (cols as f64)).ceil() as usize;
(rows, cols)
}
扩展功能
基于基础实现,我们可以添加更多功能:
rust
pub struct CryptoSquare;
impl CryptoSquare {
pub fn encrypt(input: &str) -> String {
let normalized: Vec<char> = input
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
let len = normalized.len();
if len == 0 {
return String::new();
}
let (rows, cols) = Self::calculate_dimensions(len);
let mut result = String::with_capacity(rows * (cols + 1));
for col in 0..cols {
if col > 0 {
result.push(' ');
}
for row in 0..rows {
let index = row * cols + col;
if index < len {
result.push(normalized[index]);
} else {
result.push(' ');
}
}
}
result
}
pub fn decrypt(encrypted: &str) -> String {
if encrypted.is_empty() {
return String::new();
}
// 分割列
let columns: Vec<&str> = encrypted.split_whitespace().collect();
if columns.is_empty() {
return String::new();
}
let cols = columns.len();
let rows = columns[0].len();
// 重新构建原始文本
let mut result = String::with_capacity(cols * rows);
for row in 0..rows {
for col in 0..cols {
if row < columns[col].len() {
let chars: Vec<char> = columns[col].chars().collect();
if chars[row] != ' ' {
result.push(chars[row]);
}
}
}
}
result
}
fn calculate_dimensions(len: usize) -> (usize, usize) {
let sqrt = (len as f64).sqrt();
let cols = sqrt.ceil() as usize;
let rows = ((len as f64) / (cols as f64)).ceil() as usize;
(rows, cols)
}
// 获取加密网格的可视化表示
pub fn visualize_grid(input: &str) -> String {
let normalized: Vec<char> = input
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
let len = normalized.len();
if len == 0 {
return String::new();
}
let (rows, cols) = Self::calculate_dimensions(len);
let mut result = String::new();
for i in 0..rows {
for j in 0..cols {
let index = i * cols + j;
if index < len {
result.push(normalized[index]);
} else {
result.push(' ');
}
}
result.push('\n');
}
result
}
}
实际应用场景
密码方块在实际开发中有以下应用:
- 教育工具:作为密码学教学的经典示例
- 文本处理:在某些文本格式化场景中使用
- 游戏开发:解谜游戏中的加密机制
- 编码练习:算法和数据结构练习的经典题目
- 历史研究:研究古典密码学方法
算法复杂度分析
-
时间复杂度:O(n)
- 规范化输入:O(n)
- 计算尺寸:O(1)
- 构建结果:O(n)
-
空间复杂度:O(n)
- 存储规范化字符:O(n)
- 存储结果字符串:O(n)
与其他实现方式的比较
rust
// 使用迭代器的函数式实现
pub fn encrypt_functional(input: &str) -> String {
let normalized: Vec<char> = input
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
let len = normalized.len();
if len == 0 {
return String::new();
}
let (rows, cols) = calculate_dimensions(len);
(0..cols)
.map(|col| {
(0..rows)
.map(|row| {
let index = row * cols + col;
if index < len {
normalized[index]
} else {
' '
}
})
.collect::<String>()
})
.collect::<Vec<String>>()
.join(" ")
}
// 使用字符串切片的实现
pub fn encrypt_slice_based(input: &str) -> String {
let normalized: String = input
.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
let len = normalized.len();
if len == 0 {
return String::new();
}
let (rows, cols) = calculate_dimensions(len);
let mut result_parts: Vec<String> = Vec::with_capacity(cols);
for col in 0..cols {
let mut column = String::with_capacity(rows);
for row in 0..rows {
let index = row * cols + col;
if index < len {
column.push(normalized.chars().nth(index).unwrap());
} else {
column.push(' ');
}
}
result_parts.push(column);
}
result_parts.join(" ")
}
总结
通过 crypto-square 练习,我们学到了:
- 文本处理:掌握了字符串规范化和字符处理技巧
- 矩阵操作:理解了如何将一维数据映射到二维网格
- 算法实现:学会了实现经典的密码学算法
- 性能优化:了解了预分配内存和避免不必要的数据复制
- 边界处理:学会了处理各种边界情况
- 格式化输出:掌握了按特定格式组织输出结果
这些技能在实际开发中非常有用,特别是在处理文本数据、实现加密算法和进行数据格式化时。密码方块虽然看起来简单,但它涉及到了文本处理、矩阵映射和算法实现等许多核心概念,是学习 Rust 文本处理的良好起点。
通过这个练习,我们也看到了 Rust 在字符串处理和算法实现方面的强大能力,以及如何用安全且高效的方式实现复杂的文本操作。这种结合了安全性和性能的语言特性正是 Rust 的魅力所在。