【问题标题】:How to separate a String using a List of words?如何使用单词列表分隔字符串?
【发布时间】:2022-01-08 19:10:18
【问题描述】:

如何使用预先给定的字符串列表来分隔字符串,并用空格分隔它们?

例如:

单词列表:words = {"hello", "how", "are", "you"}

我要分离的字符串:text = "hellohowareyou"

public static String separateText(String text, List<String> words) {
    String new_text;

    for (String word : words) {
        if (text.startsWith(word)) {
            String suffix = text.substring(word.length());  //'suffix' is the 'text' without it's first word
            new_text += " " + word;  //add the first word of the 'string'
            separateString(suffix, words);
        }
    }
    
    return new_text;
}

new_text 应该返回hello how are you

请注意,words 列表的顺序可能不同,并且包含更多单词,例如字典。

如果需要,我如何进行这种递归?

【问题讨论】:

  • 按照Oracle's naming conventions for Java,你应该用camelCase命名局部变量。
  • 如果文本是“ihoweverywhere”并且字典包含诸如[“ever”、“every”、“how”、“however”、“where”之类的词,您是否有如何消除歧义的规则, “everywhere”] 给定的顺序没有定义?
  • 我的解决方案将按照单词在单词列表中的顺序消除歧义。位于列表开头的单词优先。如果您希望较长的单词优先,您可以按自然降序(或简单地按长度降序)对单词进行排序。
  • 如果这些词是 ["now", "here", "no", "where"]。您将如何拆分“无处”?它是首先找到的最短单词,所以分成“no”和“where”?如果首先选择最短的单词会导致死胡同,是否必须支持backtracking - 例如,如果输入是“nownow”,选择“no”会留下无法拆分的“wnow”?还是首先选择最短的单词(如果这是行为)保证不需要回溯?
  • @Ma3x 排序如果没有 OP 为我们提供独特的规则集(即rangeofanorange 不能正确间隔我们提供[an, of, or, range, orange] 的任何顺序),但除此之外您的解决方案还有 O( n * m) 时间复杂度(m - 文本长度,n - 字典大小)我同意这至少是一个很好的起点

标签: java string list recursion


【解决方案1】:

这应该做你想做的事

  • 如果您发现自己反复附加到字符串,则应该使用 StringBuilder
  • 使用while循环遍历text,一次删除一个单词,当text为空时结束
public static String separateText(String text, List<String> words){
        StringBuilder newTextBuilder = new StringBuilder();

        outerLoop:
        while(text.length() > 0){
            for(String word : words){
                if(text.startsWith(word)){
                    newTextBuilder.append(word + " ");
                    text = text.substring(word.length());
                    continue outerLoop;
                }
            }
        }

        return newTextBuilder.toString();
    }
}

