Rust 练习册 97:Run-Length Encoding 压缩算法

在数据处理和存储领域,压缩是一种非常常见的技术。今天我们要探讨的是一个经典的压缩算法------Run-Length Encoding(游程编码)。这是一种简单但有效的数据压缩方法,在某些特定场景下能发挥重要作用。

什么是 Run-Length Encoding?

Run-Length Encoding 是一种无损数据压缩算法,特别适用于包含大量连续重复元素的数据集。它的基本思想是将连续出现的相同字符替换为该字符出现次数和字符本身的组合。

例如,字符串 "AAABBCC" 可以被压缩为 "3A2B2C",这样就节省了存储空间。当然,这种方法对于没有太多连续重复字符的数据效果不佳,甚至可能会增加数据大小。

实现思路

让我们先看看我们需要实现的两个核心函数:

rust 复制代码
pub fn encode(source: &str) -> String {
    unimplemented!("Return the run-length encoding of {}.", source);
}

pub fn decode(source: &str) -> String {
    unimplemented!("Return the run-length decoding of {}.", source);
}

这两个函数分别负责编码和解码操作。我们的任务是完成它们的具体实现。

编码实现

我们从编码开始。编码过程需要遍历输入字符串,统计连续相同字符的数量,并按照规则进行转换:

rust 复制代码
pub fn encode(source: &str) -> String {
    if source.is_empty() {
        return String::new();
    }
    
    let mut result = String::new();
    let chars: Vec<char> = source.chars().collect();
    let mut i = 0;
    
    while i < chars.len() {
        let current_char = chars[i];
        let mut count = 1;
        let mut j = i + 1;
        
        // 计算连续相同字符的数量
        while j < chars.len() && chars[j] == current_char {
            count += 1;
            j += 1;
        }
        
        // 根据数量决定如何表示
        if count == 1 {
            result.push(current_char);
        } else {
            result.push_str(&count.to_string());
            result.push(current_char);
        }
        
        i = j;
    }
    
    result
}

解码实现

解码过程则是编码的逆向操作,需要解析数字和字符的组合,并将其还原为原始字符串:

rust 复制代码
pub fn decode(source: &str) -> String {
    if source.is_empty() {
        return String::new();
    }
    
    let mut result = String::new();
    let chars: Vec<char> = source.chars().collect();
    let mut i = 0;
    
    while i < chars.len() {
        if chars[i].is_digit(10) {
            // 如果当前字符是数字,则需要读取完整的数字
            let mut j = i;
            while j < chars.len() && chars[j].is_digit(10) {
                j += 1;
            }
            
            // 解析数字
            let count: usize = source[i..j].parse().unwrap();
            let character = chars[j];
            
            // 添加指定数量的字符到结果中
            for _ in 0..count {
                result.push(character);
            }
            
            i = j + 1;
        } else {
            // 单个字符直接添加到结果中
            result.push(chars[i]);
            i += 1;
        }
    }
    
    result
}

测试案例分析

通过测试案例我们可以更深入地理解算法的行为:

rust 复制代码
#[test]
fn test_encode_empty_string() {
    assert_eq!("", rle::encode(""));
}

#[test]
fn test_encode_single_characters() {
    assert_eq!("XYZ", rle::encode("XYZ"));
}

#[test]
fn test_encode_string_with_no_single_characters() {
    assert_eq!("2A3B4C", rle::encode("AABBBCCCC"));
}

#[test]
fn test_encode_single_characters_mixed_with_repeated_characters() {
    assert_eq!(
        "12WB12W3B24WB",
        rle::encode("WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB")
    );
}

这些测试展示了各种情况下的编码行为,包括空字符串、无重复字符、纯重复字符以及混合情况。

解码的测试案例也相应地验证了逆向过程:

rust 复制代码
#[test]
fn test_decode_empty_string() {
    assert_eq!("", rle::decode(""));
}

#[test]
fn test_decode_single_characters_only() {
    assert_eq!("XYZ", rle::decode("XYZ"));
}

#[test]
fn test_decode_string_with_no_single_characters() {
    assert_eq!("AABBBCCCC", rle::decode("2A3B4C"));
}

#[test]
fn test_decode_single_characters_with_repeated_characters() {
    assert_eq!(
        "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB",
        rle::decode("12WB12W3B24WB")
    );
}

最后还有一个一致性测试确保编码后再解码能得到原始数据:

rust 复制代码
#[test]
fn test_consistency() {
    assert_eq!(
        "zzz ZZ  zZ",
        rle::decode(rle::encode("zzz ZZ  zZ").as_str())
    );
}

应用场景

Run-Length Encoding 虽然简单,但在某些特定场景下非常有用:

  1. 图像处理:在黑白二值图像中,经常有大片连续的黑色或白色像素。
  2. 简单数据压缩:对于具有大量重复数据的简单文本文件。
  3. 教学用途:作为入门级的数据压缩算法示例。

总结

通过这个练习,我们学习了如何实现 Run-Length Encoding 算法,掌握了 Rust 中字符串处理的基本技巧。虽然这只是一个简单的压缩算法,但它很好地展示了数据压缩的基本原理。

在实际应用中,我们会遇到更多复杂的压缩算法如 Huffman 编码、LZW 等,但理解这种基础算法有助于我们更好地掌握更高级的概念。

相关推荐
树若逝花若殇3 分钟前
基于Spring AOP优雅记录请求响应日志
java·spring boot
Dr_Fourier9 分钟前
mmap的使用与底层原理
linux·c++
Yyyyyy~30 分钟前
【java】数组的定义和使用
java
麻辣布丁30 分钟前
java集合篇面试模拟
java·开发语言·面试
niucloud-admin32 分钟前
JAVA V6 多商户商城 开发文档——插件开发规范
java·开发语言
嘟哩DuliDuli1 小时前
创作 Agent 的任务状态该如何保存
android·java·javascript
wdfk_prog1 小时前
lely-canopen-rtt:为什么推荐在 RT-Thread 上使用 Lely CANopen 构建主站
java·运维·缓存·docker·容器
Token掘金室1 小时前
JSON模式结构化输出报错
java·服务器·json
秋饼1 小时前
Spring AI 2.0 接入 DeepSeek V4.1 Flash 生产级实战
java·ai·技术分享·后端开发
IT枫斗者枫哥1 小时前
Java 分批导出仍然 OOM?用 32 MiB 堆复现三种 CSV 写法
java