0

With my little understanding of streams, I might be doing something wrong.

My sample class:

@Getter
@Setter
@AllArgsConstructor
class SampleAction {
    private String grouping;
    private String category;
    private String actionType;
    private String details;
    private String moreDetails;
    private String etc;
}

Sample data:

List<SampleAction> items = new ArrayList<SampleAction>() {{
    add(new SampleAction("Group1", "Cat1", "Type1", "details1", "", "etc"));
    add(new SampleAction("Group2", "Cat1", "Type1", "details2", "", "etc"));
    add(new SampleAction("Group1", "Cat1", "Type2", "details3", "", "etc"));
    add(new SampleAction("Group1", "Cat2", "Type1", "details4", "", "etc"));
    add(new SampleAction("Group2", "Cat2", "Type2", "details5", "", "etc"));
    add(new SampleAction("Group2", "Cat2", "Type2", "details6", "", "etc"));
    add(new SampleAction("Group2", "Cat3", "Type1", "details7", "", "etc"));
}};

My grouping code (group on grouping, category and action type):

items.stream()
   .collect(Collectors.groupingBy(SampleAction::getGrouping,
            Collectors.groupingBy(SampleAction::getCategory,
            Collectors.groupingBy(SampleAction::getActionType))));

This gives me the grouped output, but the return type is Map<String, Map<String, Map<String, List<SampleAction>>>>

This looks a little scary. What is the best way to manage this? Do I really need this complex type for the output?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
DDan
  • 8,068
  • 5
  • 33
  • 52
  • 1
    Does this answer your question? [Group by multiple field names in java 8](https://stackoverflow.com/questions/28342814/group-by-multiple-field-names-in-java-8) – Ferry Mar 11 '21 at 10:39

2 Answers2

0

If you don't want to deal with such nested Maps, you can create a composite key to group by:

Map<String,List<SampleAction>> map =
    items.stream()
          .collect(Collectors.groupingBy(sa -> sa.getGrouping () + '/' + 
                                               sa.getCategory() + '/' + 
                                               sa.getActionType()));

Of course, you'll have to create a composite key whenever you want to search for values in the Map.

For example:

List<SampleAction> list = map.get (aGroup + '/' + aCategory + '/' + anActionType);
Eran
  • 387,369
  • 54
  • 702
  • 768
0

solution 1: keep on your solution. But it's verbose. solution 2: use composite object. But you must override equals and hashcode to avoid collision and it must be immutable for good sake.

class SampleGroup{
    private final String grouping;
    private final String category;
    private final String actionType;

//override equals and hashcode here to make it unique and avoid hashmap collision
//all arguments constructor
}

Good news is you can provide immutable and equals and hashcode by using lombok annotations @Value if you have it. So you don't have to write your own code to override that methods.

so you can use it like:

items.stream()
       .collect(Collectors.groupingBy(sample -> new SampleGroup(sample.getGrouping(), 
            sample.getCategory(), sample.getActionType());

and call it like:

items.get(new SampleGroup("bla", "blo", "bli"));

solution 3: like Eran's solution, give a unique symbols to concat between composite keys. But you have to remember its format and consistent on using it.

choose wisely.

Ferry
  • 344
  • 3
  • 10