使用 AI 解决一道算法题
善用 AI。 AI 可以是一个好帮手。
问题
问题是工作中遇到的。
题目很简单: 将一个文件的文本内容中的命中指定正则表达式的部分用 <qthighlight-- 命中规则的文本 --qthighlight>
包围起来。每个这样的命中内容,如果前后五行没有这样的标签围起来的内容,则作为一个独立块输出;如果前后五行有这样的标题,则需要连起来作为一个块输出。
比如给定正则 "123", "GET", 文本内容如下:
<?
php eval($_GET['cmd']);
echo 123;
?>
则输出
{"StartLine":0,"PartContent":"<?nnphp eval($_<qthighlight--GET--qthighlight>['cmd']);nnecho <qthighlight--123--qthighlight>;n?>"}
给定文本内容:
<?
php eval($_GET['cmd']);
echo 123;
?>
输出:
{"StartLine":0,"PartContent":"<?nnphp eval($_<qthighlight--GET--qthighlight>['cmd']);nnnnnnnnnnnnecho <qthighlight--123--qthighlight>;n?>"}
{"StartLine":10,"PartContent":""}
求解受挫
复用现有方法
遇到这个问题,首先想到的是能不能复用现有方法。现有方法 HighlightBase64 已经能够实现对于一个正则表达式输出这样的内容。
很快写了个实现。这个实现有个局限性:因为后续匹配都是基于第一次匹配后的 PartContent,如果第一次匹配的 PartContent 不能匹配其它正则表达式,则命中结果是缺失的。
for _, regex := range Regexes {
matchContents := HighlightBase64(content, regex)
if slice_utils.IsNotEmpty(matchContents) {
startLine = matchContents[0].StartLine
for _, matchContent := range matchContents {
if startLine > matchContent.StartLine {
// 取所有匹配最小匹配行
startLine = matchContent.StartLine
}
// 每次都用标红的文本内容作为下一次标红的基础
content = util.Base64Encode([]byte(matchContent.PartContent))
}
}
}
组合高亮块
接着又想到,能不能把命中不同规则的高亮块组合起来呢 ?
发现组合这些高亮块太难了,因为同一行可能命中多个正则,高亮的位置都不相同,很难组合。
这可犯难了。
新思路
只能另起蹊径。无法组合高亮块,那只能先用所有正则表达式匹配文本内容,把所有高亮块都替换掉,然后再生成对应的区域了。
想到用 AI 来辅助完成这件事。逐步来完成这件事:
(1)给定文本 content 和一个正则表达式 regex, 将 content 中命中正则 regex 的文本用 <qthighlight-- --qthighlight> 围起来,生成新的文本;
(2)对于文本 content, 输出含有 <qthighlight-- --qthighlight> 的行号;
(3)对于给定的的行号数组,生成一个起始行号数组 int[2][]。
(4) 给定一个 content ,输出其行号在 [start, end] 的内容。
第一步: 替换内容
- 给定文本 content 和一个正则表达式 regex, 将 content 中命中正则 regex 的文本用 <qthighlight-- --qthighlight> 围起来,生成新的文本。
比如有正则表达式 “GET” ,文本 如下【题目如上】
使用 go 实现,并输出高亮所在的行。方法签名为
func highlightMatched(content, regex string) string
AI 给出答案:
// HighlightMatched wraps all occurrences of the regex pattern in the input content with the specified highlight tags.
func HighlightMatched(content, regex string) string {
re := regexp.MustCompile(regex)
return re.ReplaceAllStringFunc(content, func(match string) string {
return fmt.Sprintf("<qthighlight--%s--qthighlight>", match)
})
}
// 自己简单组合下:
func HighlightMatchedWithMultiRules(content string, regexes []string) string {
if slice_utils.IsEmpty(regexes) {
return content
}
if len(regexes) == 1 {
return HighlightMatched(content, regexes[0])
}
result := content
for _, regex := range regexes {
result = HighlightMatched(result, regex)
}
return result
}
第二步:生成行号
- 对于文本 content, 输出含有 <qthighlight-- --qthighlight> 的行号.
func highlightLines(content string) []int
AI 给出答案:
// HighlightLines 接收一个字符串参数:content。
// 它会返回一个整数切片,其中包含了含有高亮标记的行号。
func HighlightLines(content string) []int {
// 定义高亮标记的正则表达式
highlightRegex := regexp.MustCompile(`<qthighlight--.*?--qthighlight>`)
// 使用strings.SplitN分割文本到每一行
lines := strings.Split(content, "n")
var highlightedLines []int
lineNumber := 1 // 跟踪行号
for _, line := range lines {
// 检查当前行是否包含高亮标记
if highlightRegex.MatchString(line) {
// 如果找到高亮标记,记录行号
highlightedLines = append(highlightedLines, lineNumber)
}
lineNumber++
}
return highlightedLines
}
第三步:生成起始行号数组
- 对于给定的的行号数组,生成一个数组 int[2][]。
对于一个行号 line[i] :
(1) 向前向后数 5 行,如果没有与后面的重合的行号,则起始行为 line[i]-5 (小于 0 则为 0 ) 末行为 line[i]+5;
(2) 如果 line[i]+5 大于第二个行号 line[i+1] 且 line[i+1] + 5 < line[i+2],则起始行为 line[i]-5 末行为 line[i+1] +5
(3) 如果 line[i]+5 大于第二个行号 line[i+1], 且 line[i+1]+5 > line[i+2],直到有 line[i+N] + 5 < line[i+N-1], 则 起始行为 起始行为 line[i]-5 ,末行为 line[i+N] + 5
依次类推。比如 [4, 5, 9, 30] ,生成的对象数组为 [0, 14], [25, 35]
写一个 go 函数,实现这个算法
func lineBetween(lineNos []int) [][2]int
AI 给出的答案有点问题,我修改 了下:
// LineBetween 接收一个按升序排列的行号数组lineNos,
// 返回一个二维数组,每个元素是一个长度为2的整数数组,表示每个行号的范围。
func LineBetween(lineNos []int) [][2]int {
var result [][2]int
n := len(lineNos)
for i := 0; i < n; {
start := max(0, lineNos[i]-HighLightLineNum)
end := lineNos[i] + HighLightLineNum
var j int
// 检查是否存在下一个元素,并且当前行号的结束行号是否超过下一个行号
for j = i + 1; j < n && end >= lineNos[j]-HighLightLineNum; {
end = lineNos[j] + HighLightLineNum
j++
}
result = append(result, [2]int{start, end})
i = j
}
return result
}
第四步:生成行号之间的内容
- 给定一个 content ,输出其行号在 [start, end] 的内容。
func output(content string, lineBetween [2]int) string
func HighlightOutput(lines []string, hitLines [][2]int) []*dto.MatchContentDTO {
result := make([]*dto.MatchContentDTO, 0)
for _, hitline := range hitLines {
matchContent := &dto.MatchContentDTO{
StartLine: int32(hitline[0]),
PartContent: output(lines, hitline),
}
result = append(result, matchContent)
}
return result
}
第五步:整合
func HighlightWithMultiRules(content string, regexes []string) []*dto.MatchContentDTO {
highlightedContent := HighlightMatchedWithMultiRules(content, regexes)
highLines := HighlightLines(highlightedContent)
fmt.Println(highLines)
linesBetween := LineBetween(highLines)
fmt.Println(linesBetween)
return HighlightOutput(strings.Split(highlightedContent, "rn"), linesBetween)
}
大功告成!
经验小结
- 遇到不太好解决的问题,尤其是算法类时,AI 可以给出很好的辅助(尤其是代码实现)。
- 出题时,要求要明确,最好能给出期望的方法签名。
- 如果不太好描述问题,就举例说明。
- 逐步尝试,一步步探索前进。虽然文中是直接给出了思路,实际中是想一步走一步。