1

I need to know how to properly iterate over an ArrayList of elements and count the number of an elements appearance in the list without before hand knowing the element. I have tried a couple of approaches and have one that currently works but I feel that it is ugly and improper.

To explain a little more in depth, I have a java web application, one of the pages will present a graph that displays the number of hits a site gets in a day/week/month. I am taking the dates that the site is hit and putting them in an array list, iterating over the array list grabbing an element and counting the number of times it appears, I then put the date as a string and its total count and putting it into a map as a key value pair of (String,int), I will then take this map and convert it to a JSON object and use it with the D3JS library to create a nice bar chart that depicts the information. Below is the code I have tried and the results it presents. The results are accurate, but rather than just working I would like to do it properly and elegant and I know I have not achieved that here. I will explain it as thoroughly as possible and look forward to feedback and ideas of a better approach.

test code main method:

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 testing purposes I created an array list and added multiple string dates to it (honestly I could have used an array and cut down on test code but I wanted to get as close to an actual case as possible). Afterwards I create a nested for loop, the outer for loop grabs the first item in the list and uses that as the current date for the key in the map, it is also tasked with starting the counter at 0. The inner for loop starts at zero regardless of where I is and grabs the elements of the list to compare them to element i if they are equal then counter is incremented by 1. Once the loop is completed just before exiting the outer loop the current date being worked with and the counter total is added into the map.

The results of the counter:

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

These were pulled from the console after the test ran. The results are correct. My question is more to see if there are more elegant or proper ways to perform this task?

Thank you!

  • Some orthogonal tips… To serialize date-time values as strings, follow the standard [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) formats. And in your Java code use the date-time classes found in either the [Joda-Time](http://www.joda.org/joda-time/) library or the java.time package of Java 8 (inspired by Joda-Time). Both offer a `LocalDate` that could represent this data. And both use ISO 8601 as their defaults when parsing/generating string representations. – Basil Bourque Nov 12 '14 at 16:17

3 Answers3

3

Multiset

I would suggest the MultiSet collection in Google Guava. It has nice abilities that can save you a lot of time.

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

Print:

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

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
1

Map

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

The whole benefit of the map is that you can do a lookup on it very fast. This allows you to do the 'counting' while you scan through the array once. This makes the running time of the algorithm significantly faster (theoretically speaking, we improve from O(n^2) to O(n)).

We also can use java's for-each loop (as I did here), rather than need to maintain a separate counter (i.e. the i variable) in the for loop. This is just for aesthetics - it makes the code a bit easier to read, but doesn't fundamentally change the algorithm.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • This is exactly what I was looking for, I knew that My code was going over the list way to many times plus it was bulky and hard to read, I don't discredit the other responses in here but this is the one I have accepted for my answer. Thank you very much. –  Nov 12 '14 at 15:32
  • @vaxquis - While I'm not going to undo your edit, I much prefer the `==false` to the `!` syntax. It is (IMHO) easy to misread the `!` when looking over the code. This is pretty subjective, I know. – dan.m was user2321368 Nov 12 '14 at 16:53
  • thanks. I've done it for the very sake of providing the most common coding style around - the inlined braces are ubiquitous in Java (and even sometimes in C/C++), from library codes to tutorials; as to the latter... `== false` goes on the end of line, while `!` goes at the very start - that makes `!` harder to skip, esp. with long lines. Still, that one is just an opinion; I made the edit because the consensus on this one is heavily in favour of the !bool syntax, http://stackoverflow.com/questions/2661110/is-it-bad-to-explicitly-compare-against-boolean-constants-e-g-if-b-false-i etc. –  Nov 12 '14 at 17:21
0

Your code is doing a lot of duplicate counting. I'd use a Set to avoid that. There's also the Collections.frequency() method to count the occurrences:

Set<String> compSet = new HashSet<String>(comp); // unique elements of comp
for (String s : compSet) {
    compMap.put(s, Collections.frequency(comp, s));
}
James
  • 195
  • 3
  • 11
  • This is still a O(n^2) algorithm. Which is completely unnecessary as there exists a fairly obvious O(n) solution. – dan.m was user2321368 Nov 12 '14 at 15:23
  • I liked this answer also however @user2321368 is correct in this situation there is no need to run through the list more than once, which in the end is what I was looking for in terms of elegance and time, there will be many items in the list that I am checking and I would like it to run as optimally as possible. Thank you very much for your reply and taking the time to help someone out. –  Nov 12 '14 at 15:35
  • 1
    @James Not the most optimal approach for this particular problem, but thanks for posting. I did not know about that handy utility in `Collections`. And for a small set of data where performance is not a concern, I'd rather use this shorter code. – Basil Bourque Nov 12 '14 at 16:08