Python Counter 相减:库存归零和缺货,为什么一起从结果里消失

10-01 3阅读

一张库存差额表漏掉了最该关注的品类

假设手作活动开始前有四支蓝笔、两支红笔,领取记录是两支蓝笔、两支红笔和一支绿笔。做完相减,合理的差额应该同时告诉你还有什么、什么刚好用完,以及什么已经欠缺。可是把两个 Counter 直接用减号连接,结果只留下蓝笔,另外两类无声消失。

这不是计数器不能保存负数,而是减号运算采用多重集合的输出规则,只保留结果大于零的条目。用它生成可领取清单很方便,用它审查差额却会丢失信息。选择哪种写法之前,需要先明确输出是完整账面差额,还是只列仍有余量的品类。

用同一份输入对照两种减法

下面保存为 Python 文件运行,使用三点十及以上版本,本文在三点十二验证。示例只操作内存对象,先检查减号结果,再复制原始库存并调用 subtract。复制让原始数量继续保留,便于后续核对,也避免把同一个计数器同时当成期初数与结余数。

样例故意包括正数、零、负数以及从未出现的品类,随后分别生成余量和缺口。每个断言都对应一个具体行为,其中字典相等检查要求键本身也存在,防止只验证总和而漏掉某个类别。

Python Counter 相减:库存归零和缺货,为什么一起从结果里消失

AI概念配图,非真实界面

from collections import Counter

stock = Counter(blue=4, red=2)
used = Counter(blue=2, red=2, green=1)
positive = stock - used
assert dict(positive) == {'blue': 2}
print('minus:', dict(positive))

balance = stock.copy()
result = balance.subtract(used)
assert result is None
assert dict(balance) == {'blue': 2, 'red': 0, 'green': -1}
assert dict(stock) == {'blue': 4, 'red': 2}
print('signed:', dict(balance))

available = +balance
shortage = -balance
assert dict(available) == {'blue': 2}
assert dict(shortage) == {'green': 1}
assert balance.total() == 1
assert list(balance.elements()) == ['blue', 'blue']
print('shortage:', dict(shortage))

assert balance['yellow'] == 0
assert 'yellow' not in balance
assert 'red' in balance
assert Counter(red=0) == Counter()
assert dict(Counter(red=0)) != dict(Counter())
del balance['red']
assert 'red' not in balance

empty = Counter()
empty.subtract({'new': 2})
assert dict(empty) == {'new': -2}
empty.subtract({'new': 0})
assert dict(empty) == {'new': -2}
print('zero, missing and signed checks passed')

先保留差额,再选择展示范围

运行后,减号结果只有蓝笔两支;完整差额还保留红笔为零、绿笔为负一。对完整差额使用一元加号,可以得到只含正数的视图;使用一元减号,则把负数条目转成正数缺口。这两种视图都应由完整差额派生,不能先扔掉负数再期待恢复缺货信息。

subtract 会修改接收它的计数器,返回值是 None,因此不要把返回值覆盖回变量。它接受另一个计数映射,也接受逐个元素组成的可迭代对象;两种输入含义不同。传入字符串会逐字符扣减,传入字典才会按字典中的数量扣减,接口约定应说清楚。

值为零和没有这个键不是同一件事

读取缺失键会得到零,但这次读取不会把键插入计数器。已有键被赋成零也不会自动删除,所以成员测试能够区分“明确记录为零”和“从未记录”。若最终需要完全移除一个品类,应使用删除操作,并确认这不会抹去需要保留的业务状态。

三点十及以后,Counter 的相等比较把缺失计数视为零,因此只写两个 Counter 相等的断言,不足以证明键集合相同。代码同时检查字典与成员关系,就是为了让这个差别可见。若下游导出要求每个登记品类都有一行,应该按品类清单遍历,而不是按正数视图遍历。

另一个边界是 elements:它按整数计数展开元素,只输出正数次数,忽略零与负数。因此它适合把可用数量展开成领取序列,不适合重建完整差额账。total 则会把所有计数相加,负数也参与;总和为一,并不表示只剩一种物品。

让计数规则与业务校验各负其责

计数器允许负数,只代表容器能存储它,不代表业务允许最终库存为负。若系统要求拒绝超领,先在副本上扣减,检查有没有负值,再决定是否采用结果;若业务允许登记欠缺,就保留差额并另出缺口清单。容器不会替你选择这项规则。

这份内存实验也不承担并发库存更新。多人同时领取时,还需要在实际存储层保证读取、校验与扣减的协调。迁移到真实数据前,至少保留恰好归零、出现新缺口、空输入和重复扣减的样本,让每一次信息过滤都有明确用途。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。