Showing posts with label maps. Show all posts
Showing posts with label maps. Show all posts

Thursday, 19 October 2023

Combining two collections using streams

So, I have two collections, and I wish to combine both lists somehow.

Requirements are thusly:

  • I have a collection newPersons
  • I have a collection oldPersons
  • I want a collection containing all the oldPersons but replaced (*some of) the oldPersons with the newPersons (based on id).
public record Person(Long id, String name, String surname) {
}

Using the record class above.

Solution 1.

Create a new list based on oldPersons, replace items in this new list with equivalent items in newPersons.

This solution leaves much to be desired. It's confusing and error prone.

I was looking for something better.

  public List<Person> mergeMetadata(List<Person> newPersons,
      List<Person> oldPersons) {
    var result = new ArrayList<>(oldPersons);
    newPersons.forEach(newPerson -> {
      result.stream()
          .filter(person -> person.id().equals(newPerson.id()))
          .findFirst()
          .ifPresent(result::remove);
      result.add(newPerson);
    });
    return result;
  }

Solution 2.

Sometimes getting back to our roots using for-loops can help readability.

We could try it the other way around, see if that helps.

This time we create a new list based on newPersons and add an oldPerson if it's not already in the list.

This seems a little more clear.

  public List<Person> mergeMetadata(List<Person> newPersons,
      List<Person> oldPersons) {
    var result = new ArrayList<>(newPersons);
    for (Person oldPerson : oldPersons) {
      if (result.stream().noneMatch(x -> x.id().equals(oldPerson.id()))) {
        result.add(oldPerson);
      }
    }
    return result;
  }

Solution 3.

Merge two collections into one list, by using a map.

  public List<Person> mergeMetadata(List<Person> newPersons,
      List<Person> oldPersons) {
    Map<Long, Person> result = Stream
        .concat(newPersons.stream(), oldPersons.stream())
        .collect(Collectors.toMap(Person::id, Function.identity(), (l, r) -> l));
    return new ArrayList<>(result.values());
  }

Although this solution seems to be the shortest (in code), using a Map function can be a bit daunting (it was for me) because of inherent complexity in the method call for creating it.

Still, perhaps it's just me and my inexperience with combining maps and streams.

I don't know if there's an even better way. Will keep an eye out.

Thursday, 25 November 2021

Replacing many if statements

I recently was asked by a colleague how to get rid of a staggering amount of if statements.

They all seem to have remarkable similarities, as is obvious from the code below.

So I initially thought about changing it to:

Of course it is also possible to create a static Map, that contains Functions. That way, the ValidationMessage instances are only created once. It depends on your needs, really.

In the end, I don't know if the improvement is really that huge.

It's closer together at least, but I feel it could be improved.