基于特定格式字符串的差异标记算法
实验室项目中遇到这样一个需求,将OCR识别后的两段文字进行互相校验并标记出差异。两段文字分别来自不同源的OCR识别,自然会存在差异,而且本身也有误差。实际上这个标记是十分麻烦的,不同于普通的字符串算法,它是一种更加模糊,而且多变但又有着特殊形式的字符串。之前考虑过匹配算法如KMP、自动机之类都不适合;考虑相似度检测的算法如编辑距离和余弦相似度也不适合,实际上不需要这么复杂,只需要根据特殊的规律去标记即可。
在识别出的两段字符串当中,由于是对同一张图片进行的文字识别,因此绝大部分的字符串都是相同,存在的不同主要是识别误差,可能是漏了、多了或者错了。源图片是电子病历,所以会存在许多的不同字符,这是很麻烦的事情。
字符串预处理
由于比较的字符串很容易多了许多奇奇怪怪符号的干扰,因为符号本身就容易不清晰,出现误差几率更大,并且符号相对内容而言并不重要,所以将两段字符串的符号用正则表达式清除。
//比较时去除不重要的标点符号干扰
String tempA = a.replaceAll("\\s*\\p{Punct}\\s*","");
String tempB = b.replaceAll("\\s*\\p{Punct}\\s*","");
if(tempA.equalsIgnoreCase(tempB)){
Log.e("比较","两者相等");
}else{
Log.e("比较","两者不等");
//标记算法
List<String> list = insertMark(tempA,tempB,a,b);
//标记结果更新
bRs.set(i,list.get(0));
tRs.set(i,list.get(1));
}
算法思路:
遍历预处理后的两段字符串并比较(不区分大小写),相等就两者下标向前进,每遍历一个字符各自记录出现的次数,利用map集合记录,以字符本身为key,出现的次数为value
如果字符不相等,需要记录当前字符在原始字符串中的下标,为了求得这个坐标,我们前面才要记录各自出现的次数,然后通过key取得最新记录的次数,即表示当前字符在原始字符中出现的次数k,因为它必定不是被预处理掉的字符,接着利用indexOf求出该字符在原始字符串中首次出现的位置,根据记录的次数跳过k-1次,最后一次的索引便是所求的索引
接下来需要定位到有差异的字符到哪里为止,由于绝大部分字符都相同,因此一般情况下,跳过若干位后的字符串必定与第二段字符串相邻索引的字符相同,例子如下:
eg
AB CDE FGH
AB MLKE FGH
此处,当我们定位到第一个不同的是C 与 M,计算得到他们对应在原始字符串的下标后存起来,然后就是如何定位到两者相同字符 E的各自下标了,标记他们C~E和M~E的字符串就完成目标了,如何定位到末尾坐标呢?
- 我们可以给定一个范围,比如我设为5,也就是开始出现不相等的字符和之后的4个字符,逐渐遍历他们,寻找其后的4个字符内第一个和另一段字符串第一个不相等字符之后首次匹配的字符,然后计算出它的下标,看看它是不是也在5个字符内,如果是,成功定位,标记,继续比较;如果没有定位到,说明这两段的各自5个字符都是不匹配的,那么这两段的各自5个字符都标记,继续比较。注意数组下标溢出问题,也要注意在计算次数的时候,记得跳过的次数要加上插入的标记字符串中也包含的次数。
简陋实现如下:
/**
* 字符串差异标记算法
* @param tempA 经过预处理清除干扰后的字符串——前面一段
* @param tempB 经过预处理清除干扰后的字符串——另外一段
* @param a 原始字符串前面一段
* @param b 原始字符串后面一段
* @return 原始字符串处理后的标记结果
*/
private List<String> insertMark(String tempA,String tempB,String a,String b){
//a,b标记次数
int mFlag1=0;
int mFlag2=0;
//存储结果的集合
List<String> list = new ArrayList<>();
//