使用 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] 的内容。


第一步: 替换内容

  1. 给定文本 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
}

第二步:生成行号

  1. 对于文本 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
}

第三步:生成起始行号数组

  1. 对于给定的的行号数组,生成一个数组 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
}

第四步:生成行号之间的内容

  1. 给定一个 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 可以给出很好的辅助(尤其是代码实现)。
  • 出题时,要求要明确,最好能给出期望的方法签名。
  • 如果不太好描述问题,就举例说明。
  • 逐步尝试,一步步探索前进。虽然文中是直接给出了思路,实际中是想一步走一步。

posted @ 2024-07-13 20:49  琴水玉  阅读(56)  评论(0编辑  收藏  举报