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

如何实现更快的Python分组笛卡尔积对遍历算法?

百万级数据下,同分组内笛卡尔积配对的高效实现

我有两个数据集(lhs和rhs),各包含一个分组变量字段(还有其他变量)。需求是:

  • 仅保留lhs与rhs分组变量值相同的笛卡尔积对,忽略仅单侧存在的分组对应的项
  • 遍历配对时要执行自定义逻辑(示例中是单词长度比较,实际是一系列函数调用)
  • 分组变量值由外部参数动态生成,且数据量可达百万级,速度是核心需求,同时不希望对数据排序

目前我用字典手动建索引的方案能得到正确结果,但想找更快的实现;试过numpy版本,结果正确但速度更慢。请问有更高效的策略吗?


现有正确实现(字典索引版)

lhs_data = [{'lhs_id':'001', 'word':'clean'},
            {'lhs_id':'002', 'word':'stop'},
            {'lhs_id':'003', 'word':'those'}]
rhs_data = [{'rhs_id':'001', 'word':'quack'},
            {'rhs_id':'002', 'word':'step'},
            {'rhs_id':'003', 'word':'stir'},
            {'rhs_id':'004', 'word':'state'},
            {'rhs_id':'005', 'word':'storm'},
            {'rhs_id':'006', 'word':'thin'},
            {'rhs_id':'008', 'word':'thaw'},
            {'rhs_id':'009', 'word':'thorn'},
            {'rhs_id':'010', 'word':'thumb'},
            {'rhs_id':'011', 'word':'true'},
            {'rhs_id':'012', 'word':'trade'}]

# 创建分组变量与索引映射
lhs_allgroups = {}
rhs_allgroups = {}
for recidx in range(len(lhs_data)):
   lhs_group = lhs_data[recidx]['word'][0:2]
   if lhs_group in lhs_allgroups:
      lhs_allgroups[lhs_group].append(recidx)
   else:
      lhs_allgroups[lhs_group] = [recidx]
for recidx in range(len(rhs_data)):
   rhs_group = rhs_data[recidx]['word'][0:2]
   if rhs_group in lhs_allgroups:
      if rhs_group in rhs_allgroups:
         rhs_allgroups[rhs_group].append(recidx)
      else:
         rhs_allgroups[rhs_group] = [recidx]

# 遍历配对并执行逻辑
for lhs_group in lhs_allgroups:
   if lhs_group in rhs_allgroups:
      for lhs_idx in lhs_allgroups[lhs_group]:
         for rhs_idx in rhs_allgroups[lhs_group]:
            if len(lhs_data[lhs_idx]['word']) == len(rhs_data[rhs_idx]['word']):
               print(lhs_data[lhs_idx]['lhs_id'], lhs_data[lhs_idx]['word'],
                     rhs_data[rhs_idx]['rhs_id'], rhs_data[rhs_idx]['word'])

正确输出:

002 stop 002 step
002 stop 003 stir
003 those 009 thorn
003 those 010 thumb

较慢的Numpy实现

import numpy as np

lhs_data = np.array([('001', 'clean'),
                     ('002', 'stop'),
                     ('003', 'those')],
                    dtype=[('lhs_id', 'S3'), ('word', 'S10')])
rhs_data = np.array([('001', 'quack'),
                     ('002', 'step'),
                     ('003', 'stir'),
                     ('004', 'state'),
                     ('005', 'storm'),
                     ('006', 'thin'),
                     ('008', 'thaw'),
                     ('009', 'thorn'),
                     ('010', 'thumb'),
                     ('011', 'true'),
                     ('012', 'trade')],
                    dtype=[('rhs_id', 'S3'), ('word', 'S10')])

# 生成分组变量
lhs_grouping_var = np.array([row['word'][:2] for row in lhs_data])
rhs_grouping_var = np.array([row['word'][:2] for row in rhs_data])

# 获取公共分组
lhs_unique = np.unique(lhs_grouping_var)
rhs_unique = np.unique(rhs_grouping_var)
common = np.intersect1d(lhs_unique, rhs_unique)

# 遍历配对
for group in common:
   lhs_idx = np.where(lhs_grouping_var == group)
   rhs_idx = np.where(rhs_grouping_var == group)
   for l in lhs_idx[0]:
      for r in rhs_idx[0]:
         if len(lhs_data[l]['word']) == len(rhs_data[r]['word']):
            print(lhs_data[l]['lhs_id'], lhs_data[l]['word'], rhs_data[r]['rhs_id'], rhs_data[r]['word'])

高效优化策略

1. 优化字典索引构建(轻量无依赖)

用collections.defaultdict简化代码,同时预取分组内的所有记录,减少循环内的字典查找开销:

from collections import defaultdict

lhs_data = [{'lhs_id':'001', 'word':'clean'},
            {'lhs_id':'002', 'word':'stop'},
            {'lhs_id':'003', 'word':'those'}]
