Search code examples
javalambdajava-streamlinkedhashmapcollectors

How to replace HashMap with LinkedhashMap with streams?


Could you please help me to sort my Map?

I have the following structure and I want to assemble apps under customers in a LinkedHashMap in a reversed order by value.

[{
  "CUSTOMER_1": {
    "APP_1": "1"
  },
  "CUSTOMER_2": {
    "APP_1": "2",
    "APP_3": "7",
    "APP_2": "3"
  }
}]

I already have a lambda that assembles the map:

 final Map<String, Map<String, Long>> organizationApps = taskHandles.stream().map(this::mapPayload)
            .flatMap(e -> e.entrySet().stream())
            .collect(groupingBy(Map.Entry::getKey, of(HashMap::new,
                    (r, t) -> t.getValue().forEach((k, v) -> r.merge(k, v, Long::sum)),
                    (r1, r2) -> {
                        r2.forEach((v1, v2) -> r1.merge(v1, v2, Long::sum));
                        return r1;
                    }))
            );

What I need now is to replace organizationApps values that are represented by HashMap with a LinkedHashMap and put the values into LinkedHashMap in a proper order.

How can I do it in lambda?

UPD:

I can achieve the necessary output with the following piece of code:

    final Map<String, Map<String, Long>> organizationApps = taskHandles.stream().map(this::mapPayload)
            .flatMap(e -> e.entrySet().stream())
            .collect(groupingBy(Map.Entry::getKey, of(HashMap::new,
                    (r, t) -> t.getValue().forEach((k, v) -> r.merge(k, v, Long::sum)),
                    (r1, r2) -> {
                        r2.forEach((v1, v2) -> r1.merge(v1, v2, Long::sum));
                        return r1;
                    }))
            );

    organizationApps.forEach((key, value) -> {
        final Map<String, Long> sorted = value.entrySet()
                .stream()
                .sorted(reverseOrder(Map.Entry.comparingByValue()))
                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2,
                        LinkedHashMap::new));
        organizationApps.put(key, sorted);
    });

How can I do it in one lambda?


Solution

  • You can not avoid collecting the values into the map, before you can create the LinkedHashMap ordered by the resulting values, but you can specify the conversion step as finisher function of your custom collector:

    Map<String, Map<String, Long>> organizationApps = input.stream()
        .flatMap(e -> e.entrySet().stream())
        .collect(groupingBy(Map.Entry::getKey, Collector.of(HashMap::new,
            (r, t) -> t.getValue().forEach((k, v) -> r.merge(k, v, Long::sum)),
            (r1, r2) -> {
                r2.forEach((v1, v2) -> r1.merge(v1, v2, Long::sum));
                return r1;
            },
            (Map<String, Long> m) -> m.entrySet().stream()
                .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2,
                    LinkedHashMap::new))
        )));
    

    which would be equivalent to the following (Java 9+) code

    Map<String, Map<String, Long>> organizationApps = input.stream()
        .flatMap(m -> m.entrySet().stream())
        .collect(groupingBy(Map.Entry::getKey, collectingAndThen(flatMapping(
                t -> t.getValue().entrySet().stream(),
                toMap(Map.Entry::getKey, Map.Entry::getValue, Long::sum)),
            (Map<String, Long> m) -> m.entrySet().stream()
                .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2,
                    LinkedHashMap::new))
        )));
    

    At the end of this answer is a Java 8 compatible implementation of the flatMapping collector, but your custom collector does the job as well.