【问题标题】:grouping List of Objects and counting using Java collection使用 Java 集合对对象列表进行分组和计数
【发布时间】:2016-08-29 08:42:51
【问题描述】:

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

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

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

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

【问题讨论】:

  • A HashMap<String, Integer> 之前已多次用于类似目的(假设您将 aaa 存储为 String 对象;否则您将希望使用 String 以外的其他类型作为键,只需确保该类型实现hashCode()equals())。
  • 嗨,感谢您提供许多答案,但没有解决我的问题...我有一个像 Hashset messages = new HashSet(); 这样的列表消息包含上述用户和消息列的列表。所以我必须提取并与用户一起显示计数。
  • 您必须向我们展示一些代码,这将是描述您的设计的最准确方式。最好为我们生成 i minimal, complete and verifiable example
  • 那里有很多代码……

标签: java collections count grouping


【解决方案1】:

您可以使用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);
  }
}

然后你可以在完成后迭代地图:

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())

【讨论】:

    【解决方案2】:

    将其他几个答案的部分拼凑在一起,根据其他问题调整您的代码并修复一些小错误:

        // 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 版本。
    • 嘿,谢谢它的工作......但我无法使用流(java 7/8)功能来做到这一点......不知道为什么它仍然在 Eclipse 中给出错误......所以我用像地图条目这样的旧方式并遍历它的工作!
    • 如何从中取出最后一个结果??
    • 呃,我相信这不是问题所在。幸运的是,这很容易,你可能会发现自己很容易:stringsWithCount 被声明为TreeMapTreeMapSortedMap 接口的实现(这就是@GhostCar 和@NicolasFilotto 建议的原因)。在SortedMap 的API 文档中,您注意到九个方法之一是lastKey()。所以stringsWithCount.lastKey() 将返回"WVU"。如果你把它传递给stringsWithCount.get(),你也会得到计数(在这种情况下是1)。
    • 我试过 stringWithCount.lastKey() 像 G_LO G_LO G_LO WVU WVU WVU WVU WVU WVU 一样打印我
    【解决方案3】:

    你需要一个来自番石榴的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() + ")");            
    }
    

    【讨论】:

    • 提示:您应该更明确地说明需要为您的解决方案使用 3rd 方库。而且我还想知道多重集是否会将“ccc”视为“CCC” - 正如他的示例输入中所给出的那样。
    • 我会改写:他不需要。也许他可以;但无需需要求助于第三方库即可解决问题。
    【解决方案4】:

    假设您使用 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()
                )
            );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-20
      • 1970-01-01
      • 2013-09-05
      • 2020-12-29
      • 2020-09-03
      • 2014-06-07
      相关资源
      最近更新 更多