rhs_data = [{'rhs_id':'001', 'word':'quack'},
            {'rhs_id':'002', 'word':'step'},
            {'rhs_id':'003', 'word':'stir'},
            {'rhs_id':'004', 'word':'state'},
            {'rhs_id':'005', 'word':'storm'},
            {'rhs_id':'006', 'word':'thin'},
            {'rhs_id':'008', 'word':'thaw'},
            {'rhs_id':'009', 'word':'thorn'},
            {'rhs_id':'010', 'word':'thumb'},
            {'rhs_id':'011', 'word':'true'},
            {'rhs_id':'012', 'word':'trade'}]

# 构建lhs分组索引
lhs_groups = defaultdict(list)
for idx, rec in enumerate(lhs_data):
    group = rec['word'][:2]
    lhs_groups[group].append(idx)

# 仅保留lhs存在的分组,构建rhs索引
rhs_groups = defaultdict(list)
for idx, rec in enumerate(rhs_data):
    group = rec['word'][:2]
    if group in lhs_groups:
        rhs_groups[group].append(idx)

# 遍历配对,预取分组内记录减少查找
for group in lhs_groups:
    if group not in rhs_groups:
        continue
    # 预取当前分组的所有记录
    lhs_recs = [lhs_data[i] for i in lhs_groups[group]]
    rhs_recs = [rhs_data[i] for i in rhs_groups[group]]
    for lhs_rec in lhs_recs:
        lhs_len = len(lhs_rec['word'])
        for rhs_rec in rhs_recs:
            if len(rhs_rec['word']) == lhs_len:
                print(lhs_rec['lhs_id'], lhs_rec['word'], rhs_rec['rhs_id'], rhs_rec['word'])

2. Pandas向量化分组(适合百万级结构化数据)

Pandas底层用C实现分组与合并操作,比纯Python循环快一个数量级,适合结构化数据场景:

import pandas as pd

lhs_df = pd.DataFrame(lhs_data)
rhs_df = pd.DataFrame(rhs_data)

# 生成分组变量
lhs_df['group'] = lhs_df['word'].str[:2]
rhs_df['group'] = rhs_df['word'].str[:2]

# 过滤仅单侧存在的分组
common_groups = lhs_df['group'].unique()
rhs_df = rhs_df[rhs_df['group'].isin(common_groups)]

# 内连接自动生成同分组的笛卡尔积
merged = lhs_df.merge(rhs_df, on='group', how='inner')

# 执行自定义过滤逻辑
filtered = merged[merged['word_x'].str.len() == merged['word_y'].str.len()]

# 输出结果
for _, row in filtered.iterrows():
    print(row['lhs_id'], row['word_x'], row['rhs_id'], row['word_y'])

3. Numba JIT加速(保留Python逻辑,接近C速度)

如果需要保留自定义循环逻辑,用Numba编译循环,大幅提升执行速度:

from numba import jit
import numpy as np
from collections import defaultdict

# 转成NumPy结构化数组
lhs_dtype = [('lhs_id', 'U3'), ('word', 'U10')]
rhs_dtype = [('rhs_id', 'U3'), ('word', 'U10')]

lhs_np = np.array([(d['lhs_id'], d['word']) for d in lhs_data], dtype=lhs_dtype)
rhs_np = np.array([(d['rhs_id'], d['word']) for d in rhs_data], dtype=rhs_dtype)

# 构建分组索引
lhs_groups = defaultdict(list)
for idx in range(len(lhs_np)):
    group = lhs_np[idx]['word'][:2]
    lhs_groups[group].append(idx)

rhs_groups = defaultdict(list)
for idx in range(len(rhs_np)):
    group = rhs_np[idx]['word'][:2]
    if group in lhs_groups:
        rhs_groups[group].append(idx)

# Numba编译配对逻辑
@jit(nopython=True)
def process_pairs(lhs_data, rhs_data, lhs_groups, rhs_groups):
    results = []
    for group in lhs_groups:
        if group not in rhs_groups:
            continue
        lhs_idxs = lhs_groups[group]
        rhs_idxs = rhs_groups[group]
        for l_idx in lhs_idxs:
            lhs_word = lhs_data[l_idx]['word']
            lhs_len = len(lhs_word)
            lhs_id = lhs_data[l_idx]['lhs_id']
            for r_idx in rhs_idxs:
                rhs_word = rhs_data[r_idx]['word']
                if len(rhs_word) == lhs_len:
                    rhs_id = rhs_data[r_idx]['rhs_id']
                    results.append((lhs_id, lhs_word, rhs_id, rhs_word))
    return results

# 执行并输出
results = process_pairs(lhs_np, rhs_np, lhs_groups, rhs_groups)
for res in results:
    print(res[0], res[1], res[2], res[3])

方案对比

方案优势适用场景
优化后的字典方案轻量、无额外依赖、逻辑灵活非结构化数据、高度自定义逻辑
Pandas向量化方案速度快、代码简洁、内置大数据优化结构化数据、百万级以上数据集
Numba加速方案保留Python逻辑、接近C的执行速度复杂自定义逻辑、循环密集型场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 01:55:35