4

I have the following usecase. I have a nested map with following structure:

Map<String, Map<WorkType, List<CostLineItem>>>

I have to iterate over the map and get the list of CLObject. If the single entry in the list has identifier as null. I have to generate the unique identifier per EnumType. I am not sure how to do it with streams? Following iteration logic will make clear what i want to accomplish

for(Map.Entry<String, Map<WorkType, List<CostLineItem>>> cliByWorkTypeIterator: clisByWorkType.entrySet()) {
       Map<WorkType, List<CostLineItem>> entryValue = cliByWorkTypeIterator.getValue();
       for(Map.Entry<WorkType, List<CostLineItem>>cliListIterator : entryValue.entrySet()) {
           List<CostLineItem> clis = cliListIterator.getValue();
           //if any CLI settlementNumber is zero this means we are in standard upload
           //TODO: Should we use documentType here? Revisit this check while doing dispute file upload
           if(clis.get(0).getSettlementNumber() == null) {
               clis.forEach(f -> f.toBuilder().settlementNumber(UUID.randomUUID().toString()).build());
           }
       }
   } 

Nested loop makes the code bit boiler plate and dirty. Can someone help me with streams here?

Holger
  • 285,553
  • 42
  • 434
  • 765
user3681970
  • 1,201
  • 4
  • 19
  • 37
  • Streams won't really help since the problem is having the nested map in the first place. A map of maps of lists is a definite code smell, and no amount of streams will make that go away. – Kayaman Aug 02 '18 at 10:43
  • What's your expected result? – Mạnh Quyết Nguyễn Aug 02 '18 at 10:44
  • CostLineItem is grouped on the basis of workType and string (some identifier). If the CostLineItem.settlementNumber is null , i have to assign the settlementNumber to it. Please have a look at nested for loops. Apologies.. i know its very clumsy. I would request to see if we can simplify the loops to achieve the similar results. – user3681970 Aug 02 '18 at 10:47
  • 1
    You code becomes much simpler when you stop iterating over the `entrySet()` when you are just interested in the `values()`: `for( Map> map: clisByWorkType.values()) { for(List clis: map.values()) { your-if-statement } }`… – Holger Aug 02 '18 at 10:47

3 Answers3

2

You can use flatMap to iterate over all the List<CostLineItem> values of all the inner Maps.

clisByWorkType.values() // returns Collection<Map<WorkType, List<CostLineItem>>>
              .stream() // returns Stream<Map<WorkType, List<CostLineItem>>>
              .flatMap(v->v.values().stream()) // returns Stream<List<CostLineItem>>
              .filter(clis -> clis.get(0).getSettlementNumber() == null) // filters that Stream
              .forEach(clis -> {do whatever logic you need to perform on the List<CostLineItem>});
Eran
  • 387,369
  • 54
  • 702
  • 768
0
clisByWorkType.values()
              .stream()
              .flatMap(e -> e.values().stream())
              .filter(clis -> clis.get(0).getSettlementNumber() == null)
              .flatMap(Collection::stream)
              .forEach(f -> f.toBuilder().settlementNumber(UUID.randomUUID().toString()).build());
Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
0

The following is equivalent to your for-loop:

clisByWorkType.entrySet()
    .map(Map.Entry::getValue) // cliByWorkTypeIterator.getValue();
    .flatMap(m -> m.entrySet().stream())
    .map(Map.Entry::getValue)
    .map(CostLineItem::getValue)
    .filter(clis.get(0).getSettlementNumber() == null) //filter before flattening
    .flatMap(List::stream)
    .forEach(f -> f.toBuilder().settlementNumber(UUID.randomUUID().toString()).build());
ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • I must say you "guys" are awesome. Can you please explain bit more on like what CostLineItem::getValue is doing? I am afraid. I generally do not make the code change which i dont understand completely – user3681970 Aug 02 '18 at 10:54