如何从数组列表中提取项目并对其进行计数,以便从元素及其总计数(正确)在 Java 中创建映射?

Posted

技术标签:

【中文标题】如何从数组列表中提取项目并对其进行计数,以便从元素及其总计数(正确)在 Java 中创建映射?【英文标题】:How do you pull items from an array list and count them in order to create a map from the element and its total count (properly) in Java? 【发布时间】:2014-11-12 15:07:10 【问题描述】:

我需要知道如何正确地遍历元素的 ArrayList 并在事先不知道元素的情况下计算列表中出现的元素的数量。我尝试了几种方法,目前有一种方法可行,但我觉得它丑陋且不合适。

为了更深入地解释一下,我有一个 java web 应用程序,其中一个页面将显示一个图表,显示网站在一天/一周/一个月内获得的点击次数。我正在获取网站被点击的日期并将它们放入数组列表中,遍历数组列表,获取一个元素并计算它出现的次数,然后将日期作为字符串及其总数并将其放入作为 (String,int) 的键值对转换为映射,然后我将获取此映射并将其转换为 JSON 对象,并将其与 D3JS 库一起使用来创建一个漂亮的条形图来描述信息。以下是我尝试过的代码及其呈现的结果。结果是准确的,但不仅仅是工作,我想正确而优雅地完成它,我知道我在这里没有做到这一点。我会尽可能彻底地解释它,并期待更好的方法的反馈和想法。

测试代码主方法:

public static void main(String[] args) 

    Map<String, Integer> compMap = new HashMap<String, Integer>();

    ArrayList<String> comp = new ArrayList<String>();
    comp.add("10-31-2014");
    comp.add("10-31-2014");
    comp.add("10-31-2014");
    comp.add("10-31-2014");
    comp.add("10-15-2014");
    comp.add("10-15-2014");
    comp.add("10-15-2014");
    comp.add("10-15-2014");
    comp.add("10-15-2014");
    comp.add("10-12-2014");
    comp.add("10-12-2014");
    comp.add("10-12-2014");
    comp.add("10-12-2014");
    comp.add("10-12-2014");
    comp.add("10-12-2014");
    comp.add("10-12-2014");
    comp.add("10-12-2014");
    comp.add("10-12-2014");
    comp.add("10-09-2014");
    comp.add("10-09-2014");
    comp.add("10-09-2014");
    comp.add("10-09-2014");
    comp.add("10-09-2014");
    comp.add("10-01-2014");
    comp.add("10-01-2014");
    comp.add("10-01-2014");
    comp.add("10-01-2014");
    comp.add("10-01-2014");
    comp.add("10-01-2014");
    comp.add("10-01-2014");

    // 01 = 7 : 09 = 5 : 12 = 9 : 15 = 5 : 31 = 4

    for (int i = 0; i < comp.size(); i++) 

        int counter = 0;
        String current = comp.get(i);

        for (int j = 0; j < comp.size(); j++)
            if (comp.get(j).equals(comp.get(i)))
                counter++;
            
        
        compMap.put(current, counter);
    

    for (Map.Entry<String,Integer> entry : compMap.entrySet()) 

        System.out.println("Key Value Pair Is: " + entry.getKey() + " : " + entry.getValue());

    

 

出于测试目的,我创建了一个数组列表并向其中添加了多个字符串日期(老实说,我本可以使用数组并减少测试代码,但我希望尽可能接近实际情况)。之后我创建了一个嵌套 for 循环,外部 for 循环抓取列表中的第一项并将其用作地图中键的当前日期,它还负责从 0 开始计数器。内部 for 循环开始于零,无论我在哪里,并抓取列表中的元素以将它们与元素 i 进行比较,如果它们相等,则计数器增加 1。一旦在退出外循环之前完成循环,当前正在使用的日期和计数器总计被添加到地图中。

计数器的结果:

Key Value Pair Is: 10-15-2014 : 5
Key Value Pair Is: 10-12-2014 : 9
Key Value Pair Is: 10-31-2014 : 4
Key Value Pair Is: 10-01-2014 : 7
Key Value Pair Is: 10-09-2014 : 5

