Rust 练习册 66:密码方块与文本加密

在密码学的历史中,有许多巧妙的加密方法被用来保护信息。密码方块(Crypto Square)是一种经典的文本加密技术,它通过将文本重新排列成矩形网格,然后按列读取来实现加密。在 Exercism 的 "crypto-square" 练习中,我们将实现这种加密算法,这不仅能帮助我们掌握文本处理和矩阵操作技巧,还能深入学习 Rust 中的字符串处理和算法实现。

什么是密码方块?

密码方块加密算法的工作原理如下:

  1. 规范化输入:将输入文本转换为小写,并移除所有非字母数字字符
  2. 计算尺寸:确定矩形网格的行数和列数
  3. 填充网格:将规范化后的文本填充到网格中
  4. 按列读取:按列的顺序读取字符,形成加密结果

例如,对于输入 "If man was meant to stay on the ground, god would have given us roots.":

  1. 规范化后:"ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots"

  2. 文本长度为 54,因此矩形尺寸为 7×8(行×列)

  3. 填充网格:

    复制代码
    ifmanwas
    meanttos
    tayontheg
    roundgodw
    ouldhave
    givenusr
    oots
  4. 按列读取:"imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau "

让我们先看看练习提供的函数签名:

rust 复制代码
pub fn encrypt(input: &str) -> String {
    unimplemented!("Encrypt {:?} using a square code", input)
}

我们需要实现这个函数,它应该:

  1. 接收任意字符串输入
  2. 按照密码方块算法进行加密
  3. 返回加密后的字符串

算法实现

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
    }
}

实际应用场景

密码方块在实际开发中有以下应用:

  1. 教育工具:作为密码学教学的经典示例
  2. 文本处理:在某些文本格式化场景中使用
  3. 游戏开发:解谜游戏中的加密机制
  4. 编码练习:算法和数据结构练习的经典题目
  5. 历史研究:研究古典密码学方法

算法复杂度分析

  1. 时间复杂度:O(n)

    • 规范化输入:O(n)
    • 计算尺寸:O(1)
    • 构建结果:O(n)
  2. 空间复杂度: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 练习,我们学到了:

  1. 文本处理:掌握了字符串规范化和字符处理技巧
  2. 矩阵操作:理解了如何将一维数据映射到二维网格
  3. 算法实现:学会了实现经典的密码学算法
  4. 性能优化:了解了预分配内存和避免不必要的数据复制
  5. 边界处理:学会了处理各种边界情况
  6. 格式化输出:掌握了按特定格式组织输出结果

这些技能在实际开发中非常有用,特别是在处理文本数据、实现加密算法和进行数据格式化时。密码方块虽然看起来简单,但它涉及到了文本处理、矩阵映射和算法实现等许多核心概念,是学习 Rust 文本处理的良好起点。

通过这个练习,我们也看到了 Rust 在字符串处理和算法实现方面的强大能力,以及如何用安全且高效的方式实现复杂的文本操作。这种结合了安全性和性能的语言特性正是 Rust 的魅力所在。

相关推荐
q***31891 小时前
Spring Boot 实战篇(四):实现用户登录与注册功能
java·spring boot·后端
IT_陈寒1 小时前
React性能翻倍!90%开发者忽略的5个Hooks最佳实践
前端·人工智能·后端
亿元程序员1 小时前
光图片就300多M,微信小游戏给再大的分包也难啊!
前端
专注于大数据技术栈1 小时前
java学习--==和equals
java·python·学习
鲸沉梦落1 小时前
JVM类加载
java·jvm
carry杰1 小时前
esayExcel导出图片
java·easyexcel 图片
路人甲ing..1 小时前
Android Studio 快速的制作一个可以在 手机上跑的app
android·java·linux·智能手机·android studio
中工钱袋1 小时前
前端请求到底是从哪里发出去的?
前端
心灵宝贝2 小时前
Mac 安装 JDK 8u281(JDK-8u281-1.dmg)详细步骤(附安装包)
java·macos·intellij-idea