flyingfox
flyingfox

Reputation: 13506

Java-Stream - Split, group and map the data from a String using a single Stream

I have a string as below:

String data = "010$$fengtai,010$$chaoyang,010$$haidain,027$$wuchang,027$$hongshan,027$$caidan,021$$changnin,021$$xuhui,020$$tianhe";

And I want to convert it into a map of type Map<String,List<String>> (like shown below) by performing the following steps:

Example of the resulting Map:

{ 
  027=[wuchang, hongshan, caidan],
  020=[tianhe],
  010=[fengtai, chaoyang, haidain],
  021=[changnin, xuhui]
}

I've used a traditional way of achieving this:

private Map<String, List<String>> parseParametersByIterate(String sensors) {
    List<String[]> dataList = Arrays.stream(sensors.split(","))
        .map(s -> s.split("\\$\\$"))
        .collect(Collectors.toList());
    
    Map<String, List<String>> resultMap = new HashMap<>();
    for (String[] d : dataList) {
        List<String> list = resultMap.get(d[0]);
        if (list == null) {
            list = new ArrayList<>();
            list.add(d[1]);
            resultMap.put(d[0], list);
        } else {
            list.add(d[1]);
        }
    }
    return resultMap;
}

But it seems more complicated and verbose. Thus, I want to implement this logic one-liner (i.e. a single stream statement).

What I have tried so far is below

Map<String, List<String>> result =  Arrays.stream(data.split(","))
    .collect(Collectors.groupingBy(s -> s.split("\\$\\$")[0]));

But the output doesn't match the one I want to have. How can I generate a Map structured as described above?

Upvotes: 2

Views: 1099

Answers (2)

Alexander Ivanchenko
Alexander Ivanchenko

Reputation: 28978

You can extract the required information from the string without allocating intermediate arrays and by iterating over the string only once and also employing the regex engine only once instead of doing multiple String.split() calls and splitting first by coma , then by $$. We can get all the needed data in one go.

Since you're already using regular expressions (because interpreting "\\s\\s" requires utilizing the regex engine), it would be wise to leverage them to the full power.

Matcher.results()

We can define the following Pattern that captures the pieces of you're interested in:

public static final Pattern DATA = // use the proper name to describe a piece of information (like "027$$hongshan") that the pattern captures
    Pattern.compile("(\\d+)\\$\\$(\\w+)");

Using this pattern, we can produce an instance of Matcher and apply Java 9 method Matcher.result(), which produces a stream of MatchResults.

MatchResult is an object encapsulating information about the captured sequence of characters. We can access the groups using method MatchResult.group().

private static Map<String, List<String>> parseParametersByIterate(String sensors) {
    
    return DATA.matcher(sensors).results() // Stream<MatchResult>
        .collect(Collectors.groupingBy(
            matchResult -> matchResult.group(1),     // extracting "027" from "027$$hongshan"
            Collectors.mapping(
                matchResult -> matchResult.group(2), // extracting "hongshan" from "027$$hongshan"
                Collectors.toList())
        ));
}

main()

public static void main(String[] args) {
    String data = "010$$fengtai,010$$chaoyang,010$$haidain,027$$wuchang,027$$hongshan,027$$caidan,021$$changnin,021$$xuhui,020$$tianhe";
    
    parseParametersByIterate(data)
        .forEach((k, v) -> System.out.println(k + " -> " + v));
}

Output:

027 -> [wuchang, hongshan, caidan]
020 -> [tianhe]
021 -> [changnin, xuhui]
010 -> [fengtai, chaoyang, haidain]

Upvotes: 1

luk2302
luk2302

Reputation: 57114

You simply need to map the values of the mapping. You can do that by specifying a second argument to Collectors.groupingBy:

Collectors.groupingBy(s -> s.split("\\$\\$")[0],
    Collectors.mapping(s -> s.split("\\$\\$")[1],
        Collectors.toList()
))

Instead of then splitting twice, you can split first and group afterwards:

Arrays.stream(data.split(","))
    .map(s -> s.split("\\$\\$"))
    .collect(Collectors.groupingBy(s -> s[0],
        Collectors.mapping(s -> s[1],Collectors.toList())
    ));

Which now outputs:

{027=[wuchang, hongshan, caidan], 020=[tianhe], 021=[changnin, xuhui], 010=[fengtai, chaoyang, haidain]}

Upvotes: 4

Related Questions