Rust 练习册 :掌握文本处理与词频统计

在信息时代,文本处理和分析是计算机科学中的一个重要领域。无论是搜索引擎、社交媒体分析,还是自然语言处理,都需要对文本进行有效的处理和统计。今天我们要探讨的是一个基础但非常实用的问题------单词计数(Word Count)。通过Rust语言,我们将一起学习如何高效地处理文本并统计词频。

问题背景

单词计数是文本处理中最基础也是最重要的操作之一。它广泛应用于:

  • 搜索引擎的索引构建
  • 文本分析和数据挖掘
  • 词频统计和语言学研究
  • 代码质量分析工具
  • 社交媒体内容分析

在Unix/Linux系统中,有一个经典的wc命令用于统计文件的行数、单词数和字符数。而我们今天要实现的是更精细的单词频率统计功能。

问题描述

我们的任务是实现这样一个函数:

rust 复制代码
use std::collections::HashMap;

/// Count occurrences of words.
pub fn word_count(words: &str) -> HashMap<String, u32> {
    unimplemented!("Count of occurrences of words in {:?}", words);
}

该函数接收一个字符串作为输入,返回一个HashMap,其中键是单词(小写形式),值是该单词在文本中出现的次数。

根据测试案例,我们需要满足以下要求:

  1. 能够正确分割单词(通过空格、换行、逗号等分隔符)
  2. 忽略标点符号
  3. 将所有单词转换为小写形式
  4. 正确处理撇号(如don't)
  5. 忽略多余的空格

解决方案

让我们实现一个完整的单词计数功能:

rust 复制代码
use std::collections::HashMap;

/// Count occurrences of words.
pub fn word_count(words: &str) -> HashMap<String, u32> {
    let mut counts = HashMap::new();
    
    // 将输入转换为小写并分割成单词
    for word in words.to_lowercase().split(|c: char| {
        // 使用自定义分隔符:除了字母、数字和撇号之外的所有字符都是分隔符
        !c.is_alphanumeric() && c != '\''
    }) {
        // 去除单词前后的撇号
        let cleaned_word = word.trim_matches('\'');
        
        // 忽略空字符串
        if !cleaned_word.is_empty() {
            *counts.entry(cleaned_word.to_string()).or_insert(0) += 1;
        }
    }
    
    counts
}

测试案例详解

通过查看测试案例,我们可以更好地理解函数的行为:

rust 复制代码
#[test]
fn test_count_one_word() {
    check_word_count("word", &[("word", 1)]);
}

最简单的情况:单个单词。

rust 复制代码
#[test]
fn test_count_one_of_each() {
    check_word_count("one of each", &[("one", 1), ("of", 1), ("each", 1)]);
}

多个不同的单词,每个出现一次。

rust 复制代码
#[test]
fn test_count_multiple_occurrences() {
    check_word_count(
        "one fish two fish red fish blue fish",
        &[("one", 1), ("fish", 4), ("two", 1), ("red", 1), ("blue", 1)],
    );
}

同一个单词多次出现,需要正确计数。

rust 复制代码
#[test]
fn cramped_lists() {
    check_word_count("one,two,three", &[("one", 1), ("two", 1), ("three", 1)]);
}

使用逗号分隔的单词列表。

rust 复制代码
#[test]
fn expanded_lists() {
    check_word_count("one\ntwo\nthree", &[("one", 1), ("two", 1), ("three", 1)]);
}

使用换行符分隔的单词列表。

rust 复制代码
#[test]
fn test_ignore_punctuation() {
    check_word_count(
        "car : carpet as java : javascript!!&@$%^&",
        &[
            ("car", 1),
            ("carpet", 1),
            ("as", 1),
            ("java", 1),
            ("javascript", 1),
        ],
    );
}

忽略各种标点符号。

rust 复制代码
#[test]
fn test_normalize_case() {
    check_word_count("go Go GO Stop stop", &[("go", 3), ("stop", 2)]);
}

将所有单词转换为小写进行统计。

rust 复制代码
#[test]
fn with_apostrophes() {
    check_word_count(
        "First: don't laugh. Then: don't cry.",
        &[
            ("first", 1),
            ("don't", 2),
            ("laugh", 1),
            ("then", 1),
            ("cry", 1),
        ],
    );
}

正确处理包含撇号的单词。

rust 复制代码
#[test]
fn multiple_spaces_not_detected_as_a_word() {
    check_word_count(
        " multiple   whitespaces",
        &[("multiple", 1), ("whitespaces", 1)],
    );
}

忽略多余的空格。

Rust语言特性运用

在这个实现中,我们运用了多种Rust语言特性:

  1. HashMap: 使用标准库的HashMap存储单词计数
  2. 字符串处理: 使用[to_lowercase()]、[split()]、[trim_matches()]等方法处理文本
  3. 闭包: 在[split()]方法中使用闭包定义分隔符
  4. 字符处理: 使用[is_alphanumeric()]方法识别字母和数字
  5. 模式匹配: 使用[entry()] API高效地更新计数
  6. 引用和生命周期: 正确处理字符串切片
  7. 迭代器: 使用迭代器链式操作处理单词序列

更多实现方式

我们可以用多种方式实现单词计数功能:

使用函数式编程风格

rust 复制代码
use std::collections::HashMap;

pub fn word_count_functional(words: &str) -> HashMap<String, u32> {
    words
        .to_lowercase()
        .split(|c: char| !c.is_alphanumeric() && c != '\'')
        .map(|word| word.trim_matches('\''))
        .filter(|word| !word.is_empty())
        .fold(HashMap::new(), |mut acc, word| {
            *acc.entry(word.to_string()).or_insert(0) += 1;
            acc
        })
}

使用正则表达式

rust 复制代码
use std::collections::HashMap;
use regex::Regex;

pub fn word_count_regex(words: &str) -> HashMap<String, u32> {
    let re = Regex::new(r#"[a-z0-9]+(?:'[a-z0-9]+)*"#).unwrap();
    let mut counts = HashMap::new();
    
    for word in re.find_iter(&words.to_lowercase()) {
        *counts.entry(word.as_str().to_string()).or_insert(0) += 1;
    }
    
    counts
}

使用更复杂的预处理

rust 复制代码
use std::collections::HashMap;

pub fn word_count_advanced(words: &str) -> HashMap<String, u32> {
    let mut counts = HashMap::new();
    
    // 自定义分词函数
    fn is_word_char(c: char) -> bool {
        c.is_alphanumeric() || c == '\''
    }
    
    let mut current_word = String::new();
    
    for c in words.chars() {
        let c_lower = c.to_lowercase().next().unwrap_or(c);
        
        if is_word_char(c_lower) {
            current_word.push(c_lower);
        } else {
            if !current_word.is_empty() {
                // 清理单词前后的撇号
                let cleaned = current_word.trim_matches('\'').to_string();
                if !cleaned.is_empty() {
                    *counts.entry(cleaned).or_insert(0) += 1;
                }
                current_word.clear();
            }
        }
    }
    
    // 处理最后一个单词
    if !current_word.is_empty() {
        let cleaned = current_word.trim_matches('\'').to_string();
        if !cleaned.is_empty() {
            *counts.entry(cleaned).or_insert(0) += 1;
        }
    }
    
    counts
}

性能优化

对于大规模文本处理,我们可以考虑以下优化:

rust 复制代码
use std::collections::HashMap;

pub fn word_count_optimized(words: &str) -> HashMap<String, u32> {
    // 预分配HashMap容量以减少重新分配
    let mut counts = HashMap::with_capacity(words.len() / 9); // 启发式预估
    
    // 使用字符迭代器而非字符串操作
    let mut word_start = None;
    let chars: Vec<char> = words.chars().collect();
    
    for (i, c) in chars.iter().enumerate() {
        let c_lower = c.to_lowercase().next().unwrap_or(*c);
        let is_word_char = c_lower.is_alphanumeric() || c_lower == '\'';
        
        if is_word_char {
            if word_start.is_none() {
                word_start = Some(i);
            }
        } else {
            if let Some(start) = word_start {
                let word: String = chars[start..i]
                    .iter()
                    .collect::<String>()
                    .to_lowercase();
                
                let cleaned = word.trim_matches('\'');
                if !cleaned.is_empty() {
                    *counts.entry(cleaned.to_string()).or_insert(0) += 1;
                }
                word_start = None;
            }
        }
    }
    
    // 处理最后一个单词
    if let Some(start) = word_start {
        let word: String = chars[start..]
            .iter()
            .collect::<String>()
            .to_lowercase();
        
        let cleaned = word.trim_matches('\'');
        if !cleaned.is_empty() {
            *counts.entry(cleaned.to_string()).or_insert(0) += 1;
        }
    }
    
    counts
}

实际应用场景

单词计数在许多实际场景中都有应用:

  1. 搜索引擎: 构建倒排索引,统计词频用于排名
  2. 文本分析: 分析文档主题和关键词
  3. 代码分析: 统计代码中的标识符使用频率
  4. 社交媒体: 分析推文和帖子的内容趋势
  5. 教育工具: 帮助学生了解文章的词汇分布
  6. 语言学习: 统计外语文章中的词汇频率
  7. 内容推荐: 基于内容关键词进行推荐

扩展功能

我们可以为这个功能添加更多特性:

rust 复制代码
use std::collections::HashMap;

// 支持停用词过滤
pub fn word_count_with_stopwords(words: &str, stopwords: &[&str]) -> HashMap<String, u32> {
    let stopword_set: std::collections::HashSet<&str> = stopwords.iter().cloned().collect();
    
    word_count(words)
        .into_iter()
        .filter(|(word, _)| !stopword_set.contains(word.as_str()))
        .collect()
}

// 返回按频率排序的结果
pub fn word_count_sorted(words: &str) -> Vec<(String, u32)> {
    let mut counts: Vec<(String, u32)> = word_count(words).into_iter().collect();
    counts.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
    counts
}

// 统计文本的各种指标
pub struct TextStats {
    pub word_count: HashMap<String, u32>,
    pub total_words: u32,
    pub unique_words: usize,
    pub average_word_length: f64,
}

pub fn analyze_text(words: &str) -> TextStats {
    let word_count = word_count(words);
    let total_words: u32 = word_count.values().sum();
    let unique_words = word_count.len();
    let total_chars: usize = word_count.keys().map(|w| w.len()).sum();
    let average_word_length = if unique_words > 0 {
        total_chars as f64 / unique_words as f64
    } else {
        0.0
    };
    
    TextStats {
        word_count,
        total_words,
        unique_words,
        average_word_length,
    }
}

与其他实现方式的比较

Python实现

python 复制代码
import re
from collections import Counter

def word_count(words):
    # 使用正则表达式提取单词
    words_list = re.findall(r"[a-z0-9]+(?:'[a-z0-9]+)*", words.lower())
    return dict(Counter(words_list))

JavaScript实现

javascript 复制代码
function wordCount(words) {
    const matches = words.toLowerCase().match(/[a-z0-9]+(?:'[a-z0-9]+)*/g);
    if (!matches) return {};
    
    return matches.reduce((counts, word) => {
        counts[word] = (counts[word] || 0) + 1;
        return counts;
    }, {});
}

Java实现

java 复制代码
import java.util.*;
import java.util.regex.*;

public class WordCount {
    public static Map<String, Integer> wordCount(String words) {
        Map<String, Integer> counts = new HashMap<>();
        Pattern pattern = Pattern.compile("[a-z0-9]+(?:'[a-z0-9]+)*");
        Matcher matcher = pattern.matcher(words.toLowerCase());
        
        while (matcher.find()) {
            String word = matcher.group();
            counts.put(word, counts.getOrDefault(word, 0) + 1);
        }
        
        return counts;
    }
}

Rust的实现相比其他语言,具有内存安全、无垃圾回收、编译时检查等优势,同时性能与C/C++相当。

总结

通过这个练习,我们学习到了:

  1. 如何使用Rust处理字符串和文本分析
  2. HashMap在统计场景中的应用
  3. 字符处理和正则表达式的使用
  4. 函数式编程和迭代器链的运用
  5. 性能优化和内存管理技巧
  6. 错误处理和边界条件的考虑

单词计数问题虽然看似简单,但它涉及了文本处理、数据结构和算法设计等多个方面。通过这个练习,我们不仅掌握了具体的实现技巧,也加深了对Rust语言特性的理解。

在实际应用中,这类文本处理功能非常常见。Rust的安全性和性能优势使得它成为构建高性能文本处理工具的优秀选择。

这个练习也展示了Rust在处理现实世界问题时的表达能力,通过类型系统和丰富的标准库,我们可以编写出既安全又高效的代码。

相关推荐
熊小猿2 小时前
Redis 缓存怎么更新?—— 四种模型与一次“迟到的删除”
java·后端·spring
火龙谷2 小时前
DrissionPage遇到iframe
开发语言·前端·javascript
方圆想当图灵2 小时前
Cursor 无法跨项目读取源码怎么办?MCP Easy Code Reader 帮你解决!
后端·cursor·mcp
HalvmånEver2 小时前
Linux的第二章 : 基础的指令(二)
linux·运维·服务器·开发语言·学习
JaguarJack2 小时前
深入理解 Laravel Middleware:完整指南
后端·php·laravel
绝无仅有2 小时前
某多多大厂面试相关计算机网络知识点总结
后端·面试·架构
风象南2 小时前
SpringBoot实现隐式参数注入
后端
绝无仅有2 小时前
调用服务出现网络错误的问题排查与解决
后端·面试·架构