Sorting a map – Functional style programming – extending API

193. Sorting a map

Let’s assume that we have the following map:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  …
}
Map<Integer, Car> cars = Map.of(
  1, new Car(“Dacia”, “diesel”, 350),
  2, new Car(“Lexus”, “gasoline”, 350),
  3, new Car(“Chevrolet”, “electric”, 150),
  4, new Car(“Mercedes”, “gasoline”, 150),
  5, new Car(“Chevrolet”, “diesel”, 250),
  6, new Car(“Ford”, “electric”, 80),
  7, new Car(“Chevrolet”, “diesel”, 450),
  8, new Car(“Mercedes”, “electric”, 200),
  9, new Car(“Chevrolet”, “gasoline”, 350),
  10, new Car(“Lexus”, “diesel”, 300)
);

Next, we want to sort this map into a List<String> as follows:

If the horsepower values are different then sort in descending order by horsepower

If the horsepower values are equal then sort in ascending order by the map keys

The result, List<String>, should contain items of type key(horsepower)

Under these statements, sorting the cars map will result in:[7(450), 1(350), 2(350), 9(350), 10(300), 5(250), 8(200), 3(150), 4(150), 6(80)]Obviously, this problem requires a custom comparator. Having two map entries (c1, c2), we elaborate the following logic:

Check if c2’s horsepower is equal to c1’s horsepower If they are equal, then compare c1’s key with c2’key Otherwise, compare c2’s horsepower with c1’s horsepower Collect the result into a List

In code lines, this can be expressed as follows:

List<String> result = cars.entrySet().stream()
  .sorted((c1, c2) -> c2.getValue().getHorsepower()
        == c1.getValue().getHorsepower()
     ? c1.getKey().compareTo(c2.getKey())
     : Integer.valueOf(c2.getValue().getHorsepower())
        .compareTo(c1.getValue().getHorsepower()))
  .map(c -> c.getKey() + “(“
                       + c.getValue().getHorsepower() + “)”)
  .toList();

Or, if we rely on Map.Entry.comparingByValue(), comparingByKey(), and java.util.Comparator then we can write it as follows:

List<String> result = cars.entrySet().stream()
  .sorted(Entry.<Integer, Car>comparingByValue(
            Comparator.comparingInt(
              Car::getHorsepower).reversed())
  .thenComparing(Entry.comparingByKey()))
  .map(c -> c.getKey() + “(“
    + c.getValue().getHorsepower() + “)”)
  .toList();

This approach is more readable and expressive.

194. Filtering a map

Let’s consider the following map:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  …
}
Map<Integer, Car> cars = Map.of(
  1, new Car(“Dacia”, “diesel”, 100),
  …
  10, new Car(“Lexus”, “diesel”, 300)
);

In order to stream a map, we can start from Map’s entrySet(), values(), or keyset() followed by a stream() call. For instance, if we want to express a pipeline as Map -> Stream -> Filter -> String that returns a List<String> containing all the electric brands then we can rely on entrySet() as follows:

String electricBrands = cars.entrySet().stream()
  .filter(c -> “electric”.equals(c.getValue().getFuel()))
  .map(c -> c.getValue().getBrand())
  .collect(Collectors.joining(“, “));

But, as you can see, this stream pipeline doesn’t use the map’s keys. This means that we can better express it via values() instead of entrySet() as follows:

String electricBrands = cars.values().stream()
  .filter(c -> “electric”.equals(c.getFuel()))
  .map(c -> c.getBrand())
  .collect(Collectors.joining(“, “));

This is more readable and it clearly expresses its intention.Here is another example that you should be able to follow without further details:

Car newCar = new Car(“No name”, “gasoline”, 350);
String carsAsNewCar1 = cars.entrySet().stream()
 .filter(c -> (c.getValue().getFuel().equals(newCar.getFuel())
   && c.getValue().getHorsepower() == newCar.getHorsepower()))
 .map(map -> map.getValue().getBrand())
 .collect(Collectors.joining(“, “));
      
String carsAsNewCar2 = cars.values().stream()
 .filter(c -> (c.getFuel().equals(newCar.getFuel())
   && c.getHorsepower() == newCar.getHorsepower()))
 .map(map -> map.getBrand())
 .collect(Collectors.joining(“, “));