【问题标题】:Fastest way to search several strings in a string在一个字符串中搜索多个字符串的最快方法
【发布时间】:2021-10-29 19:30:53
【问题描述】:

下面是我查找给定单个字符串中所有子字符串出现次数的代码

public static void main(String... args) {
    String fullString = "one is a good one. two is ok. three is three. four is four. five is not four";
    String[] severalStringArray = { "one", "two", "three", "four" };
    Map<String, Integer> countMap = countWords(fullString, severalStringArray);
}

public static Map<String, Integer> countWords(String fullString, String[] severalStringArray) {
    Map<String, Integer> countMap = new HashMap<>();

    for (String searchString : severalStringArray) {
        if (countMap.containsKey(searchString)) {
            int searchCount = countMatchesInString(fullString, searchString);
            countMap.put(searchString, countMap.get(searchString) + searchCount);
        } else
            countMap.put(searchString, countMatchesInString(fullString, searchString));
    }

    return countMap;
}

private static int countMatchesInString(String fullString, String subString) {
    int count = 0;
    int pos = fullString.indexOf(subString);
    while (pos > -1) {
        count++;
        pos = fullString.indexOf(subString, pos + 1);
    }
    return count;
}

假设完整的字符串可能是作为字符串读取的完整文件。以上是搜索的有效方式还是其他更好的方式或最快的方式?

谢谢

【问题讨论】:

  • 可以找Trie数据结构降低时间复杂度
  • 对于字符串匹配,您可以使用 Knuth–Morris–Pratt 算法。
  • 明确地说,你问的问题是关于counting多个字符串出现在另一个字符串中的次数。这意味着涉及正则表达式等的简单解决方案不起作用。
  • 另外...注意搜索字符串重叠的情况;例如{"one","onerous"}。这几乎排除了将正则表达式与替代项一起使用。
  • @Deepeshkumar 我们需要一个带有代码 sn-p 的示例。算法对于开发人员来说太复杂了,无法理解和实现。如果你分享一个例子,理解或理解会很棒。

标签: java


【解决方案1】:

您可以只形成要搜索的单词的正则表达式交替,然后针对该正则表达式进行一次搜索:

public static int matchesInString(String fullString, String regex) {
    int count = 0;

    Pattern r = Pattern.compile(regex);
    Matcher m = r.matcher(fullString);

    while (m.find())
        ++count;

    return count;
}

String fullString = "one is a good one. two is ok. three is three. four is four. five is not four";
String[] severalStringArray = { "one", "two", "three", "four" };
String regex = "\\b(?:" + String.join("|", severalStringArray) + ")\\b";

int count = matchesInString(fullString, regex);
System.out.println("There were " + count + " matches in the input");

打印出来:

输入中有 8 个匹配项

请注意,上面示例中使用的正则表达式模式是:

\b(?:one|two|three|four)\b

【讨论】:

  • 我尝试了 Regex 和 Pattern,发现在处理巨大的文件字符串时速度很慢。你在处理大文件时也获得了速度吗?
  • 问题是 indexOf 或 Regex 还是 contains - 应该探索或验证在 Java 中快速搜索巨大字符串中的哪个。
【解决方案2】:

正则表达式

您的问题可以使用 regex(正则表达式)来解决。正则表达式是一种帮助您匹配字符串中的模式的工具。此模式可以是一个单词,也可以是一组字符。

Java 中的正则表达式

在 Java 中,有两个对象可以帮助您处理正则表达式:PatternMatcher

您可以在下面看到一个示例,用于在 Java 中搜索字符串 stackoverflowXstackoverflowXXXstackoverflowXX 中是否存在单词 stackoverflow

String pattern = "stackoverflow";
String stringToExamine = "stackoverflowXstackoverflowXXXstackoverflowXX";

Pattern patternObj = Pattern.compile(pattern);
Matcher matcherObj = patternObj.matcher(stringToExamine);

计算一个单词在给定字符串中出现的次数

正如here 所写,您有不同的解决方案,具体取决于您的 Java 版本:

Java 9+

long matches = matcherObj.results().count();

较早的 Java 版本

int count = 0;
while (matcherObj.find())
    count++;

问题中的正则表达式

