6

I have a series of input Strings in the following format:

typeA:code1,
typeA:code2,
typeA:code3,
typeB:code4,
typeB:code5,
typeB:code6,
typeC:code7,
...

and I need to get a Map<String, List<String>> with the following structure:

typeA, [code1, code2, code3]
typeB, [code4, code5, code6]
typeC, [code7, code8, ...]

The catch is that to generate each type I need to call a function like this one on each input String:

public static String getType(String code)
{
  return code.split(":")[0];  // yes this is horrible code, it's just for the example, honestly
}

I'm pretty confident that Streams and Collectors can do this, but I'm struggling to get the right incantation of spells to make it happen.

4 Answers 4

10

The code becomes simple if you consider what you have omitted, that you need the second part of the split string as well:

Map<String, List<String>> result = Stream.of(input).map(s->s.split(":", 2))
    .collect(groupingBy(a->a[0], mapping(a->a[1], toList())));

(assuming you have a import static java.util.stream.Collectors.*;)

There is nothing wrong with splitting a String into an array, the implementation has even a “fast-path” for the common case you are splitting using a single simple character instead of a complicate regular expression.

Sign up to request clarification or add additional context in comments.

Comments

8

Here is one way to do it (assuming the class is named A):

Map<String, List<String>> result = Stream.of(input)
                          .collect(groupingBy(A::getType, mapping(A::getValue, toList())));

If you want the output sorted you can use a TreeMap instead of the default HashMap:

.collect(groupingBy(A::getType, TreeMap::new, mapping(A::getValue, toList())));

Full example:

public static void main(String[] args) {
  String input[] = ("typeA:code1," +
                "typeA:code2," +
                "typeA:code3," +
                "typeB:code4," +
                "typeB:code5," +
                "typeB:code6," +
                "typeC:code7").split(",");

  Map<String, List<String>> result = Stream.of(input)
                    .collect(groupingBy(A::getType, mapping(A::getValue, toList())));
  System.out.println(result);
}

public static String getType(String code) {
  return code.split(":")[0];
}
public static String getValue(String code) {
  return code.split(":")[1];
}

1 Comment

Thanks, this is pretty close to what I came up with. I realized that the type of the first parameter in groupingBy is a func, and that I could use my getType function there.
2

Although I was too slow, here is an MCVE showing how this can be solved with Collectors#groupingBy.

There are obviously different options for defining the "classifier" and "mapper". Here I'm simply using String#substring to find the part before and after the ":".

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class GroupingBySubstringsTest
{
    public static void main(String[] args)
    {
        List<String> strings = new ArrayList<String>();
        strings.add("typeA:code1");
        strings.add("typeA:code2");
        strings.add("typeA:code3");
        strings.add("typeB:code4");
        strings.add("typeB:code5");
        strings.add("typeB:code6");
        strings.add("typeC:code7");

        Map<String, List<String>> result = strings.stream().collect(
            groupingBy(s -> s.substring(0, s.indexOf(":")), 
                mapping(s -> s.substring(s.indexOf(":")+1), toList())));

        for (Entry<String, List<String>> entry : result.entrySet())
        {
            System.out.println(entry);
        }
    }
}

Comments

0

Consider Student Class:

public Student(String name, Address add) {
    super();
    this.name = name;
    this.add = add;
}
private String name;
private Address add;
public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
public Address getAdd() {
    return add;
}
public void setAdd(Address add) {
    this.add = add;
}

}

And Address class:

class Address{

public Address(String city, String state) {
    super();
    this.city = city;
    State = state;
}
private String city;
private String State;
public String getCity() {
    return city;
}
public void setCity(String city) {
    this.city = city;
}
public String getState() {
    return State;
}
public void setState(String state) {
    State = state;
}

}

Now, if I want to group Student based on City & State which is part of Address class:

Student s1 = new Student("Rohit", new Address("Mumbai", "MH"));
    Student s2 = new Student("Sudeep", new Address("Mumbai", "MH"));
    Student s3 = new Student("Amit", new Address("Pune", "MH"));
    Student s4 = new Student("Rahul", new Address("Blore", "KR"));
    Student s5 = new Student("Vishal", new Address("Blore", "KR"));
    
    List<Student> st =  Arrays.asList(s1,s2,s3,s4,s5);
    
    Function<Student, String> compositeKey = studRecord -> studRecord.getAdd().getCity()+":"+ studRecord.getAdd().getState();
    
    Map<String, List<Student>> groupedStudent =  st.stream()
            .collect(Collectors.groupingBy(compositeKey));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.