Home > Enterprise >  Mapping nested objects using streams to pair all possible combinations in a List
Mapping nested objects using streams to pair all possible combinations in a List

Time:05-26

Let's say I've a nested object that contains a distinct pair of String-Integer objects (let's call each such pair as endpoint). There's a List called pair which contains exactly 2 endpoint objects. And another List called pairs that can contain any number of (say n) pair entries and I need to collect every unique pair of Strings along with their corresponding Integer values in a new object.

Consider the following classes for existing object:

    public class Endpoint {
        private String key;
        private Integer number;

        public Endpoint(String key, Integer number) {
            this.key = key;
            this.number = number;
        }

        // Getters, Setters and toString()
    }

    public class Pair {
        // It will have exactly 2 entries
        List<Endpoint> pair;

        public Pair(List<Endpoint> pair) {
            this.pair = pair;
        }

        // Getters, Setters and toString()
    }

Consider the following object (before transformation), where pairs is a List<Pair>. The entries in pairs can be in any order:

pairs: [
        pair: [{"p1",1000},{"p2",2000}],
        pair: [{"p1",3000},{"p3",4000}],
        pair: [{"p2",5000},{"p3",6000}],
        pair: [{"p1",2000},{"p2",3000}],
        pair: [{"p1",2001},{"p2",3001}],
        pair: [{"p1",4000},{"p3",5000}],
        pair: [{"p1",4001},{"p3",5001}],
        pair: [{"p2",6000},{"p3",7000}],
        pair: [{"p2",6001},{"p3",7001}]

]

Consider the following classes to populate the result:

    public class CustomEndpoint {
        private String key;
        // `numbers` can have any number of entries
        private List<Integer> numbers;

        public CustomEndpoint(String key, List<Integer> numbers) {
            this.key = key;
            this.numbers = numbers;
        }

        // Getters, Setters and toString()
    }

    public class CustomPair {
        // It will have exactly 2 entries
        List<CustomEndpoint> pair;

        public CustomPair(List<CustomEndpoint> pair) {
            this.pair = pair;
        }

        // Getters, Setters and toString()
    }

I need to collect it as follows:

custom-pairs: [
    custom-pair: {[{"p1", [1000,2000,2001]}, {"p2", [2000,3000,3001]}]},
    custom-pair: {[{"p1", [3000,4000,4001]}, {"p3", [4000,5000,5001]}]},
    custom-pair: {[{"p2", [5000,6000,6001]}, {"p3", [6000,7000,7001]}]}
]

where custom-pairs is a List<CustomPair>. The order of entries in the numbers List must be maintained as it were for the input pair. e.g Since 1000 in p1 was paired with 2000 in p2, if 1000 is the first entry in numbers List for p1 then 2000 must also be the first entry in numbers List for p2, for the combination where p1 and p2 are paired together.

How can I do this using Streams in Java?

CodePudding user response:

After the clarifications you've given about the classes and their structure I've updated my answer.

Basically, you could group the Pair elements in your list pairs by the keys of the two contained endpoints. To make the code more readable, I've added a method in your Pair class (getPairKeys) which returns the two endpoint's keys chained together, but you can avoid it by simply chaining the keys right in the stream if you prefer.

After grouping the Pair elements by their keys, you'll get a Map where each chained key maps to the n Pair instances with the same keys. At this point, you could stream the Maps entries, map each entry to a CustomPair, sort them by the first number of the first CustomEndpoint and ultimately collect the instances.

To make my solution work, I've assumed that the implementation of equals (and so also of hashCode) is present in both Pair and Endpoint. Also, as I said before, I've added a getPairKeys method within your Pair class to get a String mapping each Pair and a getFirstPairInitialNumber method in the CustomPair class to establish a sorting order among CustomPair. All the code I've just mentioned can be consulted in the link below. I preferred not to post it here to avoid a wall of text and focusing only the actual solution.

List<CustomPair> listRes = pairs.stream()
        .collect(Collectors.groupingBy(Pair::getPairKeys))  //Grouping the Pairs by the keys of the two EndPoints (key1:key2)
        .entrySet().stream()    //Streaming the entries of the map
        .map(entry -> {
            String key1 = null, key2 = null;

            //Lists for the values of the CustomEndpoint
            List<Integer> listValues1 = new ArrayList<>();
            List<Integer> listValues2 = new ArrayList<>();

            //Retrieving the keys and adding each pair's number to the respective lists
            for (Pair p : entry.getValue()) {
                key1 = p.getPair().get(0).getKey();
                key2 = p.getPair().get(1).getKey();

                listValues1.add(p.getPair().get(0).getNumber());
                listValues2.add(p.getPair().get(1).getNumber());
            }

            //Returning the new CustomPair in place of the two grouped by Pair
            return new CustomPair(new ArrayList<>(List.of(
                    new CustomEndpoint(key1, listValues1),
                    new CustomEndpoint(key2, listValues2))));
        })
        .sorted(Comparator.comparing(CustomPair::getFirstPairInitialNumber))    //Ordering the CustomPair by the beginning of their endpoint range
        .collect(Collectors.toList());  //Collecting the CustomPair

Here is an updated link to test the code

enter image description here

  • Related