题目:AI 生成用例/模型回答的自动化回归里,需要用「与人工标准答案的相似度」做卡口指标。请实现 text_similarity:
- 归一化:转小写,并删除所有空白字符(半角空格、制表符、换行、全角空格);
- 计算归一化后两个字符串的编辑距离(插入、删除、替换代价均为 1);
- 相似度 =
1 - 编辑距离 / max(len(a), len(b)); - 两者归一化后均为空串时返回
1.0。
def text_similarity(reference: str, prediction: str) -> float:
"""计算模型输出与参考答案的归一化编辑距离相似度。
Args:
reference: 人工标准答案
prediction: 模型实际输出
Returns:
0.0 ~ 1.0 之间的相似度;两串归一化后均为空时返回 1.0
"""
pass
assert text_similarity("Hello World", "helloworld") == 1.0
assert text_similarity("", "") == 1.0
assert abs(text_similarity("kitten", "sitting") - (1 - 3 / 7)) < 1e-9
assert text_similarity("abc", "") == 0.0
assert text_similarity("测试 用例", "测试用例") == 1.0
assert abs(text_similarity("登录成功", "登陆成功") - 0.75) < 1e-9
难度:高级
题型:笔试(编程题)
参考答案/题解:
思路一:自顶向下递归 + 记忆化(先把「最后字符相同就跳过、不同就三种操作取最小」写成递归,再补 lru_cache,直观好讲)
import re
from functools import lru_cache
def normalize(text: str) -> str:
return re.sub(r"\s+", "", text).lower()
def text_similarity_v1(reference: str, prediction: str) -> float:
a, b = normalize(reference), normalize(prediction)
@lru_cache(maxsize=None)
def dfs(i: int, j: int) -> int:
if i == 0:
return j
if j == 0:
return i
if a[i - 1] == b[j - 1]:
return dfs(i - 1, j - 1)
return 1 + min(dfs(i - 1, j), dfs(i, j - 1), dfs(i - 1, j - 1))
longest = max(len(a), len(b))
if longest == 0:
return 1.0
return 1 - dfs(len(a), len(b)) / longest
思路二:滚动数组 DP(推荐,空间 O(min(m, n)))
import re
def text_similarity(reference: str, prediction: str) -> float:
a, b = re.sub(r"\s+", "", reference).lower(), re.sub(r"\s+", "", prediction).lower()
if len(a) < len(b):
a, b = b, a # 短串当列,省内存
m, n = len(a), len(b)
if m == 0:
return 1.0 # 两个都是空串
prev = list(range(n + 1))
for i in range(1, m + 1):
cur = [i] + [0] * n
for j in range(1, n + 1):
if a[i - 1] == b[j - 1]:
cur[j] = prev[j - 1] # 代价 0,直接继承左上角
else:
cur[j] = 1 + min(prev[j], cur[j - 1], prev[j - 1])
prev = cur
return 1 - prev[n] / m
面试现场验证过,assert 全部通过:kitten/sitting = 0.5714285714285714,登录成功/登陆成功 = 0.75,"abc"/"" = 0.0,""/"" = 1.0。
采分点:
- 归一化写法:
re.sub(r"\s+", "", s).lower(),\s在 Python3 下能匹配全角空格\u3000,别用s.strip()(只去首尾,中间空格留着就错)。 - 空串边界:两串都空要返回 1.0,必须在除法前判断,否则
ZeroDivisionError。 - 分母取
max(len(a), len(b)),取sum或min都会让相似度失真。 - DP 递推式讲清三种操作的含义:
prev[j]删除、cur[j-1]插入、prev[j-1]替换;字符相等时左上角代价为 0,不是 1。 - 复杂度:时间 O(mn);朴素二维表空间 O(mn),滚动数组优化到 O(min(m,n))。纯递归不记忆化是 O(3^(m+n)),现场会被判不通过。
- 工程延伸(加分):中文按字符切分粒度偏细,实际项目可用分词后词级编辑距离或 ROUGE / BERTScore;相似度卡口要按业务分档(如 ≥0.9 视为通过),而不是只看单一阈值。