Efficient ways to traverse and group similar objects from a huge collection

I am currently working towards on an implementation that basically involves attending to an arraylist of objects, say a 1000, find commonalities in their properties and group them.

For example

ArrayList itemList<CustomJaxbObj> = {Obj1,obj2,....objn} //n can reach to 1000

Object attributes - year of registration, location, amount

Grouping criteria - for objects with same year of reg and location...add the amount

If there are 10 Objects, out of which 8 objects have same loc and year of registration, add amount for all 8 and other 2 whose year of reg and loc match. So at the end of operation I am left with 2 objects. 1 which is a total sum of 8 matched objects and 1 which is a total of 2 matched criteria of objects.

Currently I am using dual traditional loops. Advanced loops are better but they dont offer much control over indices, which I need to perform grouping. It allows me to keep track of which individual entries combined to form a new entry of grouped entries.

for (i = 0; i < objlist.size(); i++) {
  for(j = i+1; j< objList.size();j++){
    //PErform the check with if/else condition and traverse the whole list
   }
}

Although this does the job, looked very inefficient and process heavy. Is there a better way to do this. I have seen other answers which asked me to use Java8 streams, but the operations are complex, hence grouping needs to be done. I have given an example of doing something when there is a match but there is more to it than just adding.

Is there a better approach to this? A better data structure to hold data of this kind which makes searching and grouping easier?

Adding more perspective, apologies for not furnishing this info before.

The arraylist is a collection of jaxb objects from an incoming payload xml.

XML heirarchy

    <Item>
<Item1>
    <Item-Loc/>
    <ItemID>
    <Item-YearofReg/>
    <Item-Details>
        <ItemID/>
        <Item-RefurbishMentDate>
        <ItemRefurbLoc/>
    </Item-Details>
</Item1>
<Item2></Item2>
<Item3></Item3>
....
</Item>

So the Jaxb Object of Item has a list of 900-1000 Items. Each item might have a sub section of ItemDetails which has a refurbishment date.The problem I face is, dual loops work fine when there is no Item Details section, and every item can be traversed and checked. Requirement says if the item has been refurbished, then we overlook its year of reg and instead consider year of refurbishment to match the criteria.

Another point is, Item Details need not belong to same Item in the section, that is Item1's item details can come up in Item2 Item Details section, item id is the field using which we map the correct item to its item details.

This would mean I cannot start making changes unless I have read through the complete list. Something a normal for loop would do it, but it would increase the cyclomatic complexity, which has already increased because of dual loops.

Hence the question, which would need a data structure to first store and analyse the list of objects before performing the grouping.

Apologies for not mentioning this before. My first question in stackoverflow, hence the inexperience.

Upvotes: 1

Views: 569

Answers (4)

Ousmane D.
Ousmane D.

Reputation: 56433

Not 100% sure what your end goal is but here is something to get you started. to group by the two properties, you can do something like:

Map<String, Map<Integer, List<MyObjectType>>> map = itemList.stream()
                .collect(Collectors.groupingBy(MyObjectType::getLoc,
                         Collectors.groupingBy(MyObjectType::getYear)));

The solution above assumes getLoc is a type String and getYear is a type Integer, you can then perform further stream operations to get the sum you want.

Upvotes: 1

user2080225
user2080225

Reputation:

I would make "Year+Location" concatenated be the key in a hashmap, and then let that map hold whatever is associated with each unique key. Then you can just have one "for loop" (not nested looping). That's the simplest approach.

Upvotes: 0

xiaofeng.li
xiaofeng.li

Reputation: 8587

You can use Collectors.groupingBy(classifier, downstream) with Collectors.summingInt as the downstream collector. You didn't post the class of the objects so I took the leave to define my own. But the idea is similar. I also used AbstractMap.SimpleEntry as the key to the final map.

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GroupByYearAndLoc {
    static class Node {
        private Integer year;
        private String loc;
        private int value;

        Node(final Integer year, final String loc, final int value) {
            this.year = year;
            this.loc = loc;
            this.value = value;
        }
    }

    public static void main(String[] args) {
        List<Node> nodes = new ArrayList<>();
        nodes.add(new Node(2017, "A", 10));
        nodes.add(new Node(2017, "A", 12));
        nodes.add(new Node(2017, "B", 13));
        nodes.add(new Node(2016, "A", 10));

        Map<AbstractMap.SimpleEntry<Integer, String>, Integer> sums = nodes.stream()
                // group by year and location, then sum the value.
                .collect(Collectors.groupingBy(n-> new AbstractMap.SimpleEntry<>(n.year, n.loc), Collectors.summingInt(x->x.value)));
        sums.forEach((k, v)->{
            System.out.printf("(%d, %s) = %d\n", k.getKey(), k.getValue(), v);
        });
    }
}

And the output:

(2017, A) = 22
(2016, A) = 10
(2017, B) = 13

Upvotes: 0

Priyanka Soni
Priyanka Soni

Reputation: 11

You can use hash to add the amounts of elements having same year of registration and location

Upvotes: 0

Related Questions