Java groupingBy and transform Strings

Viewed 427

I am receiving a List of Strings that I am attempting to partition with .groupingBy(), which will give me a Map<String, List<String>>, and transform the Strings that are going to end up in the Lists that are in the Map.

Examples I've found use Collectors.mapping which was what I thought to use, but all of the examples are using a method reference, like .mapping(SomeClass::getSomething, ... which, since I'm acting on Strings, can't be done.

I thought I could use a lambda as the mapping function but can't seem to get the syntax right, ending up with "Type mismatch: cannot convert..." errors.

This illustrates the kind of thing I'm trying to do —

import java.util.*;
import java.util.stream.Collectors;

class Main
{
  public static void main(String[] args)
  {
        List<String> inputs = Arrays.asList("D8", "S1", "S5", "D2", "D15", "S9");
        System.out.println(inputs);

        Map<String, List<String>>
        outputs = inputs.stream()
              .collect(
                  Collectors.groupingBy(s -> s.startsWith("D") ? "desperate" : "serious")
                  //,Collectors.mapping(n -> Integer.valueOf(n.replaceAll("[DS]", "")), Collectors.toList())
              )
              ;

        System.out.println(outputs);
  }
}

This creates the Map, grouping the input data as I expect, and produces the output
{desperate=[D8, D2, D15], serious=[S1, S5, S9]}

but what I want, as you may guess from the commented out line
Collectors.mapping(n -> Integer.valueOf(n.replaceAll("[DS]", "")), Collectors.toList())
is a Map<String, List<Integer>> containing the values extracted from the strings by removing the prefix.

My real use cases may transform one String to another String, or extract Integers from a more complex string, but this illustrates what I'm trying to do.

How can I use a lambda function to do the mapping when I'm groupingBy?
I'm also not clear on the use of the second argument toList() in Collectors.mapping from the examples I've read.

I have created a repl with this code.

2 Answers

Collectors.groupingBy() has a version that takes another collector for the values, for example:

@Test
public void streamsTest() {
    List<String> inputs = Arrays.asList("D8", "S1", "S5", "D2", "D15", "S9");
    System.out.println(inputs);

    Map<String, List<Integer>> outputs = inputs.stream()
            .collect(
                    Collectors.groupingBy(
                            s -> s.startsWith("D") ? "desperate" : "serious",
                            Collectors.mapping(
                                    s -> Integer.valueOf(s.substring(1)),
                                    Collectors.toList())));

    System.out.println(outputs);
}

This produces:

[D8, S1, S5, D2, D15, S9]
{desperate=[8, 2, 15], serious=[1, 5, 9]}

This is similar to Erik's answer. The difference is, transformation can happen outside grouping and can evolve independently.

Stream, map, group

  1. Stream the elements
  2. transform each element using the map (this can be delegated to a separate function)
  3. define collection strategy
  4. group based on collection strategy
  5. collect the elements per the need
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

class TransformAndGroupBy {

  public static void main(String[] args) {
    List<String> inputs = Arrays.asList("D8", "S1", "S5", "D2", "D15", "S9");

    System.out.println(inputs);

    Function<String, SimpleEntry<String, Integer>> transform = s -> s.startsWith("D")
      ? new SimpleEntry<>("desparate", Integer.parseInt(s.substring(1)))
      : new SimpleEntry<>("serious", Integer.parseInt(s.substring(1)));

    Map<String, List<Integer>> outputs = inputs.stream() // convert to stream
      .map(transform) // do the necessary transformation
      .collect(Collectors.groupingBy(e -> e.getKey(), Collectors.mapping(e -> e.getValue(), Collectors.toList()))); // group and collect as per the transformation
    System.out.println(outputs);
  }
}
Related