【问题标题】:Counting Occurrences of a word from a String计算字符串中单词的出现次数
【发布时间】:2018-02-05 23:21:55
【问题描述】:

我希望能够计算每个单词在给定文件中重复的次数。但是,我在这样做时遇到了麻烦。我尝试了两种不同的方法。我使用 HashMap 并将单词作为键并将其频率作为关联值的一种。但是,这似乎不起作用,因为有了 HashMap,您无​​法访问指定索引处的元素。现在我尝试使用两个单独的数组列表,一个用于单词,一个用于该单词的每次出现。我的想法是这样的:在将单词添加到 wordsCount 数组列表时,如果单词已经在 wordsCount 中,则在已看到单词的索引处增加 cnt ArrayList 中元素的值。但是,我不确定要写什么来增加值

import java.io.*;
import java.lang.reflect.Array;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

public class MP0 {
    Random generator;
    String delimiters = " \t,;.?!-:@[](){}_*/";
    String[] stopWordsArray = {"i", "me", "my", "myself", "we", "our", "ours", "ourselves", "you", "your", "yours",
            "yourself", "yourselves", "he", "him", "his", "himself", "she", "her", "hers", "herself", "it", "its",
            "itself", "they", "them", "their", "theirs", "themselves", "what", "which", "who", "whom", "this", "that",
            "these", "those", "am", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "having",
            "do", "does", "did", "doing", "a", "an", "the", "and", "but", "if", "or", "because", "as", "until", "while",
            "of", "at", "by", "for", "with", "about", "against", "between", "into", "through", "during", "before",
            "after", "above", "below", "to", "from", "up", "down", "in", "out", "on", "off", "over", "under", "again",
            "further", "then", "once", "here", "there", "when", "where", "why", "how", "all", "any", "both", "each",
            "few", "more", "most", "other", "some", "such", "no", "nor", "not", "only", "own", "same", "so", "than",
            "too", "very", "s", "t", "can", "will", "just", "don", "should", "now"};
    private static String str;
    private static File file;
    private static Scanner s;   

    public MP0() {
    }

    public void process() throws Exception{
        ArrayList<Integer> cnt = new ArrayList<Integer>();
        boolean isStopWord = false;
        StringTokenizer st = new StringTokenizer(s.nextLine(), delimiters);
        ArrayList<String> wordsCount = new ArrayList<String>();

        while(st.hasMoreTokens()) {
            String s = st.nextToken().toLowerCase();
            if(!wordsCount.contains(s)) {
                for(int i = 0; i < stopWordsArray.length; i++) {
                    isStopWord = false;
                    if(s.equals(stopWordsArray[i])) {
                        isStopWord = true;
                        break;
                    }
                }
                if(isStopWord == false) {
                    wordsCount.add(s);
                    cnt.add(1);
                }
            }
            else { // i tried this but only displayed "1" for all words
                cnt.set(wordsCount.indexOf(s), cnt.get(wordsCount.indexOf(s) + 1));
            }
        }


        for(int i = 0; i < wordsCount.size(); i++) {
            System.out.println(wordsCount.get(i) + " " + cnt.get(i));
        }

    }

    public static void main(String args[]) throws Exception {
            try {
                file = new File("input.txt");
                s = new Scanner(file);
                str = s.nextLine();
                String[] topItems;
                MP0 mp = new MP0();
                while(s.hasNext()) {
                    mp.process();
                    str = s.nextLine();
                }
            }
            catch(FileNotFoundException e) {
                System.out.println("File not found");
            }
    }

}

【问题讨论】:

  • 如果您将键更改为单词,则哈希图的想法将起作用。我会打一个例子。
  • 这听起来像是 XY 问题。您可能需要考虑这个关于计算字符串中单词频率的问题:stackoverflow.com/questions/21771566/…

标签: java string arraylist hashmap


【解决方案1】:

我相信你可以使用 hashmap 来做你想做的事。像这样的:

              HashMap<String, Integer> mymap= new HashMap<>();

                for(String word: stopWordsArray) {
                    if (mymap.containsKey(word))
                        mymap.put(word, mymap.get(word) + 1);
                    else{
                        mymap.put(word, new Integer(1));
                    }
                }

编辑:在 cmets 中添加了更正