这些是在测试运行后从控制台中提取的。结果是正确的。我的问题更多是看看是否有更优雅或更合适的方法来执行这项任务?

谢谢!

【问题讨论】:

一些正交提示... 要将日期时间值序列化为字符串,请遵循标准 ISO 8601 格式。在您的 Java 代码中,使用在 Joda-Time 库或 Java 8 的 java.time 包(受 Joda-Time 启发)中找到的日期时间类。两者都提供了可以代表此数据的LocalDate。在解析/生成字符串表示时,两者都使用 ISO 8601 作为默认值。 【参考方案1】:

Multiset

我建议Google Guava 中的MultiSet 集合。它具有很好的功能,可以为您节省大量时间。

com.google.common.collect.Multiset<String> multiset = com.google.common.collect.HashMultiset.create();
multiset.add("10-31-2014");
multiset.add("10-31-2014");
multiset.add("10-15-2014");
multiset.add("10-15-2014");
multiset.add("10-15-2014");
multiset.add("10-15-2014");
multiset.toString();

打印:

[10-31-2014 x 2, 10-15-2014 x 4]

【讨论】:

【参考方案2】:

地图

for(String arrayElement : comp) 
    if(!compMap.containsKey(arrayElement)) 
        compMap.put(arrayElement, 1);
     else 
        int newCount = compMap.get(arrayElement) + 1;
        compMap.put(arrayElement, newCount);
    

地图的全部好处是您可以非常快速地对其进行查找。这允许您在扫描阵列一次时进行“计数”。这使得算法的运行时间显着加快(理论上,我们从 O(n^2) 提高到 O(n))。

我们也可以使用 java 的 for-each 循环(就像我在这里所做的那样),而不需要在 for 循环中维护一个单独的计数器(即 i 变量)。这只是为了美观 - 它使代码更易于阅读,但不会从根本上改变算法。

【讨论】:

这正是我要找的,我知道我的代码在列表中重复了很多次,而且它体积庞大且难以阅读,我不会抹黑这里的其他回复但这是我接受的答案。非常感谢。 @vaxquis - 虽然我不会撤消您的编辑,但我更喜欢 ==false 而不是 ! 语法。查看代码时(恕我直言)很容易误读!。这是相当主观的,我知道。 谢谢。我这样做是为了提供最常见的编码风格——从库代码到教程,内联大括号在 Java(甚至有时在 C/C++)中无处不在;至于后者...== false 位于行尾,而! 位于最开始 - 这使得! 更难跳过,尤其是。用长线。不过,那只是一种意见。我进行了编辑,因为对此的共识非常支持 !bool 语法,***.com/questions/2661110/… 等。【参考方案3】:

您的代码进行了大量重复计数。我会使用 Set 来避免这种情况。还有Collections.frequency() 方法来统计出现次数:

Set<String> compSet = new HashSet<String>(comp); // unique elements of comp
for (String s : compSet) 
    compMap.put(s, Collections.frequency(comp, s));

【讨论】:

这仍然是一个 O(n^2) 算法。这是完全没有必要的,因为存在一个相当明显的 O(n) 解决方案。 我也喜欢这个答案,但是@user2321368 在这种情况下是正确的,不需要多次遍历列表,这最终是我在优雅和时间方面所寻找的,我正在检查列表中的许多项目,我希望它尽可能以最佳状态运行。非常感谢您的回复并花时间帮助别人。 @James 不是解决这个特定问题的最佳方法,但感谢您的发帖。我不知道Collections 中有那个方便的实用程序。对于不关心性能的一小部分数据,我宁愿使用这个更短的代码。

以上是关于如何从数组列表中提取项目并对其进行计数,以便从元素及其总计数(正确)在 Java 中创建映射?的主要内容,如果未能解决你的问题,请参考以下文章

使用正则表达式提取不同格式的日期并对其进行排序 - 熊猫

如何按列值的计数进行分组并对其进行排序?

如何使用字符串列表搜索上下文并对其进行排序?

从 SQL 中提取一列值并将它们放入数组 VBA [关闭]

从 txt 中读取数字并对其进行排序 - 转换错误

如何从数据库中获取所有表名并对其进行分页