0

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?

Pasha
  • 1,768
  • 6
  • 22
  • 43
  • Here is a related question: https://stackoverflow.com/questions/29721095/converting-a-collection-to-map-by-sorting-it-using-java-8-streams. The important part is that you need to sort your stream appropriately before collecting. – Ryan Cogswell Oct 29 '18 at 01:41
  • Hi Ryan! I think I need to sort it after I assemble the map because initially I can have 2 lists with maps with duplicated keys and I merge the maps by summing the values – Pasha Oct 29 '18 at 01:47
  • I have following question: Why do you need to do this in one lambda? Even if this possible - you will probably have really big and ugly peace of code. And I have hard times understanding what's going on in you code already. IMHO lambdas are not always best solution for this kind of problems. As stated [here](https://www.baeldung.com/java-8-lambda-expressions-tips) - **lambdas should be an expression, not a narrative. Despite its concise syntax, lambdas should precisely express the functionality they provide.** – rxn1d Oct 29 '18 at 01:50
  • 1) Because I want to look at the result and compare it with the following 2) I'd like to acquire more knowledge about lambdas – Pasha Oct 29 '18 at 01:54
  • There may be some different ways to organize the operations, but in the end you need the result of the merge/sum for apps before you can do the sorting so I think it will always entail (at least) two logical steps. – Ryan Cogswell Oct 29 '18 at 02:00
  • A basic question to clear few doubts, please specify what is `taskHandles` and what are those static imports that you've used. Would make the question much clearer. – Naman Oct 29 '18 at 02:08
  • taskHandles is a List, Map> mapPayload is a function that extracts payload from each task and I have static imports of methods java.util.stream.Collector... of(), groupingBy(), toMap() and java.util.Collections.reverseOrder; – Pasha Oct 29 '18 at 02:19
  • Ryan, so that is my question - where and how should I insert sorting step – Pasha Oct 29 '18 at 02:20
  • You’ve already done it in your update as two separate steps (though really it’s much more than two since each chained method call on the stream is a separate step). – Ryan Cogswell Oct 29 '18 at 11:32
  • Ryan, and I’d like to know hot to do it in one stream – Pasha Oct 29 '18 at 11:42
  • Yes, I understand that, but the point of my earlier comment is that I don’t think that is possible. You need two passes through your data since you need the full result of the first stream before it is possible to do the sorting work of the second stream. – Ryan Cogswell Oct 29 '18 at 11:48
  • Maybe somehow in a merge function in a Collector? – Pasha Oct 29 '18 at 11:49
  • Proper order is reversed order. I don’t create LinkedHashMap because on this step I merge values by duplicated keys from different maps. So first of all I should sum up values by duplicated keys and only after sort it – Pasha Oct 30 '18 at 09:14
  • Ok, the problem is that you asked a Java question, but only provided some kind of JSON to describe your input. As far as recognizable, you’re calling `stream()` on some `taskHandles` of unknown type, map the elements by calling `this.mapPayload(…)`, to convert these element of unknown type to something other of unknown type, followed by a complex `collect` operation, which we are supposed to understand without any clue about the actual input. There might be a solution, but not when we have to do reverse engineering of your code first. – Holger Oct 30 '18 at 09:21
  • After I do mapPayload just return Map>. So after this step I have a list of Maps – Pasha Oct 30 '18 at 09:23
  • You mean, a stream of maps? – Holger Oct 30 '18 at 09:26
  • Yes, exactly. JSON represents this intermediate step – Pasha Oct 30 '18 at 09:31

1 Answers1

2

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.

Holger
  • 285,553
  • 42
  • 434
  • 765