这组任务使用同一份可执行参考程序和实际结果。只需Python 3.10或更新版本,不依赖第三方包。把程序保存为 algorithms-packed-string-check.py,运行 python -B algorithms-packed-string-check.py > actual.json;再运行 python -O -B algorithms-packed-string-check.py > optimized.json。两份结果应逐字节相同,并与下载的结果文件一致。验证使用显式检查,不能依靠被 -O 删除的 assert。
阅读主线是匹配区间、字宽与成本、活跃状态集、Shift-And 字符类前沿,再接编辑网格与Myers 差分列。1986年的最短插删脚本用于辨认另一种同名算法,不是本程序的内部步骤。
任务一:把字符类编译成两个字
模式有五个位置:a、a或b、b、非b、a。在程序中构造
classes = (b'a', b'ab', b'b', bytes(c for c in range(256) if c != 98), b'a')
s = ShiftAnd(classes, width=3)
交出 a、b、x 的两个掩码字,注明字数组与字内部的位序。正确答案分别是十进制 [3,3]、[6,0]、[0,1],或二进制 [011,011]、[110,000]、[000,001]。末字只用低两位。
逐字喂入 b'aabxaabya',给出每一步状态以及命中边界。状态依次为 [1,0]、[3,0]、[6,0]、[0,1]、[1,2]、[3,0]、[6,0]、[0,1]、[1,2];端点是 [5,9],区间是 [0,5)、[4,9)。重点写清第4步怎样把低字最高位传到高字,以及第5步为何同时保留一个新起点。
验收不只看端点:对每个文本前缀,直接枚举所有可能的模式前缀长度,用逐位置集合成员检查得到活跃集合,再与位向量逐位一致。公开程序实际检查263,934个这类状态;一个偶然正确的最终命中无法代替它们。
任务二:区分空模式、空类和错误的失败链接
分别执行以下三项,并写出初始边界与后续输出:
- 空模式
ShiftAnd((), 2),依次喂b''、b'a'、b''、b'b'。initial_end为0,各次feed返回[]、[1]、[]、[2];不能重复报告边界0。 - 含空类的模式
(b'a', b'', b'b'),对任意文本都不能完整匹配。它与空模式的答案正相反。 - 模式
(b'a', b'ab')、文本b'aba'。正确端点只有2。若把两个类“有交集”当成相等,给完整模式设置长度1的失败链接,读完ab后会错误地把结尾b当成首类a的匹配,进而多报端点3。
再用四个 a、字宽3验证跨字移位:若每个字独自左移、不给高字传入旧低字的最高位,读完四个 a 仍只有状态 [7,0],漏掉正确端点4。提交这两个反例的具体状态,不接受一句“位运算容易出错”。
任务三:给每个编辑分数标明边界含义
对模式 b'ababa'、文本 b'zzababa',分别创建 Myers(pattern, 'global', 3) 与 Myers(pattern, 'infix', 3)。初始分数都是5。把初始列也包括在内,逐边界输出应为
- global:
[5,5,5,4,3,2,3,2] - infix:
[5,5,5,4,3,2,1,0]
请独立建立一个普通整数网格:左边界是 C[i,0]=i,顶边界分别是 C[0,j]=j 与 C[0,j]=0,内部取删除、插入、配对或替换三个候选的最小值。比较每列全部六项,不只比较最后一格。
infix 在边界3的列是 [0,0,1,2,3,4],最终列是 [0,0,1,0,1,0];global 最终列是 [7,6,5,4,3,2]。阈值1下,infix 合格端点为6、7,global没有。解释为什么这些输出没有给出起点:模式 ab 在 acb 的末边界,后缀 acb 和 cb 都能以费用1达到。不得把一个最小分数报告成唯一片段或已恢复脚本。
任务四:手工执行跨字加法,再重建差分列
先做最短的跨字进位证据:模式 abbb、文本 a、字宽3。初始正差分 [111,001],相等掩码 [001,000]。低字 001+111=1000 产生进位1,高字必须算 000+001+1=010。完成异或、或与末字有效位裁剪后,Xh=[111,001],末行分数由4降到3。
把高字输入进位故意清零,会返回4;请给出错误高字的 Xh 为零的算式。然后恢复实现,不要把这个变体混入正常结果。
再取上题 infix 的第3步:Eq=[5,2]、Pv=[7,3]、Mv=[0,0],应得到 Xh=[7,3]、Ph=[0,0]、Mh=[7,3]。先令分数5减1,再移位,得到新 Pv=[6,3]、Mv=[0,0]。从顶边界零逐项累加差分,重建 [0,0,1,2,3,4]。
最后写出进位递推 carry_next = p_bit & (equal_bit | carry),验证它与把 P 加上 E&P 的二进制进位一致。公开程序枚举5,460组最长六位的 P,E,另穷举18种格子输入。这些有限检查要和正文的逐位推导一起交付,不能把测试数量当成一般证明。
任务五:证明分块继续与拒绝输入不会改写语义
把同一文本按任意边界分块,中间插入空块;两种算法持续使用同一对象,结果应与一次喂完整文本相同。不要在每块开始时重置状态,否则跨块出现会丢失。feed 只报告新消费位置,Myers的初始分数和Shift-And的 initial_end 都由调用方单独取一次。
至少验证字宽1、2、3、5、8、31、63,模式长度在字边界两侧,特别是62、63、64、65、126、127、128、129。每个状态必须满足:所有填充位为零,Myers正负差分没有相交位。高字不足一整字时,检查的末行位是实际第 m-1 位,不是固定第 w-1 位。
提交以下拒绝及拒绝前后状态相同的证据:宽度0、64、布尔值与浮点数;非 bytes 模式或块;非字节类;负数、256、布尔值、浮点数作为输入字节;未定义的边界模式;超过4096的模式;总位置将超过 2**63-1 的下一次消费。达到位置上限以后,空块仍合法且不改状态。参考程序39项拒绝检查覆盖这些类别。
空模式也要验证:Myers global 分数依次为0、1、2,infix依次为0、0、0。若阈值至少等于非空模式长度,infix也会在所有边界达标,因为允许空后缀;说明这为何不是“每处都有非空相似片段”的保证。
任务六:交付可复现证据,并为加速逐项计费
提交一个结果包,包含:
- 两种Python运行模式的原始输出与逐字节比较结果
- 字符类端点、每一步活跃位、两种编辑边界的完整网格列
- 任务二与四的具体失败见证,以及
a对ba时global为1、infix为0的边界注入见证 - 对所有短串的逐列核对、长模式的跨字核对、随机分块和拒绝原子性检查
- 编译表、在线状态、诊断列和保存输出的四份成本说明
本版实际计数为64,449组字符类配置、263,934个活跃状态、47,628组短串编辑配置、292,506个非初始编辑列、600组较长随机输入、1,200次分块核对、18种格子输入、5,460组进位模式及39项拒绝。随机测试使用固定种子;它是回归证据,不是概率正确性声明。
令 q=ceil(m/w)。字符类编译还要读取总长为K的显式类描述,密集表有256行、每行q个字,不能只报扫描时间。非空模式每消费一个字节扫描常数趟q个字;空模式单独做常数工作。在线表与状态为 O(256*(q+1)+q+1) 个字,保存z个匹配端点另外占 O(z),保存n个编辑分数另外占 O(n)。参考程序只留一列位级诊断;若外部把全部诊断都保存下来,还要支付相应的 O(n*q) 空间。
column() 本身逐行重建,花 O(m+1) 时间和空间;公开回归为核对而每步调用它,所以测试总耗时不能被当成生产扫描的 O(n*q)。最终说明还须区分1999年单位替换差分算法与1986年仅插删的最远前沿:这里不输出脚本,也没有利用小编辑数D来缩小搜索区。