Combine two lists by ID

Tags: , , ,



I have two lists of objects with contents that look like this:

List A
id = 1, name = "alex", height = null, weight = 60
id = 2, name = "sara", height = null, weight = 50

List B
id = 1, name = "alex", height = 40, weight = null
id = 2, name = "sara", height = 30, weight = null

I can’t come up with anything good to “merge” these two lists overriding the nulls. The ideal end result would be:

id = 1, name = "alex", height = 40, weight = 60
id = 2, name = "sara", height = 30, weight = 50

I’ve been looking into Kotlin collections to see if there’s anything that would help me get closer to this but I have only been able to do this:

a.union(b).groupBy { it.id }

This returns a map with the ID as key and the full object as value… but I still need to merge the values into one object.

Is there anything that will do this that is not a very manual approach of checking nulls and picking the value in the other list?

Answer

I made the following (using “User” as a placeholder. Replace it with your own data structure):

List<User> ul1 = new ArrayList<>();
ul1.add(new User(1, "alex", null, 90));
ul1.add(new User(2, "sara", null, 50));

List<User> ul2 = new ArrayList<>();
ul2.add(new User(1, "alex", 40, null));
ul2.add(new User(2, "sara", 30, null));

List<User> rl = new ArrayList<>(Stream.concat(ul1.stream(), ul2.stream())
                                      .collect(Collectors.toMap(
                                              User::getId,
                                              e -> e,
                                              User::merge
                                      )).values());

for (User u : rl) {
    System.out.println(u.toString());
}

And this is the “User” class (Replace it with your own Data structure):

public static class User {
    private final int id;
    private final String name;
    private final Integer height;
    private final Integer weight;

    public User(int id, String name, Integer height, Integer weight) {
        this.id = id;
        this.name = name;
        this.height = height;
        this.weight = weight;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Integer getHeight() {
        return height;
    }

    public Integer getWeight() {
        return weight;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + ''' +
                ", height='" + height + ''' +
                ", weight='" + weight + ''' +
                '}';
    }

    public static User merge(User e1, User e2) {
        if (e1.id != e2.id)
            throw new IllegalArgumentException();

        return new User(
                e1.id,
                eval(e1.name, e2.name),
                eval(e1.height, e2.height),
                eval(e1.weight, e2.weight)
        );
    }

    private static <T> T eval(T v1, T v2) {
        return v1 != null ? v1 : v2;
    }
}

The result I got was:

User{id=1, name='alex', height='40', weight='90'}
User{id=2, name='sara', height='30', weight='50'}

So I guess it works



Source: stackoverflow