第二次编辑 Here 是一个关于如何做到这一点的 oracle 教程:

同样的思路,但看起来更简洁一些。以下是相关代码的摘要:

for (String word : stopWordsArray) {
            Integer freq = m.get(word);
            m.put(word, (freq == null) ? 1 : freq + 1);
        }

【讨论】:

  • 啊,我赢了。
  • 您不应该将初始计数设置为 1 吗?
  • @IanMc 是的,我应该:)
  • @FelipeCenteno 您可以使用一条线路! mymap.merge(word, 1, Integer::sum);
【解决方案2】:

你也可以使用 Pattern 和 matcher。

String in = "our goal is our power";
int i = 0;
Pattern p = Pattern.compile("our");
Matcher m = p.matcher( in );
while (m.find()) {
    i++;
}

【讨论】:

    【解决方案3】:

    我认为 Map 绝对是表示每个单词的计数的方法。在我看来,获取地图的最佳方式(或者至少是一种尚未提及的不同方式)是将单词放在特定的Stream 上。这样,您可以利用已经在 J​​ava 标准库中编写的大量代码,使您的代码更加简洁,并避免重新发明所有轮子的需要。流可能有一点学习曲线,但是一旦你理解了,它们就会非常有用。例如,观察你的 20+ 行方法减少到 2 行:

    import java.util.Map;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.stream.Stream;
    import static java.util.stream.Collectors.groupingBy;
    import static java.util.stream.Collectors.summingInt;
    import static java.util.function.Function.identity;
    
    public class CountWords
    {
        private static String delimiters = "[ \t,;.?!\\-:@\\[\\](){}_*/]+";
        private static ArrayList<String> stopWords =    new ArrayList<>(Arrays.asList(new String[] {"i", "me", "my", "myself", "we", "our", "ours", "ourselves", "you", "your", "yours",
                                                    "yourself", "yourselves", "he", "him", "his", "himself", "she", "her", "hers", "herself", "it", "its",
                                                    "itself", "they", "them", "their", "theirs", "themselves", "what", "which", "who", "whom", "this", "that",
                                                    "these", "those", "am", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "having",
                                                    "do", "does", "did", "doing", "a", "an", "the", "and", "but", "if", "or", "because", "as", "until", "while",
                                                    "of", "at", "by", "for", "with", "about", "against", "between", "into", "through", "during", "before",
                                                    "after", "above", "below", "to", "from", "up", "down", "in", "out", "on", "off", "over", "under", "again",
                                                    "further", "then", "once", "here", "there", "when", "where", "why", "how", "all", "any", "both", "each",
                                                    "few", "more", "most", "other", "some", "such", "no", "nor", "not", "only", "own", "same", "so", "than",
                                                    "too", "very", "s", "t", "can", "will", "just", "don", "should", "now"}));
        public static void main(String[] args) throws IOException //Your code should likely catch this
        {
            Path fLoc = Paths.get("test.txt"); //Or get from stdio, args[0], etc...
            CountWords cw = new CountWords();
            Map<String, Integer> counts = cw.count(Files.lines(fLoc).flatMap(s -> Arrays.stream(s.split(delimiters))));
            counts.forEach((k, v) -> System.out.format("Key: %s, Val: %d\n", k, v));
        }
    
        public Map<String, Integer> count(Stream<String> words)
        {
            return words.filter(s -> !stopWords.contains(s))
                        .collect(groupingBy(identity(), summingInt(s -> 1)));
        }
    }
    

    API 中查找这一切很容易,但这里有些内容可能不太容易解释:

    • Files.lines:一个漂亮的小方法,它将获取文件的路径,并返回文件中所有行的Stream。不过,我们实际上想要一个词流,这将我们带到下一个操作。
    • .flatMap:一般来说,映射操作会获取集合中的每一项,并将其转换为其他内容。 Streams 有一个方法,称为map,它将获取每个项目并将其转换为另一个项目。然而,在我们的例子中,我们想要将我们的行转换为单词,并且每一行可能包含许多单词,所以map 将不起作用。输入flatMap:一个映射操作后跟一个展平操作。通常,展平操作获取集合的每个元素,如果子元素本身就是集合,则扩展集合,以便父元素不再包含子集,而是将所有子元素的子元素作为自己的子元素.如果这听起来令人困惑,请听听有人比我解释得更好here。在 Java 的情况下,这意味着我们的映射操作必须返回一个 Stream,而扁平化将由 flatMap 方法处理。
    • 等一下,-&gt; 是干什么的?很高兴你问。看,flatMaphigher-order function - 也就是说,它是一个以另一个函数作为参数的函数。我们可以在某处将函数写为方法(以避免混淆术语,因为它们非常相似:方法是附加到对象或类的函数),但是这个特定函数没有附加到任何特定对象的逻辑基础,更重要的是,我们不关心重复使用它,所以它甚至不需要一个名字。仅指定内联函数会容易得多。输入lambda expressions!不过,这个问题与他们无关,因此请阅读链接以了解更多信息。
    • 我们的 lambda 将每个 Stringsplits 沿您指定的分隔符(我将您的 delim 字符串转换为 regular expression)。这会返回一个数组,但我们需要一个Stream,所以我们使用方便的方法Arrays.stream 方便转换。现在每一行都将变成一个单词流,flatMap 将处理将单独的行展平为文件中所有单词的单个流。虽然我自己想出了这个,但几乎相同的行是used in the common usage examples of the API
    • .filter:又一个高阶函数。这个从流中删除所有不会导致给定函数返回true的条目。在问题的示例代码中,您避免计算数组中的所有停用词,所以在这里我使用filter 来做同样的事情,借助相当方便(且不言自明)List.contains(它需要在List 中装箱您正在使用的数组,但我相信这对于您获得的简洁来说是值得的)。因此,我们有一个流只保留非停用词。
    • .collectgroupingBy 等:终于,好东西了。这条短线基本上完成了您的问题所要求的所有工作。 collect 是一种将Stream 重新转换为单个对象的方法,通常是一个集合对象,如列表或数组,因此得名。作为参数,它可以采用Collector,这是一个知道如何将给定 Stream 收集到所需对象的对象。我们可以建立自己的,但在这种情况下是不必要的;标准库再次为我们完成了这项工作。我们使用现有的收集器groupingBy。在其最基本的形式中,groupingBy 接受一个参数(一个函数;同样,我们有一个高阶函数),称为分类器,它将项目分类。对于这个参数,我们提供Function.identity()statically imported 来匹配收集器,这些收集器又被静态导入以匹配它们在 API 示例中使用的样式)。这个函数只是简单地获取参数并将其回显,以应对您需要函数参数但实际上不想修改输入的情况(它是等效但丑陋的x -&gt; x lambda 的替代方案)。我们想要这样做是因为这个函数的返回值构成了我们正在收集的地图的键,并且收集器将自动将所有返回值 .equal 组合在一起,并放在一个公共键下(并且我们所有重复的单词都将是.equal 彼此)。
    • 默认情况下,这将为我们留下一个映射,其中包含单词本身作为键,以及包含给定单词的每个单独实例的流作为值。我们不希望这样,但幸运的是,有一个 groupingBy 重载,它为我们提供了第二个参数来指定:一个收集器,它将每个 Stream 值变成每个键的单个对象值。由于当前流包含每个单词的所有实例,我们只需要获取每个流的长度并将其用作每个映射键的值。幸运的是,标准库又一次支持我们,使用 summingInt 收集器,它总结了流中每个项目的 int 表示。在这里,我们可以指定一个函数,该函数将为每个项目返回不同的int(例如,如果我们计算总字母而不是单词,则表达式将为s -&gt; s.length()),但我们不想这样做,所以我们忽略了使用提供给我们的s 变量,并不断用s -&gt; 1 返回1,确保为单词的每个实例添加1。
    • TL;DR 关于count 方法:我们使用内置的方法来简洁地过滤掉停用词,然后将剩余的单词分组到一个以单词为键的映射中,并统计将用作值的那些词,全部在 2 行中。

    【讨论】:

      猜你喜欢
      • 2011-02-07
      • 1970-01-01
      • 2015-09-14
      • 2021-01-09
      • 1970-01-01
      • 2013-02-01
      • 2019-07-31
      • 2014-04-29
      • 2022-01-06
      相关资源
      最近更新 更多