使用Java集合对对象列表进行分组和计数

3

哪个Java集合类更适合对对象列表进行分组?

我有一个用户消息列表,如下所示:

aaa hi
bbb hello
ccc Gm
aaa  Can?
CCC   yes
ddd   No

我想从这个消息对象列表中计算并显示 aaa(2)+bbb(1)+ccc(2)+ddd(1)。有任何的代码帮助吗?


之前已经多次使用过 HashMap<String, Integer> 来实现类似的功能(假设您将 aaa 存储为字符串对象;否则,您需要使用另一种类型作为键,但请确保该类型实现了 hashCode()equals() 方法)。 - Ole V.V.
嗨,感谢您提供的许多答案,但都没有解决我的问题...我有一个列表,类似于Hashset<String> messages = new HashSet(); messages包含用户和消息列的列表。因此,我必须提取并显示每个用户的计数。 - mangala udupa
你需要展示一些代码,这是描述你的设计最精确的方式。最好为我们提供一个最小、完整和可验证的示例 - Ole V.V.
代码在此链接中 http://stackoverflow.com/questions/39217924/parse-xml-using-java-stax-count-number-of-content-tags - mangala udupa
那是一大堆代码啊... - Ole V.V.
4个回答

4
您可以使用 Map<String, Integer> 来表示字符串的集合,其中键表示每个单独的字符串,而映射值为每个字符串的计数器。

例如,您可以这样做:
// where ever your input comes from: turn it into lower case,
// so that "ccc" and "CCC" go for the same counter
String item = userinput.toLowerCase(); 

// as you want a sorted list of keys, you should use a TreeMap
Map<String, Integer> stringsWithCount = new TreeMap<>();
for (String item : str) {
  if (stringsWithCount.contains(item)) {
    stringsWithCount.put(item, stringsWithCount.get(item)+1));
  } else {
    stringsWithCount.put(item, 0);
  }
}

然后在完成操作后,您可以迭代Map:

for (Entry<String, Integer> entry : stringsWithCount.entrySet()) {

并构建您的结果字符串。

那就像老式实现一样;如果你想要花哨并惊喜你的老师,你可以选择Java8/lambda/stream解决方案。 (我不建议除非你真的投入时间完全理解以下解决方案;因为这是未经我的测试的)

Arrays.stream(someListOrArrayContainingItems)
  .collect(Collectors
     .groupingBy(s -> s, TreeMap::new, Collectors.counting()))
  .entrySet()
  .stream()
  .flatMap(e -> Stream.of(e.getKey(), String.valueOf(e.getValue())))
  .collect(Collectors.joining())

1
你需要从Guava中使用MultiSet。该集合类型是专为此类任务而设计的:量身定制
MultiSet<String> multiSet = new MultiSet<>();
for (String line : lines) { // somehow you read the lines
    multiSet.add(line.split(" ")[0].toLowerCase());
}
boolean first = true;
for (Multiset.Entry<String> entry : multiset.entrySet()) {
    if (!first) {
        System.out.println("+");
    }
    first = false;
    System.out.print(entry.getElement() + "(" + entry.getCount() + ")");            
}

提示:您应该更明确地表明需要使用第三方库来解决问题。我也想知道 multiset 是否会将 "ccc" 视为输入示例中给出的 "CCC"。 - GhostCat
我会重新措辞:他不需要这样做。也许他可以这样做;但是问题可以在不必求助于第三方库的情况下解决。 - GhostCat

1
假设您使用的是Java 8,可以使用Stream API编写类似于以下内容的代码:
List<Message> messages = ...;
// Convert your list as a Stream
// Extract only the login from the Message Object
// Lowercase the login to be able to group ccc and CCC together
// Group by login using TreeMap::new as supplier to sort the result alphabetically
// Convert each entry into login(count)
// Join with a +
String result =
    messages.stream()
        .map(Message::getLogin)
        .map(String::toLowerCase)
        .collect(
            Collectors.groupingBy(
                Function.identity(), TreeMap::new, Collectors.counting()
            )
        )
        .entrySet()
        .stream()
        .map(entry -> entry.getKey() + '(' + entry.getValue() + ')')
        .collect(Collectors.joining("+"))
System.out.println(result);

输出:

aaa(2)+bbb(1)+ccc(2)+ddd(1)

如果您想按登录分组消息并将结果作为集合呈现,可以按以下步骤进行:
Map<String, List<Message>> groupedMessages = 
    messages.stream()
        .collect(
            Collectors.groupingBy(
                message -> message.getLogin().toLowerCase(), 
                TreeMap::new, 
                Collectors.toList()
            )
        );

1
从其他答案中整合思路,根据另一个问题调整您的代码并修复一些微不足道的错误:
    // as you want a sorted list of keys, you should use a TreeMap
    Map<String, Integer> stringsWithCount = new TreeMap<>();
    for (Message msg : convinfo.messages) {
        // where ever your input comes from: turn it into lower case,
        // so that "ccc" and "CCC" go for the same counter
        String item = msg.userName.toLowerCase();
        if (stringsWithCount.containsKey(item)) {
            stringsWithCount.put(item, stringsWithCount.get(item) + 1);
        } else {
            stringsWithCount.put(item, 1);
        }
    }
    String result = stringsWithCount
            .entrySet()
            .stream()
            .map(entry -> entry.getKey() + '(' + entry.getValue() + ')')
            .collect(Collectors.joining("+"));
    System.out.println(result);

这将打印:

aaa(2)+bbb(1)+ccc(2)+ddd(1)

抱歉风格不一致。您可以根据自己的喜好和Java版本,使用循环或流来完成所有操作。 - Ole V.V.
如何将最后一个结果取出来? - mangala udupa
额,我认为这超出了问题的范围。幸运的是,它很简单,你甚至可以自己找出答案:stringsWithCount 声明了一个 TreeMapTreeMapSortedMap 接口的一个实现(这就是为什么 @GhostCar 和 @NicolasFilotto 建议使用它)。在 SortedMap 的 API 文档中,您会发现其中九个方法之一是 lastKey()。所以 stringsWithCount.lastKey() 将返回 "WVU"。如果您将其传递到 stringsWithCount.get() 中,则还将获得数量(在本例中为 1)。 - Ole V.V.
我尝试了一下stringWithCount.lastKey()的输出结果是 G_LO、G_LO、WVU、WVU、WVU、WVU、WVU、WVU。 - mangala udupa
好的,我把代码放错位置了,循环9次...现在已经正常工作了。 - mangala udupa
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接