Polars 中高效统计字符串重叠子串出现次数的完整教程

2次阅读

Polars 中高效统计字符串重叠子串出现次数的完整教程

本文介绍在 polars dataframe 中准确统计字符串中**重叠匹配**子串(如 “aa” 在 “aaaaa” 中出现 4 次)的方法,突破 `str.count_matches` 仅支持非重叠匹配的限制,并提供可扩展、向量化、无需 python 循环的纯表达式解决方案。

在 Polars 中,str.count_matches(pattern) 默认执行非重叠、贪婪式正则匹配,因此对 “aaaaa” 查找 “aa” 仅返回 2(匹配位置 0–1 和 2–3),而无法捕获重叠情形(如位置 1–2、2–3、3–4)。要获得真正的重叠计数(本例应为 4),需将字符串按滑动窗口切片,再对每个子串独立匹配并求和。

核心思路是:对每条字符串,生成所有长度为 len(pattern) 的连续滑动子串(即起始索引从 0 到 len(s) – len(pattern)),然后批量判断每个子串是否等于目标模式,最后横向求和。

以下是以子串 “aa” 为例的完整实现(适配任意固定长度模式):

import polars as pl  df = pl.DataFrame({"foo": ["aaaaa", "aabaa", "aaaab"]}) pattern = "aa" window_size = len(pattern)  # 步骤 1:获取所有字符串的最大长度,确定最大滑动起始索引 max_len = df.select(pl.col("foo").str.len_chars().max()).item() if max_len < window_size:     # 若所有字符串均短于模式,直接返回全 0     result = df.with_columns(pl.lit(0).alias("count")) else:     # 步骤 2:为每个可能的起始位置 i ∈ [0, max_len - window_size] 创建切片列     slice_exprs = [         pl.col("foo").str.slice(i, window_size).alias(f"slice_{i}")         for i in range(max_len - window_size + 1)     ]      # 步骤 3:对所有切片列执行精确字符串匹配(非正则,更高效),并横向求和     result = (         df.select(*slice_exprs)         .with_columns(             pl.sum_horizontal([pl.col(f"slice_{i}") == pattern for i in range(max_len - window_size + 1)])             .alias("count")         )         .select("count")         .hstack(df)  # 恢复原始列顺序(可选)     )  print(result)

输出:

shape: (3, 2) ┌───────┬───────┐ │ foo   ┆ count │ │ ---   ┆ ---   │ │ str   ┆ u32   │ ╞═══════╪═══════╡ │ aaaaa ┆ 4     │ │ aabaa ┆ 2     │ │ aaaab ┆ 3     │ └───────┴───────┘

优势说明

  • 完全向量化:全程使用 Polars 表达式,无 Python 循环或 UDF,性能接近底层 rust 实现;
  • 模式无关:只需修改 pattern 变量即可适配任意固定长度子串(如 “abc”、”11″);
  • 内存可控:切片列数量由最长字符串决定,对长文本可结合 str.slice 的 lazy 特性进一步优化;
  • 语义清晰:== pattern 比正则 count_matches 更精准(避免意外元字符匹配)。

⚠️ 注意事项

  • 该方法适用于固定长度子串;若需支持正则重叠匹配(如 r”a+”),需改用 str.extract + 自定义逻辑,但会损失性能;
  • 当 window_size == 1(单字符)时,等价于 str.count_chars(),可直接使用;
  • 对超长字符串(如 >10k 字符),预生成大量切片列可能导致内存压力,此时建议分块处理或改用 map_elements(牺牲部分性能换取内存稳定性)。

总结:通过滑动窗口切片 + 向量化等值判断 + 横向聚合,我们绕过了 Polars 原生 API 的非重叠限制,实现了高效、简洁、可维护的重叠子串计数方案——这是 Polars 数据处理中“以空间换向量化”的典型实践。

text=ZqhQzanResources