Thursday, 26 October 2023

Trying my first JavaFX Program

So, I've looked up some websites on JavaFX as I wanted to quickly throw something nice together for drawing Geometry.

You can find my little tool on the GitHub1.

It works fine for what I needed. I wanted to be able to quickly show the difference between geometries.

The tool uses locationtech for geometry stuff, and javafx for display stuff.

It accepts WKT representations of geometry. Currently, these do not provide any coordinate systems, so you're unlucky there.

I did get the error:

Error: JavaFX runtime components are missing, and are required to run this application

But I changed my app to pull JavaFX from Maven Central and that seems to fixed the issues. JavaFX is no longer included in any JDK.

References

[1] GitHub.com - geoviewer
https://github.com/maartenl/geoviewer
JavaFX - Getting Started with JavaFX
https://openjfx.io/openjfx-docs/#maven
developer.com - Using Graphics in JavaFX
https://www.developer.com/open-source/using-graphics-in-javafx/
JavaFX - Main website
https://openjfx.io/

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.