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

在Rust Polars中复现Python的Jaccard相似度计算

在Rust Polars中实现Jaccard相似度计算

核心问题拆解

你遇到的类型不匹配,本质是处理Polars字符串列时的所有权、引用与空值冲突:当从列表列取数据时,易得到Option<&str>类型(兼容空值),若与直接收集的HashSet<String>混用,就会出现类型不兼容;同时需要确保函数能生成Polars认可的Series。

分步解决方案

1. 实现通用Jaccard计算函数

先写一个适配空值、统一类型的基础函数,自动过滤空值并处理字符串引用:

use std::collections::HashSet;
use polars::prelude::*;

fn jaccard_similarity<T, U>(a: T, b: U) -> f64
where
    T: IntoIterator<Item = Option<&str>>,
    U: IntoIterator<Item = Option<&str>>,
{
    let set_a: HashSet<_> = a.into_iter().flatten().collect();
    let set_b: HashSet<_> = b.into_iter().flatten().collect();
    
    let intersection = set_a.intersection(&set_b).count();
    let union = set_a.len() + set_b.len() - intersection;
    
    if union == 0 { 0.0 } else { intersection as f64 / union as f64 }
}
  • flatten()自动过滤None空值,避免空值干扰计算
  • 统一用&str作为迭代元素类型,规避所有权与引用的类型冲突

2. 适配Polars DataFrame的列操作

用Polars的map_binary方法处理两列的二元计算,直接生成符合要求的Series:

fn apply_jaccard(df: &mut DataFrame) -> Result<Series, PolarsError> {
    df.map_binary(
        "col1",
        "col2",
        |s1: &ListChunked, s2: &ListChunked| {
            let mut out = Float64Chunked::with_capacity("jaccard", s1.len());
            
            for (a, b) in s1.into_iter().zip(s2.into_iter()) {
                let val = match (a, b) {
                    (Some(list_a), Some(list_b)) => {
                        // 将Polars列表元素转为Option<&str>迭代器,适配基础函数
                        let iter_a = list_a.into_iter().map(|s| s.as_str());
                        let iter_b = list_b.into_iter().map(|s| s.as_str());
                        jaccard_similarity(iter_a, iter_b)
                    }
                    _ => 0.0, // 任意一列为空时返回0,可按需调整逻辑
                };
                out.append_value(val);
            }
            
            Ok(out.into_series())
        },
    )
}
  • map_binary专门处理两列的逐行二元操作,输入为ListChunked(对应Python中转为列表的字符串列)
  • 遍历每行的两个列表,用as_str()统一类型,完全适配之前的基础计算函数
  • 最终生成Float64Chunked并转为Series,完全符合Polars的返回要求

3. 类型冲突的本质解决思路

之前的HashSet<Option<&str>>与HashSet<String>冲突,是因为:

  • 直接收集ListChunked元素为HashSet<String>会获取所有权,而&str是引用,类型不兼容
  • 解决方案是统一用引用类型(&str)处理,或显式将&str转为String,前者更高效

使用示例

假设DataFrame包含col1和col2两个列表列,调用方式如下:

fn main() -> Result<(), PolarsError> {
    let mut df = df!(
        "col1" => &[vec!["a", "b"], vec!["c"], vec![]],
        "col2" => &[vec!["b", "c"], vec!["c"], vec!["d"]]
    )?;
    
    let jaccard_series = apply_jaccard(&mut df)?;
    df.hstack_mut(&[jaccard_series])?;
    
    println!("{}", df);
    Ok(())
}

运行后会生成包含jaccard列的DataFrame,结果与Python端一致:0.333333、1.0、0.0

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 16:50:23