问题描述
|
如果我有某个字符串输入,那么我想与另一个字符串进行比较,并使用最大可能的匹配值将输入字符串的匹配内容包装在另一个字符串中。如何最好地将火柴包裹在标签中?这是一个不平凡的问题。
基本上,我想将输入的字符串与另一个字符串匹配,使用跨度标记显示在输入的字符串中找到的目标的匹配部分。
从输入字符串开头开始匹配(最大匹配)
搜索词的部分匹配项应突出显示(例如,请参见\“ barge \”,\“ barges \”)
特殊字符符应与输入的\“ fred / dred \”相匹配,这将是两个字。
输入字符串将根据用户输入内容而有所不同。
从头开始匹配输入字符串
匹配出现的每个单词
如果用户输入包含多个单词的字符串,则我想从第二个字符串中出现的位置开始,以渐进方式包装它们的匹配项。它们在输入的字符串的开头/结尾可能有空格,也可能没有空格。我希望将最大的部分包裹起来。
输入字符串示例:
\"brown cats cannot be white cats\"
\"blue pigs \"
\"large,charged/marged barge pigs\"
我希望这样包装它们:
\"<span class=\'wrapper\'>brown cats cannot be white cats</span>\"
在发生匹配的目标字符串中,即使是部分匹配也可能包含最大匹配。
要包装的字符串示例:
\"Hi bill,brown cats cannot be white cats and cows are not blue pigs,blue melons are large but not batteries charged barges with white cats carry coal\"
每个示例输入的最终字符串:
\"Hi bill,<span class=\'wrapper\'>brown cats cannot be white cats</span> and cows are not blue pigs,blue melons are large but not batteries charged barges with <span class=\'wrapper\'>white cats</span> carry coal\"
\"Hi bill,brown cats cannot be white cats and cows are not <span class=\'wrapper\'>blue pigs</span>,blue melons are large but not batteries charged barges with white cats carry coal\"
\"Hi bill,brown cats cannot be white cats and cows are not blue <span class=\'wrapper\'>pigs</span>,blue melons are large but not batteries <span class=\'wrapper\'>charged</span> <span class=\'wrapper\'>barge</span>s with white cats carry coal\"
可能的匹配项:\“棕猫不能为白猫\”
\"brown cats cannot be white cats\"
\"brown cats cannot be white\"
\"brown cats cannot be\"
\"brown cats cannot\"
\"brown cats\"
\"brown\"
\"brown\" \"cats\" \"cannot\" \"be\" \"white\" \"cats\"
如果我只包装每个匹配的单词,我可以做:
function replaceWords(wordsy,text) {
var re = \'(\' + wordsy + \')(?![^<]*(?:<\\/script|>))\',regExp = new RegExp(re,\'ig\'),sTag = \"<span class=\'wrapper\'>\",eTag = \"</span>\";
return text.replace(regExp,sTag + \'$&\' + eTag);
};
var matchstring = \"brown cats cannot be white cats\";
var wrapstring = \"Hi bill,blue melons are large but not batteries charged barges with white cats carry coal\";
var words = myValue.split(\" \");
var i = words.length; while (i--) {
wrapstring = replaceWords(words[i],wrapstring );
};
这不满足“最大匹配”的要求。我想要包装字符串中出现的匹配字符串中任何部分的最大匹配项。
使用纯JavaScript或jquery或组合的解决方案是可以接受的。
编辑:有人建议使用KMP,以下是KMP的示例jsfiddle.net/y5yJY/2,但事实并非如此,它的当前形式适合所有条件并进行单个匹配。
解决方法
我有一个有趣的解决方案,应该可以作为您的原始规格。它尚未经过压力测试,我不确定是否要处理大量文本,并且它会进行很多正则表达式匹配。不一定是最干净或最简单的解决方案,但它可以正常工作。
特点和局限性:
它处理匹配字符串中最奇怪的情况,例如重复的单词,非常相似或重复的短语等。
目前,您无法可靠地在源字符串中包含
[
和]
字符,因为它们在内部使用。如果有问题,则必须在匹配之前将它们交换为任何其他字符或字符组合。
对于N
个单词的匹配字符串,2*N + 5
字符串替换使用复杂度不同的正则表达式完成。
它匹配不区分大小写的单词和短语,忽略任何非单词字符。同时,它在结果中保留大小写混合的单词和非单词字符。
怎么运行的:
首先,它将分别查找每个单词,并将它们在匹配字符串中的索引附加到方括号中:word[2]
。如果单词多次出现,它将附加所有索引:word[3][2][1]
。
接下来,它通过查看周围单词的索引来查找并标记不在环绕边界上的单词。在一个单独的步骤中,它从这些单词中删除索引。最后,one[1] two[2] three[3]
将变为one[1] []two three[3]
。
现在剩下的就是以一定顺序进行一些假设,并包装单词/短语。看一下代码,看看所有替换完成了。
重要的是,在第一步之后,我们从不直接匹配单词,从那时起,单词被称为any number of word characters before [index]
或any number of word characters after []
。这样可以确保我们正确包装重复的单词/短语。
看看这个演示。我添加了一个悬停效果,因此您可以看到哪些单词被分组并包装在一起。
这是疯狂的代码,请尽情享受!
var matchstring = \'Brown cats cannot be white cats\';
var wrapstring = \'Hi bill,brown cats cannot be white cats and cows are not blue pigs,blue melons are large but not batteries charged barges with white cats carry coal,and the word \"cannot\" should match \';
// Pre-process matchstring to make it a flat list of words
// separated by single spaces.
matchstring = matchstring.replace(/\\W+/g,\' \');
var wrapStart = \'<span class=\"wrapped\">\';
var wrapEnd = \'</span>\';
var matcharray = matchstring.split(\' \');
var i,reg;
// Mark all matched words with indices
// one -> one[1]
for (i = 0; i < matcharray.length; i++) {
reg = new RegExp(\'\\\\b\' + matcharray[i] + \'\\\\b\',\'ig\');
wrapstring = wrapstring.replace(reg,\'$&[\' + i + \']\');
}
// Mark all inner words
// one[1] two[2] three[3] -> one[1] []two[2] three[3]
for (i = 1; i < matcharray.length; i++) {
reg = new RegExp(\'\\\\b(\\\\w+)([\\\\]\\\\d\\\\[]*\\\\[\' + (i - 1) + \'\\\\][\\\\]\\\\d\\\\[]*)(\\\\W+)(\\\\w+)([\\\\]\\\\d\\\\[]*\\\\[\' + i + \'\\\\][\\\\]\\\\d\\\\[]*)(?=\\\\W+\\\\w+[\\\\[\\\\d\\\\]]*\\\\[\' + (i + 1) + \'\\\\])\',\'$1$2$3[]$4$5\');
}
// Remove indices from inner words
// one[1] []two[2] three[3] -> one[1] []two three[3]
wrapstring = wrapstring.replace(/\\[\\](\\w+)[\\[\\d\\]]*/g,\'[]$1\');
// Start tags
// one[1] []two three[3] -> {one []two three[3]
wrapstring = wrapstring.replace(/(\\w+)\\[[\\[\\d\\]]+\\](\\W+)\\[\\]/g,wrapStart + \'$1$2[]\');
// End tags
// {one []two three[3] -> {one []two three}
wrapstring = wrapstring.replace(/\\[\\](\\w+\\W+\\w+)\\[[\\[\\d\\]]+\\]/g,\'$1\' + wrapEnd);
// Wrap double words
// one[1] two[2] -> {one two}
wrapstring = wrapstring.replace(/(\\w+)\\[[\\[\\d\\]]+\\](\\W+\\w+)\\[[\\[\\d\\]]*\\]/g,wrapStart + \'$1$2\' + wrapEnd);
// Orphan words
// unmatched matched[1] unmatched -> unmatched {matched} unmatched
wrapstring = wrapstring.replace(/(\\w+)\\[[\\[\\d\\]]+\\]/g,wrapStart + \'$1\' + wrapEnd);
// Remove left-over tags
// []word -> word
wrapstring = wrapstring.replace(/\\[\\]/g,\'\');
alert(wrapstring);
匹配部分词
如前所述,在第一步之后,仅通过其附加索引来处理单词。这意味着,如果我们要进行一些巧妙的匹配而不是整个单词,则只需在第一个“ 17”循环中修改正则表达式即可。这是本节中我们将使用的代码片段:
reg = new RegExp(\'\\\\b\' + matcharray[i] + \'\\\\b\',\'ig\');
正则表达式中的``19''表示匹配的单词边界,即单词字符序列的开头或结尾。这就是为什么上面的\\bword\\b
正则表达式仅给出完整的单词的原因,因为word
需要用单词边界包围。但这不是必须的。
如果要匹配以关键字开头的文本中的所有单词,可以将上面的行更改为以下内容:
reg = new RegExp(\'\\\\b\' + matcharray[i] + \'\\\\w*\\\\b\',\'ig\');
结果为正则表达式“ 23”。它匹配所有word
字符序列,后跟0个或多个其他单词字符(\\w*
),并用单词边界包围。请注意,需要在javascript字符串中转义反斜杠(\\\\
表示单个\\
)。
根据需求,我们可以轻松创建其他正则表达式组合:
\\bword\\w*\\b
匹配以关键字开头的单词。
\\b\\w*word\\b
匹配以关键字结尾的单词。
\\b\\w*word\\w*\\b
匹配包含关键字的单词。
\\b(\\w*word|word\\w*)\\b
匹配以关键字结尾或开头的单词。
您甚至可以说您只想匹配单词的较小修饰。例如,“ 32”仅在单词最多具有两个字母前缀和/或后缀的情况下才匹配。因此,danger
将匹配endanger
,cat
将匹配cats
,但是ѭ37but将不匹配cannot
,因为这将额外增加3个字母。
要匹配复杂的复数形式和不规则动词并不容易,您可以在服务器上构建大量不规则单词的字典并对单词进行预处理,因此,如果用户输入foot
,则使用正则表达式\\b(foot|feet)\\b
可以匹配两种形式。一个更简单的解决方案是只关心常规单词。对于大多数单词,匹配\\bword(s|es|)\\b
就足以捕获复数,它也匹配word
,words
和wordes
。对于像fly
这样的单词,正则表达式\\bfl(y|ies)\\b
可以胜任。对于index
这样的单词,正则表达式\\bind(ex|exes|ices)\\b
会匹配最常见的形式。
由于我不是真正的语言专家,因此我暂时将其保留。
输入中的通配符
与上述类似,在输入字符串中添加对通配符的支持非常容易。假设我们要让用户输入?
表示任何字符。如果输入是?red
,我们只需要在正则表达式中用\\w
替换?
。例如,\\b\\wred\\b
将与match54ѭ和dred
匹配。
就像上面一样,您也可以使用多个通配符,对于一个或多个字符,将它们替换为\\w+
,对于零个或多个字符将其替换为\\w*
。 \\bf\\w+d\\b
将匹配fed
和feed
,\\w*
也将匹配.62ѭ。
,怎么回事:(仅描述算法,而不是用代码编写)
假设您在两张纸上写下了两个字符串。放置两张纸,使其一张在另一张上。将顶页移到左侧,以便其最后一个字母位于底页的第一个字母之上。现在,两个重叠的字母是否匹配?如果是这样,则您有一个长度为1的匹配项。将其记录为最长的匹配项。然后,将顶页向右移动一个字符。现在,两个字母重叠。他们匹配吗?如果是这样,则您的最大匹配大小为2。将顶部表格向右移动1个字符,每次查找匹配的重叠字符的最大部分。始终跟踪最大的比赛。继续进行操作,直到最上面的工作表向右移,直到其第一个字符与另一个工作表的最后一个字符重叠为止。
我不知道用javascript来实现会多么容易,但是作为一种算法,我认为这很合理。
PS-,对于需要找到“匹配的重叠字符的最大部分”的位,您可以执行以下操作:
/* Note: str1 and str2 are the two overlapping portions of the strings */
var largestMatch = 0;
var currMatch = 0;
for (var i = 0; i < str1.length; i++) {
if (str1[i] == str2[i]) currMatch++;
else currMatch = 0;
largestMatch = Math.max(largestMatch,currMatch);
}
// largestMatch is the size of the largest section of matched characters
, 这是我为解决此问题所做的事情:(由于不完善而在寻求改进)
(将其包装在一个jQuery文档中)
就像这里:http://jsfiddle.net/KvM47/
function findStringLimit(searchChar,searchCharIndex,searchedString) {
return searchedString.substring(0,searchedString.lastIndexOf(searchChar,searchCharIndex));
};
function replaceWords(wordsy,text) {
var re = \'(\' + wordsy + \')(?![^<]*(?:<\\/script|>))\',regExp = new RegExp(re,\'ig\'),sTag = \"<span class=\'wrappedWord\'>\",eTag = \"</span>\";
return text.replace(regExp,sTag + \'$&\' + eTag);
};
var longstring = $(\'#mystring\');
var htmlString =longstring .html(); // instance html
myValue = \"Brown cats cannot be white cats\";
myValue = myValue.replace(/^\\s+|\\s+$/g,\"\");//trim whitespace at each end
var words = myValue.split(\" \");
var allPhrases = [];
allPhrases.push(myValue);
var i = words.length;
while (i--) {
allPhrases.push(findStringLimit(\" \",allPhrases[(words.length - i) - 1].length,allPhrases[(words.length - i) - 1]));
};
var i = allPhrases.length;
while (i--) {
if (allPhrases[i] != \"\") words = words.concat(allPhrases[i]);
};
var i = words.length;
while (i--) {
htmlString = replaceWords(words[i],htmlString);
};
longstring.html(htmlString);
有待改进的地方:
使用其他字符分隔单词,而不仅仅是空格。
提高效率
更好地检测\“ search \”和\“ matched \”字符串中的字符串“块”(两个或更多单词在一起),并对它们进行处理。