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