在字符串中查找多次出现的单词并存储各自的起始索引

Find multiple occurrences of words in a string and store the respective staring indices

背景

我有一串文本和一个哈希集,其中包含我要查找的词。

给出

String doc = "one of the car and bike and one of those";
String [] testDoc = doc.split("\s+");
HashSet<String> setW = new HashSet<>();
setW.add("and");
setW.add("of");
setW.add("one");

OBJECTIVE

objective 是扫描字符串,每次我们遇到哈希集中的单词时,我们将存储该单词和起始索引的位置。

在上述情况下,我们应该可以存储以下内容

one-->0 

of-->4 

and-->15 

and-->24, 

one-->28, 

of-->32

` 尝试

//create hashmap
for(int i = 0; i<testDoc.length; i++){
    if(setW.contains(testDoc[i])) {
        doc.indexOf(testDoc[i]);
       //add string and its index to hashmap
    }

这就是我到目前为止所想到的,唯一的问题是 indexOf 方法只查看单词的第一次出现,所以我不确定该怎么做。如果我在扫描每个单词后继续修剪字符串,那么我将无法获取原始字符串中单词的索引位置。

我希望在这里提供一些意见。

有一个 indexOf() 的重载版本,它采用索引作为搜索的开始。您可以使用它来重复搜索相同的字符串,直到搜索到结尾。

请注意,您可以删除 contains() 的测试,这样您就不会搜索字符串两次。

将单词列表转换为正则表达式,让正则表达式为您搜索。

例如你的 3 个词将是这样的正则表达式:

and|of|one

当然,你不会想要部分单词,所以你会添加单词边界检查:

\b(and|of|one)\b

不需要(再次)捕获单词,因为整个匹配 这个单词,所以使用 non-capturing 组。您也可以轻松地进行单词搜索 case-insensitive.

虽然纯单词(所有字母)永远不会有问题,但是通过使用 Pattern.quote().

引用单词来保护正则表达式是个好主意

例子

String doc = "one of the car and bike and one of those";
String[] words = { "and", "of", "one" };

// Build regex
StringJoiner joiner = new StringJoiner("|", "\b(?:", ")\b");
for (String word : words)
    joiner.add(Pattern.quote(word));
String regex = joiner.toString();

// Find words
for (Matcher m = Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(doc); m.find(); )
    System.out.println(m.group() + "-->" + m.start());

输出

one-->0
of-->4
and-->15
and-->24
one-->28
of-->32

如果你想稍微压缩(混淆)代码,你可以把它写成一条语句 Java 9+:

Pattern.compile(Stream.of(words).collect(joining("|", "(?i)\b(?:", ")\b"))).matcher(doc).results().forEach(r -> System.out.println(r.group() + "-->" + r.start()));

输出相同。

好吧,如果你想减少迭代,还有另一种解决方案,这段代码遍历一次字符串。我想到了一个字符一个字符地访问一个字符串。我用一个 StringBuilder 来附加每个字符并检查何时获得空格,只需将该字符串附加到最终答案列表,并添加索引。 我已经在下面描述了我的方法,我认为它只是访问每个字符一次,这段代码的时间复杂度是 O(n)。

StringBuilder sb=new StringBuilder();
    ArrayList<String> answer=new ArrayList<>();
    ArrayList<Integer> index=new ArrayList<>();
    HashSet<String> setW = new HashSet<>();
    setW.add("and");
    setW.add("of");
    setW.add("one");
    index.add(0);
    String doc = "one of the car and bike and one of those";
    for(int i=0;i<doc.length();i++){
        if(i==doc.length() || doc.charAt(i)==' '){
            index.add(i+1);
            answer.add(sb.toString());
            sb=new StringBuilder();
            i++;
        }
        sb.append(doc.charAt(i));
        if(i==doc.length()-1){
            if(setW.contains(sb.toString())){
                answer.add(sb.toString());
            };
        }
    }
    for(int i=0;i<answer.size();i++){
        if(setW.contains(answer.get(i))){
            System.out.println(answer.get(i)+"-->"+index.get(i));
        }
    }

基于这个想法我得到了预期的输出,提交我对这个问题的回答的原因是为了获得另一种可能的解决方案。 (在回答 HashSet 中,我们最终会得到每个单词的索引,而不仅仅是 setW 中存在的单词,所以如果你不想要它,你可以使用一个 if(!setW.contains(answer.get (i)) 条件。)

输出

one-->0
of-->4
and-->15
and-->24
one-->28
of-->32