Home > Mobile >  How to get distinct count of a field and sum a field inside a list which has to be grouped by some f
How to get distinct count of a field and sum a field inside a list which has to be grouped by some f

Time:10-04

I need to find the unique name count, and summation of a field value inside a list

Below is my POJO:

@Getter
@Setter
public class Orders {
   private String dealId;
   private String fieldId;
   private String accountName;
   private List<Demands> demands;   //Demands contains multiple fields inside it, but I just need 
                                    //the 'amount' value mainly which is a BigDecimal.
}

List of Orders(ordersList) :

DealId  | FieldId  |  AccountName  |  Demand  |
1       |  11      |  ABC          |  100,100 |
1       |  11      |  ABC          |  200     |
1       |  11      |  PQR          |  300,100 |
1       |  12      |  ABC          |  100     |
2       |  21      |  ABC          |  200     |
2       |  21      |  PQR          |  300,500 |
2       |  21      |  XYZ          |  100     |
2       |  22      |  ABC          |  200,100 |
2       |  22      |  ABC          |  300     |

End Result :

DealId  |  FieldId  | AccountNameCount   |  DemandSum  |
1       |  11       |  2                 |   800       |
1       |  12       |  1                 |   100       | 
2       |  21       |  3                 |   1100      |
2       |  22       |  1                 |   600       | 

I need to take the end result in some POJO or collection in this format, where

AccountNameCount is the count of unique account names for a particular FieldId for a deal. DemandSum is the summation of the demand value for the particular FieldId under a DealId.

Note: Demand is a List inside the Orders model, so it may have multiple values, all those values for a particular FieldId under a DealId should be added.

For example : for DealId 1, with FieldId 11, there are 2 unique accountNames, and adding all demand values under that fieldid, and dealId = 800

I am trying this using Java 8 streams, have been able to group the list by dealId, and then the fieldId, but I am not sure how to calculate the unique account name on fieldId level, and the demand value summation, without affecting performance.

Map<String, Map<String, List<Orders>>> map = ordersList.stream()
    .collect(Collectors.groupingBy(Orders::getDealId, Collectors.groupingBy(Orders::getFieldId)));

The Collectors.reducing option is available to add inside the stream to add demands value, but as the Demands is a list inside Orders, doesnt apply here.

Collectors.reducing(BigDecimal.ZERO, Demands::getAmount, BigDecimal::add);

Any help is appreciated!

CodePudding user response:

First, let's start with grouping by dealId and fieldId

After that what we need is accountName distinct count and sum of demands, but those are available in List, which is inside Map In order to generate stream on map we will use entrySet,

This will gives us Map.Entry<dealId, Map<fieldIs, List>>

To access key, value from Map.Entry we use methods getKey and getValue

// group by dealId and fieldId
Map<String, Map<String, List<Orders>>> map = ordersList.stream()
    .collect(Collectors.groupingBy(Orders::getDealId, Collectors.groupingBy(Orders::getFieldId)));

List<OrdersSummary> result = map
    .entrySet()
    .stream()
    .map(Test::getOrdersSummaries)
    .flatMap(List::stream)
    .collect(Collectors.toList());

System.out.println(result);

  private static List<OrdersSummary> getOrdersSummaries(Map.Entry<String, Map<String, List<Orders>>> entry) {
    return entry.getValue()
        .entrySet()
        .stream()
        .map(e2 -> toOrdersSummary(entry.getKey(), e2))
        .collect(Collectors.toList());
  }

  private static OrdersSummary toOrdersSummary(String dealId, Map.Entry<String, List<Orders>> entry){
    long accountNameCount= entry.getValue().stream().map(Orders::getAccountName).distinct().count();
    BigDecimal demandSum = entry.getValue().stream().map(Orders::getDemands)
        .flatMap(List::stream)
        .reduce(BigDecimal.ZERO, BigDecimal::add);
    return new OrdersSummary(dealId, entry.getKey(), accountNameCount, demandSum);
  }

Full code Demo

CodePudding user response:

Something like this, perhaps?

    return orderList.stream()
            .collect(Collectors.groupingBy(o -> Arrays.asList(o.dealId, o.fieldId)))
            .entrySet().stream().map(e -> {
                var stats = new OrderStats();
                stats.dealId = e.getKey().get(0);
                stats.fieldId = e.getKey().get(1);
                stats.accountNameCount = e.getValue().stream()
                        .map(o -> o.accountName)
                        .distinct()
                        .count();
                stats.demandSum = e.getValue().stream()
                        .flatMap(o -> o.demands.stream())
                        .map(d -> d.amount)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                return stats;
            })
            .collect(Collectors.toList());

I use a List as key to avoid unnecessary nesting in the grouping Map. This works because List implements content-based equality.

CodePudding user response:

Although you can get result by java 8 stream API (maybe unreadable a bit). but there are another collector(teeing) provided after java 12 that helps you to write this in more readable way.

public static Collector teeing​ (Collector downstream1, 
                            Collector downstream2, 
                            BiFunction merger); 

solution: you need to create some helper objects such as Detail, Result

orders.stream()
      .collect(groupingBy(order -> Arrays.asList(order.getDealId(), order.getFieldId()),
        teeing(  
                 // unique account names for each group
                 mapping(Orders::getAccountName, Collectors.toSet()),
                 // sum all demands amount for each group
                  collectingAndThen(mapping(Orders::getDemands, Collectors.toList()),
                            demands -> demands.stream()
                                  .flatMap(List::stream)
                                  .map(Demands::getAmount)
                                  .reduce(BigDecimal.ZERO, BigDecimal::add)),
                  // create an object by previous results
                 (names, demandSum) -> new Detail(names.size(), demandSum) 
                    )
            )).entrySet().stream()
            .map(entry -> new Result(
                    entry.getKey().get(0),
                    entry.getKey().get(1),
                    entry.getValue().getCountByAccountName(),
                    entry.getValue().getDemandSum()))
            .collect(Collectors.toList()); 
  • Related