【问题标题】:Splitting Strings in a stream in Java?在Java中拆分流中的字符串?
【发布时间】:2020-08-10 06:50:51
【问题描述】:

我有一个任务,我们正在读取文本文件并计算每个单词的出现次数(忽略标点符号)。我们不必使用流,但我想练习使用它们。

到目前为止,我能够读取一个文本文件并将每一行放入一个字符串中,并将所有字符串放入一个列表中:

try (Stream<String> p = Files.lines(FOLDER_OF_TEXT_FILES)) {
    list = p.map(line -> line.replaceAll("[^A-Za-z0-9 ]", ""))
            .collect(Collectors.toList());
}

然而,到目前为止,它只是将所有行变成了一个字符串,因此列表的每个元素都不是一个单词,而是一行。有没有一种使用流的方法,我可以让每个元素都是一个单词,使用像 String 的 split 方法和正则表达式?还是我必须在流本身之外处理这个问题?

【问题讨论】:

  • 分割后使用flatMap进行展平

标签: java string java-stream


【解决方案1】:

我可能误解了你的问题。但是,如果您只想要逗号分隔的单词,您可以尝试下面的代码 将line.replaceAll("[^A-Za-z0-9 ]", "") 替换为Arrays.asList(line.replaceAll("[^A-Za-z0-9 ]", "").split(" ")).stream().collect(Collectors.joining(","))

再次使用列表上的连接方法得到逗号分隔的单词字符串。

String commaSeperated = list.stream().collect(Collectors.joining(",")) ;

您可以根据您的要求对最终字符串执行进一步的操作。

【讨论】:

    【解决方案2】:

    试试这个:

        String fileName = "file.txt";
            try {
            Map<String, Long> wordCount = Files.lines(Path.of(fileName))
                    .flatMap(line -> Arrays.stream(line.split("\\s+")))
                         .filter(w->w.matches("[a-zA-Z]+"))
                         .sorted(Comparator.comparing(String::length)
                                .thenComparing(String.CASE_INSENSITIVE_ORDER))  
                            .collect(Collectors.groupingBy(w -> w, 
             LinkedHashMap::new, Collectors.counting()));
            wordCount.entrySet().forEach(System.out::println);
            }catch (Exception e) {
                e.printStackTrace();
            }
    

    这个比较简单。它只是在空白处拆分并通过将它们放入映射中来计算单词,其中 Key 是单词,Value 是包含计数的 long。

    我添加了一个过滤器,仅捕获仅包含字母的单词。 其工作方式是将Lines 放入流中。然后使用String.split 将每一行拆分为单词。由于这会创建一个数组,flatMap 将所有这些单独的单词流转换为一个单独的流,并在其中处理它们。 它的工作马是Collectors.groupingBy,它将以特定方式为每个键对值进行分组。在这种情况下,我指定了Collectors.counting() 方法,以在每次出现键(即单词)时增加计数。

    作为一种选择,我先按长度排序单词,然后按字母顺序排序,忽略大小写。

    【讨论】:

      【解决方案3】:

      不要在一行上应用replaceAll,而是在该行的单词上执行如下操作:

      import java.util.Arrays;
      import java.util.List;
      import java.util.stream.Collectors;
      
      public class Main {
          public static void main(String[] args) {
              String str = "Harry is a good cricketer. Tanya is an intelligent student. Bravo!";
              List<String> words = Arrays.stream(str.split("\\s+")).map(s -> s.replaceAll("[^A-Za-z0-9 ]", ""))
                      .collect(Collectors.toList());
              System.out.println(words);
          }
      }
      

      输出:

      [Harry, is, a, good, cricketer, Tanya, is, an, intelligent, student, Bravo]
      

      注意:正则表达式 \\s+ 在空格上分割字符串。

      【讨论】:

        【解决方案4】:

        首先,对于每一行,我们删除所有非字母数字字符(不包括空格),然后我们按空格分割,因此所有元素都是单个单词。由于我们是平面映射,因此流由所有单词组成。然后我们简单地使用groupingBy收集器进行收集,并使用counting()作为下游收集器。这将给我们留下一个Map&lt;String, Long&gt;,键是单词,值是出现的次数。

        list = p
            .flatMap(line -> Arrays.stream(line.replaceAll("[^0-9A-Za-z ]+", "").split("\\s+")))
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting());
        

        【讨论】:

          【解决方案5】:

          由于要处理单词时,行边界无关紧要,因此首选的方法是不打扰拆分为行,只需将行拆分为单词,而是首先将文件拆分为单词.你可以使用类似的东西:

          Map<String,Long> wordsAndCounts;
          try(Scanner s = new Scanner(Paths.get(path))) {
              wordsAndCounts = s.findAll("\\w+")
                  .collect(Collectors.groupingBy(MatchResult::group, Collectors.counting()));
          }
          wordsAndCounts.forEach((w,c) -> System.out.println(w+":\t"+c));
          

          ScannerfindAll 方法需要 Java 9 或更高版本。 This answer 包含用于 Java 8 的 findAll 实现。这允许在 Java 8 上使用它,并且只需切换到标准方法即可轻松迁移到新版本。

          【讨论】:

            【解决方案6】:

            可以使用Pattern.splitAsStream 以高效的方式拆分字符串,同时在创建出现计数图之前替换所有非单词字符:

            Pattern splitter = Pattern.compile("(\\W*\\s+\\W*)+");
            String fileStr = Files.readString(Path.of(FOLDER_OF_TEXT_FILES));
            
            Map<String, Long> collect = splitter.splitAsStream(fileStr)
                    .collect(groupingBy(Function.identity(), counting()));
            
            System.out.println(collect);
            

            为了拆分和删除非单词字符,我们使用模式(\W*\s+\W*)+ 查找可选的非单词字符,然后是空格,然后再查找可选的非单词字符。

            【讨论】:

              【解决方案7】:

              对于整个“读取文本文件并使用流计算每个单词”,我建议使用如下内容:

              try (Stream<String> lines = Files.lines(FOLDER_OF_TEXT_FILES)) {
                  lines.flatMap(l -> Arrays.stream(l.split(" ")))
                      .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
              
              }
              

              无需先将所有内容收集到一个列表中,这可以内联完成。
              使用 try-with-resources 也很好。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2015-03-30
                • 2012-03-21
                • 2013-02-07
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多