您使用一种方法来计算一个单词在文本(字符串)中出现的次数,您可以像这样修改它:

Java 9+

public static int matchesInString(String fullString, String pattern)
{
    Pattern patternObj = Pattern.compile(pattern);
    Matcher matcherObj = patternObj.matcher(fullString);
    
    return matcherObj.results().count();
}

较早的 Java 版本

public static int matchesInString(String fullString, String pattern)
{
    int count = 0;

    Pattern patternObj = Pattern.compile(pattern);
    Matcher matcherObj = patternObj.matcher(fullString);
    
    while (matcherObj.find())
        count++;
        
    return count;
}

【讨论】:

    【解决方案3】:

    其实最快的方法是先扫描字符串,统计所有存在的单词,保存到Map。然后只选择所需的单词。

    简单点! regular expression 对于这个简单的任务来说太复杂且效率不高。让我们用悍马解决它!

    public static void main(String... args) {
        String str = "one is a good one. two is ok. three is three. four is four. five is not four";
        Set<String> words = Set.of("one", "two", "three", "four");
        Map<String, Integer> map = countWords(str, words);
    }
    
    public static Map<String, Integer> countWords(String str, Set<String> words) {
        Map<String, Integer> map = new HashMap<>();
    
        for (int i = 0, j = 0; j <= str.length(); j++) {
            char ch = j == str.length() ? '\0' : str.charAt(j);
    
            if (j == str.length() || !isWordSymbol(ch)) {
                String word = str.substring(i, j);
    
                if (!word.isEmpty() && words.contains(word))
                    map.put(word, map.getOrDefault(word, 0) + 1);
    
                i = j + 1;
            }
        }
    
        return map;
    }
    
    private static boolean isWordSymbol(char ch) {
        return Character.isLetter(ch) || ch == '-' || ch == '_';
    }
    

    【讨论】:

    • 创建子字符串不会创建不可变对象吗?与 indexOf 相比,它的性能真的更好吗?上面解决了什么问题而不是去 indexOf
    • regexp 太复杂了。 indexOf(char) 我觉得不合适,因为你可以有很多字母和很多空白字符。
    【解决方案4】:

    有人评论过的 Trie 树的实现。不知道快不快。

    static class Trie {
    
        static final long INC_NODE_NO = 1L << Integer.SIZE;
    
        private long nextNodeNo = 0;
        private Node root = new Node();
        private final Map<Long, Node> nodes = new HashMap<>();
    
        public void put(String word) {
            Node node = root;
            for (int i = 0, len = word.length(); i < len; ++i)
                node = node.put(word.charAt(i));
            node.data = word;
        }
    
        public List<String> findPrefix(String text, int start) {
            List<String> result = new ArrayList<>();
            Node node = root;
            for (int i = start, length = text.length(); i < length; ++i) {
                if ((node = node.get(text.charAt(i))) == null)
                    break;
                String v = node.data;
                if (v != null)
                    result.add(v);
            }
            return result;
        }
    
        public Map<String, Integer> find(String text) {
            Map<String, Integer> result = new HashMap<>();
            for (int i = 0, length = text.length(); i < length; ++i)
                for (String w : findPrefix(text, i))
                    result.compute(w, (k, v) -> v == null ? 1 : v + 1);
            return result;
        }
    
        class Node {
            final long no;
            String data;
    
            Node() {
                this.no = nextNodeNo;
                nextNodeNo += INC_NODE_NO;
            }
    
            Node get(int key) {
                return nodes.get(no | key);
            }
    
            Node put(int key) {
                return nodes.computeIfAbsent(no | key, k -> new Node());
            }
        }
    }
    
    public static void main(String args[]) throws IOException {
        String fullString = "one is a good one. two is ok. three is three. four is four. five is not four";
        String[] severalStringArray = { "one", "two", "three", "four" };
        Trie trie = new Trie();
        for (String word : severalStringArray)
            trie.put(word);
        Map<String, Integer> count = trie.find(fullString);
        System.out.println(count);
    }
    

    输出:

    {four=3, one=2, three=2, two=1}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-02
      • 1970-01-01
      • 1970-01-01
      • 2012-11-07
      相关资源
      最近更新 更多