
本文介绍如何使用 polars 对每组账户(acc)执行智能值分配:对正值从前往后“填充”未满账单,对负值从后往前“清空”,并确保每行结果严格介于 0 与账单上限之间。
本文介绍如何使用 polars 对每组账户(acc)执行智能值分配:对正值从前往后“填充”未满账单,对负值从后往前“清空”,并确保每行结果严格介于 0 与账单上限之间。
在财务或计费系统中,常需将一笔变动金额(如退款、补缴、调账)按顺序分配至多个关联账单行——例如:一个账户的总调整额为 +300,需依次填满其下各笔限额为 100/150/200 的账单;若为 -300,则应优先抵扣最后已填满的账单。该过程需满足三个核心约束:
- ✅ 方向性:正数自上而下填充(找首个未满行),负数自下而上清空(找最后一个非零行);
- ✅ 边界安全:每行分配结果不能超过账单上限(
val),也不能低于 0; - ✅ 累积感知:分配必须考虑已占用/已释放的历史状态(即
sum列的当前占用量)。
Polars 提供高效的窗口计算能力,可避免显式循环,完整实现该逻辑。关键思路是:
-
计算每组总目标值:
total = sum(原sum) + 新val(即账户当前所有账单已占总额 + 待分配变动额); -
模拟逐行占用过程:用
cum_sum().shift(1)计算“前 N−1 行已分配总量”(即used),作为当前行可用额度的基准; - 当前行分配值 = clip(total − used, 0, val)`:确保不超上限、不低于零,且自然体现“填满即止”或“清空即停”。
以下是完整可运行代码(基于 Polars v1.0+):
import polars as pl
# 输入数据:待分配的账户变动值(left)和原始账单明细(right)
left = pl.DataFrame({
"acc": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"val": [-100, -123, -75, -300, -77, -500, 111, 123, 300, 75]
})
right = pl.DataFrame({
"acc": [1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10],
"val": [100, 100, 100, 123, 150, 123, 70, 70, 150, 100, 150, 77, 500, 500, 500, 500, 100, 100, 100, 123, 123, 123, 100, 150, 200, 75, 75],
"sum": [100, 50, 0, 123, 0, 0, 70, 5, 150, 100, 150, 78, 500, 500, 500, 500, 10, 0, 0, 123, 0, 0, 0, 0, 0, 75, 0]
})
# 核心逻辑:三步完成分配
result = (
right
.join(left, on="acc", suffix="_new") # 关联变动值
.with_columns(
total = (pl.col("sum").sum() + pl.col("val_new")).over("acc"), # 每组总目标值
used = pl.col("val").cum_sum().shift(1).fill_null(0).over("acc") # 前序已用额度
)
.with_columns(
res = (pl.col("total") - pl.col("used"))
.clip(0, pl.col("val")) # 安全截断:[0, 账单上限]
.over("acc")
)
.select("acc", "val", "sum", "res") # 仅保留关键列
)
print(result)
? 注意事项与验证要点:
-
used使用cum_sum().shift(1)是关键:它让第 1 行看到0(无前置占用),第 2 行看到第 1 行的val,依此类推,天然实现“从前到后填充”的顺序逻辑; - 当
total (如 acc=2 总目标为 0),<code>total − used恒 ≤ 0,经clip(0, val)后全为 0,等效于“全部清空”; - 若输入
right.sum列与题设不一致(如 acc=10 的原始sum为[0,0]而非[75,0]),输出会不同——请确保初始状态准确; - 本方案时间复杂度 O(n),无需分组迭代,适合百万级账单批量处理。
该模式不仅适用于财务调账,还可延伸至库存配额分配、带宽调度、资源池动态伸缩等场景——只要问题可建模为“按序、有界、方向敏感”的值再分配,即可复用此 Polars 窗口范式。