【讨论】:

    【解决方案2】:

    如何使用预先给定的字符串列表来分隔字符串,并用空格分隔它们?

    你已经开始的差不多了。检查剩余文本是否以列表中的任何单词开头,删除起始单词并保留后缀。

    您已经完成了所有这些,但您决定尝试递归调用separateText,而不是仅仅保留后缀并继续迭代。

    这也是一种可能,但即使只是正常地在 while 循环中迭代直到后缀(或剩余文本)为空就足够了。

    使用像while (index &lt; text.length()) 这样的循环也适用于更长的输入,即使单词的顺序不同。

    public String separateText(String text, List<String> words){
        if (text == null) return "";
        if (words == null || words.isEmpty()) return text;
    
        StringBuilder sb = new StringBuilder();
    
        boolean unknownWord = false;
        int index = 0;
        while (index < text.length()) {
            boolean wordFound = false;
            for (String word : words) {
                if (!word.isEmpty() && text.startsWith(word, index)) {
                    wordFound = true;
                    // move the index ahead just past the last letter of the word found
                    index += word.length();
                    if (unknownWord) {
                        unknownWord = false;
                        sb.append(" ");
                    }
                    sb.append(word);
                    sb.append(" ");
                    break;
                }
            }
            if (!wordFound) {
                unknownWord = true;
                sb.append(text.charAt(index));
                index++;
            }
        }
    
        return sb.toString();
    }
    

    【讨论】:

    • 这不起作用,因为它假定:a) words 字典“知道”文本中可能出现的所有单词。 b) 字典中单词的顺序与它们在文本中出现的顺序相同。所以separateText("iamremoteserver", asList("server", "am", "i"))separateText("iamremoteserver", asList("extra")) 都会导致无限循环。这也可以通过使用StringBuilder 而不是+ 来改进,并且局部变量名称需要采用驼峰式
    • 这对我不起作用,它保持无限循环
    • 我很抱歉(对两位评论者),但您说“请注意,列表 单词 的顺序可能不同,并且还有更多单词,例如字典。”你没有说输入可以有其他词。如果是这种情况,请编辑问题以指定它。
    • @dshelya 关于 a) 我遵循了 OP 描述,如果规格不同,OP 将编辑他的问题。 b)这根本不是假设,它适用于任何订单。至于 StringBuilder 我完全同意。
    • @Ma3x for the b) - 是的,我的错,它会起作用(尽管它会在第一个字符中放置一个空格)
    【解决方案3】:

    这个解决方案非常简单,但不是内存优化,因为创建了许多新的String

    public static String separate(String str, Set<String> words) {
        for (String word : words)
            str = str.replace(word, word + ' ');
    
        return str.trim();
    }
    

    演示

    Set<String> words = Set.of("hello", "how", "are", "you");
    System.out.println(separate("wow hellohowareyouhellohowareyou", words));
    // wow hello how are you hello how are you
    

    另一种解决方案,StringBuilder,从性能角度看对我来说更好。

    public static String separate(String str, Set<String> words) {
        List<String> res = new LinkedList<>();
        StringBuilder buf = new StringBuilder();
    
        for (int i = 0; i < str.length(); i++) {
            buf.append(str.charAt(i));
    
            if (str.charAt(i) == ' ' || words.contains(buf.toString())) {
                res.add(buf.toString().trim());
                buf.delete(0, buf.length());
            }
        }
    
        return String.join(" ", res);
    }
    

    【讨论】:

    • 漂亮而简单(但不是递归的)。如果一个词可以出现多次,它就不会按预期工作。
    • @c0der Afonso Hipólito 询问了递归如果需要。这里不需要使用递归。 str.replace() 替换 ALL 次出现,因此给定字符串中的多个单词将被成功替换。
    • 你是对的。它被标记为递归,因此可以解释。总的来说,这个问题没有很好的定义。
    【解决方案4】:

    对于递归方法,请尝试以下方法:

    public static String separateText(String text, List<String> words){
        return separateText(text, words, new StringBuilder());
    }
    
    public static String separateText(String text, List<String> words, StringBuilder result){
    
        for(String word : words){
            if (text.startsWith(word)){
               result.append(word).append(" ");
               text = text.substring(word.length());
               ArrayList<String> newList = new ArrayList<>(words);
               newList.remove(word);
               separateText(text, newList, result);
               break;
            }
        }
    
        return result.toString().trim();
    }
    

    【讨论】:

    • 顺便说一句。 ArrayList&lt;String&gt; newList = new ArrayList&lt;&gt;(words); 应该是 List&lt;String&gt; newList = new ArrayList&lt;&gt;(words);。这并不高效,因为假设您有 1M 个单词,并且每次迭代都复制它。这里最好使用其他集合类型。
    • 可以是List&lt;String&gt; newList = new ArrayList&lt;&gt;(words);,但我看不出有什么大的好处。你还会使用什么其他系列?
    • Queue:在递归调用之前去掉一个单词,在后面加上。
    • 我不会这样做,也不会对 1M 字使用递归解决方案。
    • 该解决方案假定所有单词都是已知的并且存在于字典中。如果至少有一个未知 - 空结果:(虽然不确定这是否是一个问题
    【解决方案5】:
    import java.util.*;
    
    public class Main {
        public static void main(String[] args) throws Exception {
            // You must sort this by it's length, or you will not have correct result
            // since it may cause match with more shorter words.
            // In this example, it's done
            List<String> words = Arrays.asList("hello", "how", "are", "you");
            List<String> detectedWords = new ArrayList<>();
            String text = "hellohowareyou";
            int i = 0;
            while (i < text.length()) {
                Optional<String> wordOpt = Optional.empty();
    
                for (String word : words) {
                    if (text.indexOf(word, i) >= 0) {
                        wordOpt = Optional.of(word);
                        break;
                    }
                }
                if (wordOpt.isPresent()) {
                    String wordFound = wordOpt.get();
                    i += wordFound.length();
                    detectedWords.add(wordFound);
                }
            }
            String result = String.join(" ", detectedWords);
            System.out.println(result);
        }
    }
    

    我假设:

    • 你的文字永远不会是null
    • 您的文本匹配正则表达式^(hello|how|are|you)$
    • 你的话必须排序

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-22
      • 1970-01-01
      相关资源
      最近更新 更多