Search code examples
javadictionaryjava-8java-streamjava-9

Split the keys of a Map and create a new Map with the new keys and the associated values


I have a List of Objects that each one of them contains a X, Y value (Doubles) and a name String. As I have multiple entries of the same names that correspond to coordinates, I want to link every name to all the coordinates, which I managed to do by creating a Map of type <String, List<Object>>

Example of output:

One             [[0.1,0.1,0.1],[0.2,0.3,0.4]]
One,Two         [[0.1,0.1]    ,[0.4,0.5]]         
One,Two,Three   [[0.1,0.1]    ,[0.6,0.7]]

What I want to do now is to split the names where there is a comma , and achieve the following result:

One   [[0.1,0.1,0.1,0.1,0.1,0.1],[0.2,0.3,0.4,0.5,0.6,0.7]]
Two   [[0.01,0.01,0.01,0.01]    ,[0.4,0.5,0.6,0.7]]                    
Three [[0.01,0.01]              ,[0.6,0.7]]

My code that WORKS is:

Map<String, List<Coordinates>> newList = coordinateList.stream()
                .collect(Collectors.groupingBy(Coordinates::getName));

which creates the first Map.

In order to do the second part I have tried the following along with various combinations but with no luck:

newList.entrySet().stream()
        .flatMap(entry -> Arrays.stream(entry.getKey().split(","))
                .map(s -> new AbstractMap.SimpleEntry<>(s, entry.getValue())))
        .collect(Collectors.groupingBy(Map.Entry::getKey, 
                Collectors.flatMapping(entry -> entry.getValue().stream(), Collectors.toList())));

The error that I get is:

The method flatMapping((<no type> entry) -> {}, Collectors.toList()) is undefined for the type Collectors

Note that by changing flatMapping to mapping works but does not solve the problem.

Another variation of the code that I tried is:

Map<String, List<Object>> collect1 = map.entrySet().stream()
        .flatMap(entry -> Arrays.stream(entry.getKey().split(","))
                .map(s -> new AbstractMap.SimpleEntry<>(s, entry.getValue())))
        .collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())))
        .entrySet().stream()
        .flatMap(entry -> entry.getValue().stream()
                .flatMap(Collection::stream)
                .map(o -> new AbstractMap.SimpleEntry<>(entry.getKey(), o)))
        .collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));

Still no luck and I regularly get the error Cannot infer type argument(s) for <R> flatMap(Function<? super T,? extends Stream<? extends R>>)

Any ideas?


Solution

  • It seems you are not using java9. If you are using Java9 the former approach with Collectors.flatMapping would work. But I still don't see any point in creating a new Map.Entry here. Take a look at this solution.

    private static final Pattern COMMA_DELIMITER = Pattern.compile(",\\s*");
    
    Map<String, Set<Coordinate>> nameToCoordinates = coordinates.stream()
        .flatMap(
            c -> COMMA_DELIMITER.splitAsStream(c.getName())
                .map(n -> new Coordinate(n, c.getX(), c.getY())))
        .collect(Collectors.groupingBy(Coordinate::getName, Collectors.toSet()));
    

    For each coordinate, grab it's name and split it using the , delimiter and then for each such name token create a new Coordinate with that new name and x, y coordinates. Then merely collect it using the groupingBy collector. Since you need to keep only distinct Coordinate values, you have to override equals and hashCode methods in the Coordinate class. Here's how it looks.

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + Double.hashCode(x);
        result = 31 * result + Double.hashCode(y);
        return result;
    }
    
    @Override
    public boolean equals(Object obj) {
        return obj instanceof Coordinate && ((Coordinate) obj).getX() == x 
            && ((Coordinate) obj).getY() == y
            && ((Coordinate) obj).getName().equals(name);
    }
    

    For some reason if you really need to use the intermediary map created at the first step, then your solution would be like this.

    newList.values().stream().flatMap(Collection::stream)
        .flatMap(
            c -> COMMA_DELIMITER.splitAsStream(c.getName())
                .map(n -> new Coordinate(n, c.getX(), c.getY())))
        .collect(Collectors.groupingBy(Coordinate::getName, Collectors.toSet()));
    

    Here's the output.

    {One=[[name=One, x=0.1, y=0.2], [name=One, x=0.1, y=0.4], [name=One, x=0.1, y=0.3], [name=One, x=0.1, y=0.5], [name=One, x=0.1, y=0.6], [name=One, x=0.1, y=0.7]], Two=[[name=Two, x=0.1, y=0.4], [name=Two, x=0.1, y=0.5], [name=Two, x=0.1, y=0.6], [name=Two, x=0.1, y=0.7]], Three=[[name=Three, x=0.1, y=0.6], [name=Three, x=0.1, y=0.7]]}