使用 AI 解決一道演算法題

琴水玉發表於2024-07-13

善用 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 可以給出很好的輔助(尤其是程式碼實現)。
  • 出題時,要求要明確,最好能給出期望的方法簽名。
  • 如果不太好描述問題,就舉例說明。
  • 逐步嘗試,一步步探索前進。雖然文中是直接給出了思路,實際中是想一步走一步。

相關文章