【问题标题】:How to replace a list of strings in a text where some of them are substrings of other?如何替换文本中的字符串列表,其中一些是其他字符串的子字符串?
【发布时间】:2016-08-25 14:41:27
【问题描述】:

我有一个包含一些我想标记的单词的文本,并且要标记的单词包含在一个列表中。问题是其中一些单词是其他单词的子字符串,但我想标记列表中最长的识别字符串。

例如,如果我的文本是“foo and bar is different from foo bar”。我的列表包含“foo”、“bar”和“foo bar”,结果应该是“[tag]foo[/tag] 和 [tag]bar[/tag] 不同于 [tag]foo bar[/tag] 。”

String text = "foo and bar are different from foo bar.";

List<String> words = new ArrayList();
words.add("foo");
words.add("bar");
words.add("foo bar");

String tagged = someFunction(text, words);

someFunction 的代码应该是什么,使得字符串 taggedText 的值为 &lt;tag&gt;foo&lt;/tag&gt; and &lt;tag&gt;bar&lt;/tag&gt; are different from &lt;tag&gt;foo bar&lt;/tag&gt;.

【问题讨论】:

标签: java regex string substring


【解决方案1】:

用标记替换所有匹配的单词(在我的示例中,我使用 |i| 作为标记,其中 i 对应于标记单词的索引。)试试这个方法:

private static String someFunction(String text, List<String> words) {
        //Container for the tagged strings
        List<String> tagged = new ArrayList<>();

        //Create comparator class for sorting list according to string length
        Comparator<String> x = new Comparator<String>() {
            @Override
            public int compare(String s1, String s2)
            {
                if(s1.length() > s2.length())
                    return -1;

                if(s2.length() > s1.length())
                    return 1;

                return 0;
            }
        };

        //Sort list
        Collections.sort(words, x);

        //Replace all words in the text that matches a word in the word list
        //Note that we replace the matching word with a marker |0|, |1|, etc...
        for (int i = 0; i < words.size(); i++) {
            text = text.replaceAll(words.get(i), "\\|" + i + "\\|");
            //Save the matching word and put it between tags
            tagged.add("<tag>" + words.get(i) + "</tag>");
        }

        //Replace all markers with the tagged words
        for (int i = 0; i < tagged.size(); i++) {
            text = text.replaceAll("\\|" + i + "\\|", tagged.get(i));
        }


        return text;
    } 

警告:我在这里假设我的标记 '|i|'永远不会出现在文本中。将我的标记替换为您想要的不会出现在文本中的任何符号。这只是一个想法,不是完美的答案。

【讨论】:

    【解决方案2】:

    使用String的split方法。并将每个单词与 List 进行比较。

    String somefunction(String text, List<String> words){
      String res = "";
      String[] splits = text.split(" ");
      for(String st: splits){
        if(words.contains(st){
           res += "<tag>"+st+"<\tag>\n";
        }
      }
      return res;
    }
    

    【讨论】:

      【解决方案3】:

      您需要使用包含每个可能单词的正则表达式,以及一个或多个单词的贪婪匹配。然后您可以使用正则表达式中的匹配结果来获取每个匹配项,并且因为它是贪婪的,所以每个匹配项将是最大长度。正则表达式本身将取决于您的单词和您对空格的计数,以及 foobar 是否被视为“foo”和“bar”的匹配项。

      【讨论】:

        【解决方案4】:

        这闻起来像家庭作业,但我会给你一些建议。

        如果B是A的子串,如果B不等于A,那么B的长度一定小于A的长度。你自己也说了:

        [...] 但我想标记列表中最长的识别字符串。

        所以我们必须按长度对单词列表进行排序,最长的在前。我会留给你弄清楚如何做到这一点。 Collections.sort(List&lt;T&gt;, Comparator&lt;? super T&gt;) 是您将使用的。

        下一个问题是实际替换。如果您简单地遍历所有单词并使用String.replaceAll(String, String),您的示例最终将是这样的:

        <tag>foo</tag> and <tag>bar</tag> are different from <tag><tag>foo</tag> <tag>bar</tag></tag>.
        

        这是因为我们将首先包围 'foo bar',然后我们将再次包围 foo 和 bar。谢天谢地,String.replaceAll(String, String) 的第一个参数是正则表达式。

        诀窍是匹配这个词,但前提是它还没有被包围。但不仅仅是包围、引导或跟踪,因为它可能是已标记的&lt;tag&gt;foo bar&lt;/tag&gt; 中的foo。只有当word 还没有前导&gt;、尾随&lt; 并且不在另一个词的中间时,这样的"(?&lt;!(\\w|&gt;))+" + word + "(?!(\\w|&lt;))+" 才会匹配。 (我承认,我不擅长正则表达式,所以我相信这会更好)

        【讨论】:

          最近更新 更多