1

I have a List of objects List<FloatBalance> which can be represented by the following JSON. I want to get the most recent entry for each currency in the List and pass the pruned list back to the request.

    {
        "id": 1,
        "clientId": 50,
        "currency": "GBP",
        "floatType": "ChargeFloat",
        "floatCurrentValue": 300.00,
        "chargeReference": "PROD-1578486576_278",
        "cashOutReference": null,
        "cashInReference": null,
        "targetValue": 3000.00,
        "alertValue": 500.00,
        "maxValue": 4500.00,
        "createdDate": "2020-01-08T12:29:50Z"
    },
...
]

My first attempt is straight foward, I group them by currency with

Map<String, List<FloatBalance>> result = floats.stream()
                    .collect(groupingBy(FloatBalance::getCurrency));

and then I group them and order them by most recent date with

Map<String, List<FloatBalance>> floatGrouped = floats.stream()
                    .sorted(Comparator.comparing(FloatBalance::getCreatedDate).reversed())
                    .collect(groupingBy(FloatBalance::getCurrency));

Map Result

enter image description here

I can also reduce the list but I have only been able to carry out this operation on the complete list which returns only the most recent entry.

FloatBalance floatGrouped = floats.stream()
                    .sorted(Comparator.comparing(FloatBalance::getCreatedDate))
                    .reduce((first, second) -> second)
                    .orElse(null);

I have gone round in circles trying to figure this out, I've tried to .entrySet() to carry out reduce on each entry in the map but computer says no to everything I've tried.

Ideally I want to retrun the most recent entry for each currency as the system response.

Naman
  • 27,789
  • 26
  • 218
  • 353
chris loughnane
  • 2,648
  • 4
  • 33
  • 54

2 Answers2

3
List<FloatBalance> floatBalances = new ArrayList<>(floats.stream()
        .collect(Collectors.toMap(
                FloatBalance::getCurrency,
                Function.identity(),
                BinaryOperator.maxBy(Comparator.comparing(FloatBalance:: getCreatedDate))))
        .values());

First, this collects to a Map, where Key is currency and value is floatBalanace. When two currencies collide, there is a merger that will separate them - or tell which to pick. This is what BinaryOperator::maxBy. does. It basically says : "I will take one of the two floatBalancers where createdDate is the biggest". And, at the end, you simply take values - which will return a Collection<FloatBalance>.

Naman
  • 27,789
  • 26
  • 218
  • 353
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • @naman why did you edit to incorrect getCreatedAt ? getCreatedDate is correct, I guess – Eugene Kortov Jan 23 '20 at 16:21
  • What does `Function.identity()` do in this example? Everything else I can understand, but I'm not sure what purpose this serves. – Eoin Jan 23 '20 at 16:23
  • @Eoin chooses a `FloatBalance` itself as the value and then for the next value that appears with the same key, gets compared within the `maxBy` – Naman Jan 23 '20 at 16:24
  • @EugeneKortov that was just a typo, the edit was meant to improve the code readability and completeness primarily. – Naman Jan 23 '20 at 16:24
1

Are you looking for :

List<FloatBalance> result = floats.stream()
        .collect(Collectors.groupingBy(FloatBalance::getCurrency,
                Collectors.maxBy(Comparator.comparing(FloatBalance::getCreatedDate))))
        .values().stream()
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(Collectors.toList());
Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
  • I have a lot of reading to do, I tried everything you have in your working answer but obviously I had the actions in the wrong order. Thank you. – chris loughnane Jan 23 '20 at 16:01
  • You can `filter` values that are not present. Having said that, it's one thing I feel is overhead while using reduction, unless a significant identity element is defined. – Naman Jan 23 '20 at 16:02
  • @Naman you mean `.values().stream().filter(Optional::isPresent).map(Optional::get)` – Youcef LAIDANI Jan 23 '20 at 16:03
  • @YCF_L Yes, instead of allowing `null` values in the `List`. – Naman Jan 23 '20 at 16:04
  • every time you have to deal with an `Optional` from `Collectors.maxBy` what you are _really_ looking for is `BinaryOperator::max/minBy` – Eugene Jan 23 '20 at 16:07
  • 1
    @chrisloughnane this answer, while correct, does some operations that can be elided. – Eugene Jan 23 '20 at 16:13
  • 1
    @Eugene Indeed. I have also moved slowly into thinking in the direction of [replacing `groupingBy` and `reducing` by `toMap`](https://stackoverflow.com/questions/57041896/java-streams-replacing-groupingby-and-reducing-by-tomap). Not saying always, but deciding before implementing. – Naman Jan 23 '20 at 16:17