在Java中搜索字符串中的一组字符串的有效方法

时间:2012-09-11 09:45:57

标签: java regex string algorithm data-structures

我有一组大小约为100-200的元素。假设样本元素为X

每个元素都是一组字符串(这样的一组中的字符串数在1到4之间)。 X = {s1s2s3}

对于给定的输入字符串(大约100个字符),比如P,我想测试字符串中是否有X 存在

{p> X 目前 P iff中所有s属于XsP的子字符串1}}。

元素集可用于预处理。


我希望在Java中尽可能快。可能的方法不符合我的要求:

  • 检查所有字符串s是否为P的子字符串似乎是一项代价高昂的操作
  • 因为s可以是P的任何子字符串(不一定是单词),所以我不能使用单词的哈希值
  • 我无法直接使用正则表达式s1s2s3可以按任何顺序出现,所有字符串都需要作为子字符串存在

现在我的方法是用每个X构建一个巨大的正则表达式,其中包含字符串顺序的所有可能排列。因为X <= 4中的元素数量,这仍然是可行的。如果有人可以指出我更好(更快/更优雅)的方法,那将是很好的。

请注意,这组元素可用于预处理,我希望在java中使用该解决方案。

7 个答案:

答案 0 :(得分:2)

可以直接使用正则表达式:

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为真。

请注意,如果它们可能包含正则表达式元字符,则可能需要转义“”字符串“。

答案 1 :(得分:1)

听起来你在实际发现特定方法实际上太慢之前过早地优化了代码。

关于你的字符串集的nice属性是字符串必须包含X的所有元素作为子字符串 - 这意味着如果我们找到一个未包含的X元素,我们就会快速失败在P内。这可能会比其他方法更好地节省时间,特别是如果X的元素通常长于几个字符并且不包含或仅包含少量重复字符。例如,当检查是否存在具有非重复字符(例如,惯性)的5长度字符串时,正则表达式引擎仅需要检查100个长度字符串中的20个字符。而且,由于X有100-200个元素,你真的想要尽快失败。

我的建议是按照长度顺序对字符串进行排序,并依次检查每个字符串,如果找不到一个字符串则提前停止。

答案 2 :(得分:1)

看起来像Rabin–Karp algorithm的完美案例:

  

Rabin-Karp因单一模式搜索Knuth-Morris-Pratt算法,Boyer-Moore字符串搜索算法以及其他更快速的单模式字符串搜索算法而劣势,因为它具有缓慢的最坏情况行为。然而,Rabin-Karp是多模式搜索的首选算法。

答案 3 :(得分:0)

当预处理时间无关紧要时,您可以创建一个哈希表,将每个字母,两个字母,三个字母等组合映射到至少一个字符串中的字符串列表中。发生。

索引字符串的算法看起来像那样(未经测试):

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);
    }
}

将每个字符串索引为字符串长度的二次方,但每个字符串只需要执行一次。

但结果是对发生特定字符串的字符串列表进行恒速访问。

答案 4 :(得分:0)

您可能正在寻找Aho-Corasick algorithm,它从字符串集(字典)构建自动机(类似于trie),并尝试使用此自动机将输入字符串与字典进行匹配。

答案 5 :(得分:0)

一种方法是生成每个可能的子字符串并将其添加到集合中。这非常低效。

相反,您可以从任意点到最后创建所有字符串到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

答案 6 :(得分:0)

您可能还想考虑使用“后缀树”。我没有使用过此代码,但有一个描述here

我使用了专有实现(我甚至无法访问)并且它们非常快。