[英]Efficient way to search for a set of strings in a string in Java
我有一组大小约100-200的元素。 让样本元素为X
每个元素都是一组字符串(这样一组中的字符串数在1到4之间)。 X
= { s1
, s2
, s3
}
对于给定的输入字符串(约100个字符),说P
,我想测试任何是否X
是存在于串英寸
对于所有s
属于X
, X
存在于P
iff中, s
是P
的子串。
这组元素可用于预处理。
我希望在Java中尽可能快。 可能的方法不符合我的要求:
s
的子串的P
似乎是一个代价高昂的操作 s
可以是P
任何子串(不一定是单词),所以我不能使用单词的散列 s1
, s2
, s3
可以以任何顺序出现,并且所有字符串都需要作为子字符串出现 现在我的方法是从每个X
构造一个巨大的正则表达式,其中包含字符串顺序的所有可能排列。 因为X
<= 4中的元素数量,这仍然是可行的。 如果有人能指出我更好(更快/更优雅)的方法,那将是很棒的。
请注意,元素集可用于预处理,我想要java中的解决方案。
您可以直接使用正则表达式:
Pattern regex = Pattern.compile(
"^ # Anchor search to start of string\n" +
"(?=.*s1) # Check if string contains s1\n" +
"(?=.*s2) # Check if string contains s2\n" +
"(?=.*s3) # Check if string contains s3",
Pattern.DOTALL | Pattern.COMMENTS);
Matcher regexMatcher = regex.matcher(subjectString);
foundMatch = regexMatcher.find();
如果字符串中存在所有三个子字符串,则foundMatch
为true。
请注意,如果它们可能包含正则表达式元字符,则可能需要转义“”字符串“。
听起来你在实际发现特定方法实际上太慢之前过早地优化了代码。
关于你的字符串集的一个很好的属性是字符串必须包含X
所有元素作为子字符串 - 这意味着如果我们找到一个未包含在P
的X
元素,我们就会快速失败。 这可能会比其他方法更好地节省时间,特别是如果X
的元素通常长于几个字符并且不包含或仅包含少量重复字符。 例如,当检查是否存在具有非重复字符(例如,惯性)的5长度字符串时,正则表达式引擎仅需要检查100个长度字符串中的20个字符。 而且由于X
确实有100-200个元素,所以如果可以的话,真的想要快速失败。
我的建议是按照长度顺序对字符串进行排序,并依次检查每个字符串,如果找不到一个字符串则提前停止。
看起来像Rabin-Karp算法的完美案例:
Rabin-Karp因单一模式搜索Knuth-Morris-Pratt算法,Boyer-Moore字符串搜索算法以及其他更快的单模式字符串搜索算法而劣势,因为它具有缓慢的最坏情况行为。 然而,Rabin-Karp是多模式搜索的首选算法。
当预处理时间无关紧要时,您可以创建一个哈希表,该表将每个单字母,双字母,三字母等组合映射到至少一个字符串中的字符串列表中。
索引字符串的算法看起来像那样(未经测试):
HashMap<String, Set<String>> indexes = new HashMap<String, Set<String>>();
for (int pos = 0; pos < string.length(); pos++) {
for (int sublen=0; sublen < string.length-pos; sublen++) {
String substring = string.substr(pos, sublen);
Set<String> stringsForThisKey = indexes.get(substring);
if (stringsForThisKey == null) {
stringsForThisKey = new HashSet<String>();
indexes.put(substring, stringsForThisKey);
}
stringsForThisKey.add(string);
}
}
索引每个字符串的方式将是字符串长度的二次方,但只需要为每个字符串完成一次。
但结果是对发生特定字符串的字符串列表进行恒速访问。
您可能正在寻找Aho-Corasick算法 ,该算法从字符串集(字典)构造自动机(类似于trie),并尝试使用此自动机将输入字符串与字典进行匹配。
一种方法是生成每个可能的子字符串并将其添加到集合中。 这非常低效。
相反,您可以创建从任何点到最后的所有字符串到NavigableSet并搜索最接近的匹配。 如果最接近的匹配以您要查找的字符串开头,则您具有子字符串匹配。
static class SubstringMatcher {
final NavigableSet<String> set = new TreeSet<String>();
SubstringMatcher(Set<String> strings) {
for (String string : strings) {
for (int i = 0; i < string.length(); i++)
set.add(string.substring(i));
}
// remove duplicates.
String last = "";
for (String string : set.toArray(new String[set.size()])) {
if (string.startsWith(last))
set.remove(last);
last = string;
}
}
public boolean findIn(String s) {
String s1 = set.ceiling(s);
return s1 != null && s1.startsWith(s);
}
}
public static void main(String... args) {
Set<String> strings = new HashSet<String>();
strings.add("hello");
strings.add("there");
strings.add("old");
strings.add("world");
SubstringMatcher sm = new SubstringMatcher(strings);
System.out.println(sm.set);
for (String s : "ell,he,ow,lol".split(","))
System.out.println(s + ": " + sm.findIn(s));
}
版画
[d, ello, ere, hello, here, ld, llo, lo, old, orld, re, rld, there, world]
ell: true
he: true
ow: false
lol: false
您可能还想考虑使用“后缀树”。 我没有用过这个代码,但是有一个描述在这里
我使用了专有的实现(我甚至不能访问)并且它们非常快。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.