ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

600 人排考场:Excel 排序后 560 对相邻同单位,换 40 行代码降到 0

600 人排考场:Excel 排序后 560 对相邻同单位,换 40 行代码降到 0

我做了什么

600 名虚构考生、30 个报考单位、5 个岗位、20 个考场 × 30 座。我用 Excel 里最常见的两种排法各排了一遍,再用 40 行 Python 排了一遍,三份座位表按同一套规则校验。结论就在标题里:Excel 那版 580 对相邻座位里有 560 对是同一个单位,脚本那版是 0 对。下面的数字都能用文末脚本复现,数据是造的,规则是真的。

背景:为什么大家还在用 Excel

考务公司的排考场多半是这么起步的:公告发出去,报名表收回来,一个 xlsx,按单位排个序,从第 2 行开始往下贴座位号,30 个一行,一个考场一屏。零成本,谁都会,临时招来的学生助理培训十分钟就能上手。这不是偷懒,是资源约束下合理的做法——排一场 600 人的考试,服务费可能就几千块,不值得先买一套系统。

问题出在量变到质变那一步:报考单位超过 20 个、考场超过 10 个、开考前临时改动的考生超过 5 个,Excel 排出来的表就没人能证明它是对的。而"能不能证明它是对的",恰恰是考务事故里最容易被追责的那一环。

问题一:Excel 会排序,不会"约束"

排考场本质是给每个座位找一个满足条件的人。规则通常三条:同一单位的人不相邻、同一岗位的人分散、每个考场人数相等。Excel 的排序只能处理一条,而且方向是反的——它把同单位的人排到一块儿(这正是排序的定义),恰好是需要避免的。

实测数据(580 对相邻座位,同考场内座位号相差 1 的两两组合):

排法同单位相邻同岗位相邻
按单位排序后顺序贴座560 / 5800 / 580
随机打散后顺序贴座20 / 580110 / 580
逐个考场回溯求解0 / 5800 / 580

非显然结论在这:让助手"手动打散"确实能把同单位相邻从 560 压到 20,但同岗位相邻从 0 顶到 110。两个约束互相打架——单位内岗位本来就是轮转分配的,你打散了单位,等于把岗位也一起随机了。这就是手工排表总要来回挪的原因:第 18 个考场排舒服了,前面 17 个又露出两个同单位的。

Excel 里没有"约束"这个对象,只有单元格里的值和公式,约束只能靠人眼盯。

问题二:改 1 个人,463 个座位号跟着变

考务现场的变动是常态:资格审查刷掉两个、有人弃考、有人身份证号填错要改报考单位。Excel 里最快的改法是删行,下面的行整体上移一行。

实测:删掉第 137 号考生那一行,600 人的座位表里有 463 人的座位号变了,横跨 16 个考场。这 463 份准考证如果是提前打印或已发到考生手机上,全部作废。

rows=sorted(cands,key=lambdar:(r["unit"],r["id"]))seats=[{"id":r["id"],"room":i//30+1,"seat":i%30+1}fori,rinenumerate(rows)]rows2=[rforrinrowsifr["id"]!=137]# 删掉一个缺考的seats2=[{"id":r["id"],"room":i//30+1,"seat":i%30+1}fori,rinenumerate(rows2)]print(sum(1fora,binzip(seats,seats2)ifa["room"]!=b["room"]ora["seat"]!=b["seat"]))

座位号是"行号算出来的",行号一动,座位号就动。这不是操作失误,是按行号定位这种设计本身的必然后果。要让它不动,座位号必须是存下来的数据,不能是算出来的位置。

问题三:编号一换格式,两张表就关联不上

这一条最隐蔽,也最容易在考试前一天晚上爆。报名表里的报考序号是文本(0137,带前导零),准考证表里存成了数字(137)。两张表按序号一关联,600 行全部匹配不上。

text_ids=[f"{c['id']:04d}"forcincands]# 报名表:'0137'num_ids={c["id"]forcincands}# 准考证表:137print(sum(1fortintext_idsiftinnum_ids))# 0print(sum(1fortintext_idsifint(t)innum_ids))# 600

同一个坑还有共享表格的并发:两个人同时改,各自看到的是自己那份,谁后点保存谁说了算。实测两个人各改 8 条,后保存的那次写盘为准,前一个人的 8 条缺考记录静默消失,表格不报警、不弹窗、不留痕。

换成代码怎么排:40 行、5.53 毫秒、0 违规

排考场是约束满足问题:给每个座位挑一个"和前一座位不同单位、不同岗位"的人,挑完从候选池里拿走。逐个考场贪心,某个考场排不下去就整场重来(随机顺序换了,结果就变了):

defbuild(rooms=20,seats=30,seed=7):rnd=random.Random(seed)pool=[dict(c)forcincands]plan=[]forroominrange(1,rooms+1):whileTrue:rnd.shuffle(pool)seq,used=[],set()forsinrange(1,seats+1):pu=seq[-1]["unit"]ifseqelseNonepp=seq[-1]["post"]ifseqelseNonepick=next((cforcinpoolifid(c)notinusedandc["unit"]!=puandc["post"]!=pp),None)ifpickisNone:break# 本考场排不下去,整场重来used.add(id(pick))seq.append({**pick,"room":room,"seat":s})iflen(seq)==seats:plan.extend(seq)pool=[cforcinpoolifid(c)notinused]breakreturnplan

实测结果:600 人全部落座,5.53 毫秒,0 次重来,同单位相邻 0 对、同岗位相邻 0 对。

真正的差别不在速度,在于"能验证"。同一份座位表丢进一个 20 行的校验函数,违规数立刻出来;Excel 那版只能人工逐考场数,20 个考场 580 对,数到第 6 个考场眼睛就花了,剩下的靠"应该没问题"。

结果与数据

指标Excel 排序法随机打散脚本求解
同单位相邻560 / 58020 / 5800 / 580
同岗位相邻0 / 580110 / 5800 / 580
改 1 人后座位号变化463 人 / 16 考场—座位号入库,只动被改的那 1 人
排一遍耗时0.38 ms + 人工贴号0.55 ms + 人工贴号5.53 ms 全流程
能否机器校验否否是

耗时那一列得说清楚:Excel 的 0.38 毫秒只算了排序和编号,人工贴号、复检、改错的时间没算进去,那才是大头;脚本的 5.53 毫秒是排完即成品。

完整代码已开源(含座位编排、审核规则引擎等核心模块):https://gitee.com/281064811/qiuzhizhaopin

返回列表