我在这篇文章简单的介绍了前端使用js对比两个字符串的方法,但是没有给出最终的代码,本篇文章我将把全部的代码展示出来
主要是参考了这篇文章的算法。
// 类型: 相等、删除、增加
enum DiffType {
Equal = 0,
Delete = -1,
Insert = 1,
}
// path数组中的类型
enum DiffPathFrom {
Diagonal = '↖︎',
Vertical = '↑',
Horizontal = '←',
}
export class Diff {
/**
* 标记增删改的结果数组
* @param {array} arr1 旧字符串分割的数组
* @param {array} arr2 新字符串分割的数组
* @param {array} path
* @return {array} diff数组
*/
static getDiffsFromPath(arr1: Array<string>, arr2: Array<string>, path: Array<Array<string>>): Array<Array<string | DiffType>> {
const diffs: Array<Array<string | DiffType>> = []
function pathDiffs(i: number, j: number) {
if (i === 0 || j === 0) {
// 删除的字符
if (i !== 0) {
// 将剩余的依次加入diffs数组
const deleteArr = arr1.slice(0, i)
deleteArr.forEach(item => {
diffs.push([DiffType.Delete, item])
})
}
// 新增的字符
if (j !== 0) {
// 将剩余的依次加入diffs数组
const insetsArr = arr2.slice(0, j)
insetsArr.forEach(item => {
diffs.push([DiffType.Insert, item])
})
}
return
}
if (path[i][j] === DiffPathFrom.Diagonal) {
pathDiffs(i - 1, j - 1)
diffs.push([DiffType.Equal, arr1[i - 1]])
} else if (path[i][j] === DiffPathFrom.Vertical) {
pathDiffs(i - 1, j)
diffs.push([DiffType.Delete, arr1[i - 1]])
} else if (path[i][j] === DiffPathFrom.Horizontal) {
pathDiffs(i, j - 1)
diffs.push([DiffType.Insert, arr2[j - 1]])
}
}
pathDiffs(arr1.length, arr2.length)
return diffs
}
/**
* 标记增删改的结果数组
* @param {array} arr1 旧字符串分割的数组
* @param {array} arr2 新字符串分割的数组
* @return {array} diff数组
*/
static diffArr(arr1: Array<string>, arr2: Array<string>): Array<Array<string | DiffType>> {
const len1 = arr1.length
const len2 = arr2.length
// 空间优化 2 * len2 + 1
const dp = new Array(2).fill(0).map(() => new Array(len2 + 1).fill(0))
// 动态规划path数组 (len1 + 1) * (len2 +1)
const path = new Array(len1 + 1).fill(0).map(() => new Array(len2 + 1).fill(0))
let k = 1
let k2 = 0
// 外层循环,每循环一次,交换k, k2
for (let i = 1; i <= len1; i += 1, k2 = k, k = Number(!k)) {
for (let j = 1; j <= len2; j += 1) {
if (arr1[i - 1] === arr2[j - 1]) {
dp[k][j] = dp[k2][j - 1] + 1
path[i][j] = DiffPathFrom.Diagonal
} else if (dp[k2][j] > dp[k][j - 1]) {
dp[k][j] = dp[k2][j]
path[i][j] = DiffPathFrom.Vertical
} else {
dp[k][j] = dp[k][j - 1]
path[i][j] = DiffPathFrom.Horizontal
}
}
}
return this.getDiffsFromPath(arr1, arr2, path)
}
/**
* @param {array} diffs 标记增删改的结果数组
* @param {string} splitMark 原始字符串的分割字符
* @return {string} 拼接后的的html
*/
static prettyHtml(diffs: Array<Array<string | DiffType>>, splitMark: string): string {
const html: Array<string> = []
for (let x = 0; x < diffs.length; x += 1) {
const op = diffs[x][0]
// xss方法不可以处理空格
const text = `${diffs[x][1] || ''}`.replace(/ /g, ' ')
switch (op) {
case DiffType.Insert:
html[x] = `<diff class="diff-insert">${text}</diff>`
break
case DiffType.Equal:
html[x] = text
break
default:
break
}
}
// 过滤掉数组中 empty的项目,避免拼接后有多余空格
const fillterEmptyHtml = html.filter(item => item)
// 去除句子首尾空格
return fillterEmptyHtml.join(splitMark).trim()
}
static diffSentence(text1: string, text2: string, splitMark: string): string {
// 处理结果句子末尾的空格
const originLength = text2.length
const trimRightLength = text2.trimRight().length
const suffixLen = originLength - trimRightLength
let spaces = ''
for (let i = 0; i < suffixLen; i += 1) {
spaces += ' '
}
// 去除首尾的空格再diff
const arr1 = `${text1.trim() || ''}`.split(splitMark)
const arr2 = `${text2.trim() || ''}`.split(splitMark)
const diffs = this.diffArr(arr1, arr2)
// diff结果加上原来的空格用于展示
return `${this.prettyHtml(diffs, splitMark)}${spaces}`
}
// 按照单词diff, 用空格分割
static diffByWords(text1: string, text2: string): string {
return this.diffSentence(text1, text2, ' ')
}
// 按照字符diff
static diffByChars(text1: string, text2: string): string {
return this.diffSentence(text1, text2, '')
}
}
function diffText(text: string, newText: string): string {
const res = Diff.diffByWords(text, newText)
return res
}
console.log(diffText('hello word', 'hello word!'))
在这个例子中,对于英文句子使用空格拆分,如果遇到所有格(alice's)的形式,或者有标点符号的情况,对比的结果就没有那么好了,所以这只是一个简单的版本,有很大的优化的空间,但是基本上可以满足一些简单的需求。同样的,如果中英文夹杂的句子对比效果也不是很好。