Leo
Leo

Reputation: 553

Combining two lists to make a unique list (based on certain field logic)

I have a specific problem I am trying to solve, and need some assistance with.

I have a POJO with three fields:

This POJO can be populated from the DB or an external endpoint and will return a list of said POJO. Now we end up with two seperate lists with similar infomation. These lists will be unordered and varying in size (sometimes empty).

How can I combine these two lists to create a single unique list based on the ID?

My solution is very cumbersome and ugly to read - it involved two nested loops and 3 pairs of if-else statements. Is there a better way, more efficient way of doing it.

Here is some sample code:

import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

public class Scrap2 {

    public static void main(String[] args) {

        MyEntity one = new MyEntity("A", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 10);
        MyEntity two = new MyEntity("B", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 10);
        MyEntity three = new MyEntity("C", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 10);
        MyEntity four = new MyEntity("D", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 10);

        List<MyEntity> listOne = Arrays.asList(one, two, three, four);

        MyEntity aaa = new MyEntity("A", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 10);
        MyEntity bbb = new MyEntity("B", new Date(2020 - 1900, Calendar.OCTOBER, 25), 20);
        MyEntity ccc = new MyEntity("D", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 20);
        MyEntity ddd = new MyEntity("E", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 20);

        List<MyEntity> listTwo = Arrays.asList(aaa, bbb, ccc, ddd);

        for (MyEntity listItem : combineTwoLists(listOne, listTwo)) {
            System.out.println(listItem);
        }

    }

    private static List<MyEntity> combineTwoLists(List<MyEntity> listOne, List<MyEntity> listTwo) {
       
        return Arrays.asList(listOne.get(0), listTwo.get(1), listOne.get(2), listTwo.get(2), listTwo.get(3));
    }
}


class MyEntity {

    private final String id;
    private final Date date;
   private final Integer weightedNumber;

    public MyEntity(String id, Date date, Integer weightedNumber) {
        this.id = id;
        this.date = date;
        this.weightedNumber = weightedNumber;
    }

    @Override
    public String toString() {
        return "MyEntity{" +
                "id='" + id + '\'' +
                ", date=" + date +
                ", weightedNumber=" + weightedNumber +
                '}';
    }

    public String getId() {
        return id;
    }

    public Date getDate() {
        return date;
    }

    public Integer getWeightedNumber() {
        return weightedNumber;
    }
}

The output should be something like this (can be unordered/unsorted):

MyEntity{id='A', date=Fri Sep 25 2020, weightedNumber=10} 
MyEntity{id='B', date=Sun Oct 25 2020, weightedNumber=20} 
MyEntity{id='C', date=Fri Sep 25 2020, weightedNumber=10} 
MyEntity{id='D', date=Fri Sep 25 2020, weightedNumber=20} 
MyEntity{id='E', date=Fri Sep 25 2020, weightedNumber=20}

Upvotes: 5

Views: 1755

Answers (3)

fps
fps

Reputation: 34460

Here's another way to do this, without streams, yet functional:

Map<String, MyEntity> map = new LinkedHashMap<>();
Consumer<MyEntity> addAction = e -> map.merge(
        e.getId(), 
        e, 
        BinaryOperator.maxBy(Comparator.comparing(MyEntity::getDate)
                                       .thenComparing(MyEntity::getWeightedNumber)));
listOne.forEach(addAction);
listTwo.forEach(addAction);

List<MyEntity> result = new ArrayList<>(map.values());

This iterates both lists and executes the addAction consumer on each element. The addAction consumer consists of merging entities in a map, by applying the Map.merge method.

Upvotes: 1

Eklavya
Eklavya

Reputation: 18440

You can join both lists first then get the max by date then weightedNumber using Comparator with BinaryOperator.maxBy for the same id to create a map using Collectors.toMap.Then takes the value of the map in new ArrayList.

return new ArrayList<MyEntity>(
    Stream.concat(listOne.stream(), listTwo.stream())
          .collect(Collectors.toMap(MyEntity::getId, Function.identity(),
                   BinaryOperator.maxBy(Comparator.comparing(MyEntity::getDate)
                                          .thenComparing(MyEntity::getWeightedNumber))))
          .values());

Upvotes: 5

Nowhere Man
Nowhere Man

Reputation: 19545

The following approach using Collectors.groupingBy and Collectors.maxBy should work:

  1. Concatenate the streams of the two lists
  2. Group the list by ID using groupingBy
  3. And then group consequently by date and by weightedNumber using maxBy collector.
  4. Convert the resulting map of Map.Entry<String, Optional<MyEntity>> to List<MyEntity>
private static List<MyEntity> combineTwoLists(List<MyEntity> listOne, List<MyEntity> listTwo) {
    return Stream.concat(listOne.stream(), listTwo.stream())
                 .collect(Collectors.groupingBy(MyEntity::getId,
                          Collectors.maxBy(                                                     
                              Comparator.comparing(MyEntity::getDate)
                                        .thenComparing(MyEntity::getWeightedNumber))
                       )).entrySet() 
                 .stream()  // Stream<String, Optional<MyEntity>>
                 .map(Map.Entry::getValue) // take optional value 
                 .map(Optional::get)       // get from optional
                 .collect(Collectors.toList());
}

Upvotes: 2

Related Questions