Home > Enterprise >  Best solution to collapse list of object if necessary
Best solution to collapse list of object if necessary

Time:09-23

today i am trying to collapse a list of objects if they have certain characteristics. My idea is to definitely use stream().

Imagine an object made like this:

public class ObjectA {
     private Integer priority;
     private LocalDate date;
     private String string;
}

and I have a:

List<ObjectA> objects

I would like to collapse the objects in this list for objects that have the same date. If they have dates that fall within a specific time window, then only the one with the highest priority remains, if the priority are equal so the one with a specific constant in the attribute "string", the other is eliminated from the list.

Example, this:

 window = 1; // days
 constantString = "aab";
 [{"priority":1, "date":"2021-09-22", "aaa"},  
 {"priority":1, "date":"2021-09-23", "aab"}, 
 {"priority":1, "date":"2027-10-09", "bbb"}]

Became this:

[{"priority":2, "date":"2021-09-23", "aab"}, 
 {"priority":1, "date":"2027-10-09", "bbb"}]

What do you think is the best solution in terms of efficiency, considering that this list could have many elements.

Thanks a lot for your help!

CodePudding user response:

You can do this in O(n) time, with n the number of elements in the list.

Convert the list to a HashMap with keys as the dates, values as the priority. Then when you hit an element with the same date as an element you've recorded, you can replace the value for that date key if the new value has a higher priority.

Then, you can convert the HashMap back into a list if you want.

CodePudding user response:

Here is how you could do this using streams:

Collection<ObjectA> collapsed = values.stream()
    .collect(Collectors.toMap(
        ObjectA::getDate,
        Function.identity(),
        (a, b) -> a.priority < b.priority ? b : a))
    .values();

The Collectors.toMap method takes the following arguments:

Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction

The keyMapper determines the map key, we use the date from ObjectA. The valueMapper determines the map value, this is the ObjectA instance itself (identity function). The mergFunction determines what happens when two values have the same key. We provide here a BinaryOperator which chooses the element with the highest priority.

EDIT: Whilst I was answering this, you completely changed the specs of the question!

CodePudding user response:

I am not sure if it is an optimal solution, however, if you insist on using Stream API, you can achieve it with a bunch of collectors:

  • groupBy to group by date into Map<String, List<ObjectA>.
  • maxBy to reduce into a single object from List<ObjectA> with the highest priority (hence Comparator).
  • Since the collectors above result in ugly Map<LocalDate, Optional<ObjectA>> use collectingAndThen to extract what you need back into List<ObjectA> using another Stream.
final String specificConstant = "ccc";

List<ObjectA> filtered = list.stream().collect(Collectors.collectingAndThen(
    Collectors.groupingBy(
            ObjectA::getDate,
            Collectors.maxBy(Comparator
                .comparing(ObjectA::getPriority)
                .thenComparing(objA -> specificConstant.equals(objA.getString())))),
    map -> map.values().stream()
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(Collectors.toList())));

A full example with a result printed out into the console:

List<ObjectA> list = List.of(
    new ObjectA(1, LocalDate.parse("2021-09-22"), "aaa"),
    new ObjectA(2, LocalDate.parse("2021-09-22"), "aaa"),
    new ObjectA(2, LocalDate.parse("2021-09-22"), "ccc"),
    new ObjectA(1, LocalDate.parse("2021-09-09"), "bbb")
    );

final String specificConstant = "ccc";
        
List<ObjectA> filtered = list.stream().collect(Collectors.collectingAndThen(
    Collectors.groupingBy(
            ObjectA::getDate,
            Collectors.maxBy(Comparator
                .comparing(ObjectA::getPriority)
                .thenComparing(c -> specificConstant.equals(c.getConstant())))),
    map -> map.values().stream()
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(Collectors.toList())));
[ObjectA(priority=1, date=2021-09-09, constant=bbb), ObjectA(priority=2, date=2021-09-22, constant=ccc)]
  • Related