1+ # -*- coding: utf-8 -*-
2+ """
3+ 序列比对(Sequence Alignment)——只使用 NumPy 的实现(全局比对 / Needleman–Wunsch 思想)
4+ 目标:对比两段英文句子(按“单词”为单位),找出最优对齐方式,并输出对齐结果与最终得分。
5+
6+ 核心思路:
7+ 1) 动态规划 DP 表:dp[i, j] 表示“前 i 个单词的句子1”和“前 j 个单词的句子2”的最优对齐分数。
8+ 2) 转移:来自三种情况的最大值
9+ - 对角线(↘):把 words1[i-1] 与 words2[j-1] 对齐(匹配或错配)
10+ - 上方(↑):words1[i-1] 与 GAP 对齐(相当于句子2这边空一个)
11+ - 左方(←):GAP 与 words2[j-1] 对齐(相当于句子1这边空一个)
12+ 3) 回溯:从右下角开始,根据 dp 的来源方向,反推出具体的对齐路径(即何处加 GAP)。
13+
14+ 说明:
15+ - 评分规则可以自由调整:match=+2, mismatch=-1, gap=-2(示例)
16+ - 这里采用“全局对齐”(两边都要对齐到头和尾),适合长度接近、需要整体比对的场景。
17+ """
18+
19+ import numpy as np
20+
21+
22+ def sequence_alignment_numpy (sentence1 , sentence2 ):
23+ """
24+ 功能:对两个英文句子做“按单词”的全局序列比对,并打印最优对齐与得分。
25+ 参数:
26+ sentence1, sentence2: 字符串,英文句子。
27+ """
28+ # 1) 预处理:把句子按空格切分为“单词序列”(列表)
29+ words1 = sentence1 .split ()
30+ words2 = sentence2 .split ()
31+
32+ # 两个序列的长度(m 表示句子1的单词数,n 表示句子2的单词数)
33+ m , n = len (words1 ), len (words2 )
34+
35+ # 2) 定义评分规则(可根据业务调整)
36+ match_score = 2 # 单词完全相同时的加分
37+ mismatch_score = - 1 # 单词不同(错配)时的扣分
38+ gap_penalty = - 2 # 引入空位 GAP(插入/删除)的惩罚
39+
40+ # 3) 初始化 DP 表((m+1) x (n+1)),类型为 int
41+ # dp[i, j] 含义:句子1前 i 个单词 与 句子2前 j 个单词 的最优对齐分数
42+ dp = np .zeros ((m + 1 , n + 1 ), dtype = int )
43+
44+ # 3.1) 初始化第一行:dp[0, j]
45+ # 含义:句子1为空串,与句子2前 j 个单词对齐 → 只能不断对句子2加 GAP 在句子1这边
46+ # 因此得分是 j 次 gap_penalty 的累加:0, -2, -4, ...
47+ dp [0 , :] = np .arange (0 , (n + 1 ) * gap_penalty , gap_penalty )
48+
49+ # 3.2) 初始化第一列:dp[i, 0]
50+ # 含义:句子2为空串,与句子1前 i 个单词对齐 → 只能不断对句子1加 GAP
51+ dp [:, 0 ] = np .arange (0 , (m + 1 ) * gap_penalty , gap_penalty )
52+
53+ # 4) 填表(自顶向下,自左向右)
54+ # 对于每个 (i, j),考虑三种转移来源:↘、↑、←,取最大值
55+ for i in range (1 , m + 1 ):
56+ for j in range (1 , n + 1 ):
57+ # 当前要对齐的两个单词(注意下标 -1)
58+ a = words1 [i - 1 ]
59+ b = words2 [j - 1 ]
60+
61+ # 对角线情况(↘):把 a 与 b 对齐
62+ # 若相同 → match_score;不同 → mismatch_score
63+ diag = dp [i - 1 , j - 1 ] + (match_score if a == b else mismatch_score )
64+
65+ # 上方情况(↑):把 a 与 GAP 对齐(等价于在句子2这一侧插入一个空位)
66+ up = dp [i - 1 , j ] + gap_penalty
67+
68+ # 左方情况(←):把 GAP 与 b 对齐(等价于在句子1这一侧插入一个空位)
69+ left = dp [i , j - 1 ] + gap_penalty
70+
71+ # 取三者中的最大值作为 dp[i, j]
72+ dp [i , j ] = max (diag , up , left )
73+
74+ # 5) 回溯(Traceback):从右下角 (m, n) 出发,反向找出具体对齐路径
75+ aligned1 , aligned2 = [], [] # 分别存放回溯构造出的“句子1对齐序列”和“句子2对齐序列”
76+ i , j = m , n
77+
78+ # 只要还有任一序列未回溯完,就继续
79+ while i > 0 or j > 0 :
80+ current = dp [i , j ] # 当前格子的最优得分
81+
82+ # 情况A:来自对角线(↘)——把 words1[i-1] 与 words2[j-1] 对齐
83+ # 这里分“匹配”和“错配”两种计分,但来源判断都属于对角线
84+ if i > 0 and j > 0 and (
85+ (words1 [i - 1 ] == words2 [j - 1 ] and current == dp [i - 1 , j - 1 ] + match_score )
86+ or (words1 [i - 1 ] != words2 [j - 1 ] and current == dp [i - 1 , j - 1 ] + mismatch_score )
87+ ):
88+ aligned1 .insert (0 , words1 [i - 1 ]) # 回溯是反向构造,所以用 insert(0, ...)
89+ aligned2 .insert (0 , words2 [j - 1 ])
90+ i -= 1
91+ j -= 1
92+
93+ # 情况B:来自上方(↑)——words1[i-1] 与 GAP 对齐(句子2“缺了一个”)
94+ elif i > 0 and current == dp [i - 1 , j ] + gap_penalty :
95+ aligned1 .insert (0 , words1 [i - 1 ])
96+ aligned2 .insert (0 , "_" ) # 这里用下划线 '_' 表示 GAP(空位)
97+ i -= 1
98+
99+ # 情况C:来自左方(←)——GAP 与 words2[j-1] 对齐(句子1“缺了一个”)
100+ else :
101+ aligned1 .insert (0 , "_" )
102+ aligned2 .insert (0 , words2 [j - 1 ])
103+ j -= 1
104+
105+ # 6) 打印结果(对齐后的两行与最终得分)
106+ print ("\n === Sequence Alignment Result ===" )
107+ print ("Sentence 1:" , " " .join (aligned1 ))
108+ print ("Sentence 2:" , " " .join (aligned2 ))
109+ print ("Final Alignment Score:" , dp [m , n ])
110+
111+ # 可选:打印 DP 矩阵形状与内容,方便调试/学习
112+ print ("\n DP matrix shape:" , dp .shape )
113+ print (dp )
114+
115+
116+ # === 演示入口 ===
117+ if __name__ == "__main__" :
118+ # 两段英文句子(你可以自由修改)
119+ s1 = "I love learning computer science every day"
120+ s2 = "I love to learn computer science everyday"
121+
122+ # 调用主函数,执行序列比对并打印结果
123+ sequence_alignment_numpy (s1 , s2 )
0 commit comments