
我做了什么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 份准考证如果是提前打印或已发到考生手机上全部作废。rowssorted(cands,keylambdar:(r[unit],r[id]))seats[{id:r[id],room:i//301,seat:i%301}fori,rinenumerate(rows)]rows2[rforrinrowsifr[id]!137]# 删掉一个缺考的seats2[{id:r[id],room:i//301,seat:i%301}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]# 报名表0137num_ids{c[id]forcincands}# 准考证表137print(sum(1fortintext_idsiftinnum_ids))# 0print(sum(1fortintext_idsifint(t)innum_ids))# 600同一个坑还有共享表格的并发两个人同时改各自看到的是自己那份谁后点保存谁说了算。实测两个人各改 8 条后保存的那次写盘为准前一个人的 8 条缺考记录静默消失表格不报警、不弹窗、不留痕。换成代码怎么排40 行、5.53 毫秒、0 违规排考场是约束满足问题给每个座位挑一个和前一座位不同单位、不同岗位的人挑完从候选池里拿走。逐个考场贪心某个考场排不下去就整场重来随机顺序换了结果就变了defbuild(rooms20,seats30,seed7):rndrandom.Random(seed)pool[dict(c)forcincands]plan[]forroominrange(1,rooms1):whileTrue:rnd.shuffle(pool)seq,used[],set()forsinrange(1,seats1):puseq[-1][unit]ifseqelseNoneppseq[-1][post]ifseqelseNonepicknext((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