javascript字符串对比diff方法完整源代码

我在这篇文章简单的介绍了前端使用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, '&nbsp;')

			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 += '&nbsp;'
		}

		// 去除首尾的空格再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)的形式,或者有标点符号的情况,对比的结果就没有那么好了,所以这只是一个简单的版本,有很大的优化的空间,但是基本上可以满足一些简单的需求。同样的,如果中英文夹杂的句子对比效果也不是很好。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值