You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

利用Polars库加速元组序列间的最小Levenshtein距离计算

问题描述

我的pandas DataFrame的sequence列存储着元组形式的序列,示例数据如下:

id       sequence
33268    [(59, 2), (91, 2), (112, 2), (126, 2), (0, 3),...
44360    [(137, 0), (208, 0), (251, 0), (0, 1), (46, 1)...
51943    [(117, 0), (153, 1), (190, 1), (0, 7), (218, 7...
53888    [(0, 0), (18, 0), (58, 0), (69, 0), (86, 0), (...
38505    [(0, 1), (96, 1), (131, 1), (188, 1), (229, 1)...

我需要计算每个序列与列表l=[...]中同形式序列的最小Levenshtein距离。
已实现以下Levenshtein距离计算函数:

def levenshtein(s, t):
    if s == t: return 0
    elif len(s) == 0: return len(t)
    elif len(t) == 0: return len(s)
    v0 = [None] * (len(t) + 1)
    v1 = [None] * (len(t) + 1)
    for i in range(len(v0)):
        v0[i] = i
    for i in range(len(s)):
        v1[0] = i + 1
        for j in range(len(t)):
            cost = 0 if s[i] == t[j] else 1
            v1[j + 1] = min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost)
        for j in range(len(v0)):
            v0[j] = v1[j]

    return v1[len(t)]

目前用pandas.DataFrame.apply完成计算,但速度极慢,请问能否用Polars库提升计算速度?

解决方案

当然可以用Polars提升计算速度,核心是利用Polars的向量化操作和并行处理能力,同时优化计算逻辑,具体步骤如下:

1. 转换数据到Polars DataFrame

先将pandas DataFrame转为Polars格式,操作简单:

import polars as pl

pl_df = pl.from_pandas(pandas_df)

2. 结合map_elements优化计算

Polars的map_elements比pandas的apply效率更高,默认支持多线程并行。建议加上剪枝逻辑减少不必要的计算:

# 预处理目标列表,提前存储序列长度,避免重复计算
preprocessed_l = [(seq, len(seq)) for seq in l]

def min_levenshtein(row_seq):
    min_dist = float('inf')
    row_len = len(row_seq)
    for t, t_len in preprocessed_l:
        # 剪枝:长度差大于当前最小距离时直接跳过
        if abs(row_len - t_len) >= min_dist:
            continue
        dist = levenshtein(row_seq, t)
        if dist < min_dist:
            min_dist = dist
            # 找到0距离直接返回,无需继续计算
            if min_dist == 0:
                break
    return min_dist

# 应用到sequence列,生成最小距离列
pl_df = pl_df.with_columns(
    pl.col("sequence").map_elements(min_levenshtein, return_dtype=pl.Int32).alias("min_lev_dist")
)

3. 批量计算方案(适合小规模目标列表)

如果列表l规模不大,可以通过笛卡尔积批量计算距离,再按id取最小值,更好利用Polars的向量化优势:

# 将目标列表转为Polars DataFrame
target_df = pl.DataFrame({"target_seq": l})

# 笛卡尔积关联原数据与目标序列
cross_df = pl_df.join(target_df, how="cross")

# 批量计算每对序列的Levenshtein距离
cross_df = cross_df.with_columns(
    pl.struct(["sequence", "target_seq"])
      .map_elements(lambda x: levenshtein(x["sequence"], x["target_seq"]), return_dtype=pl.Int32)
      .alias("lev_dist")
)

# 按id分组取最小距离
result_df = cross_df.group_by("id").agg(pl.col("lev_dist").min().alias("min_lev_dist"))

4. 极致提速:结合Numba编译Levenshtein函数

Python循环是性能瓶颈,用Numba编译Levenshtein函数可以大幅降低开销:

from numba import jit

@jit(nopython=True)
def numba_levenshtein(s, t):
    # Numba对元组支持更好,调用前需将列表转成tuple
    if s == t:
        return 0
    len_s = len(s)
    len_t = len(t)
    if len_s == 0:
        return len_t
    if len_t == 0:
        return len_s
    v0 = list(range(len_t + 1))
    v1 = [0]*(len_t + 1)
    for i in range(len_s):
        v1[0] = i + 1
        for j in range(len_t):
            cost = 0 if s[i] == t[j] else 1
            v1[j+1] = min(v1[j] + 1, v0[j+1] + 1, v0[j] + cost)
        v0, v1 = v1, v0
    return v0[len_t]

# 调整min_levenshtein函数使用编译后的版本
def min_levenshtein(row_seq):
    min_dist = float('inf')
    row_seq_tuple = tuple(row_seq)
    for t in l:
        t_tuple = tuple(t)
        dist = numba_levenshtein(row_seq_tuple, t_tuple)
        if dist < min_dist:
            min_dist = dist
            if min_dist == 0:
                break
    return min_dist

内容的提问来源于stack exchange,提问作者exch_cmmnt_memb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 00:12:15