Reputation: 4294
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.
Upvotes: 6
Views: 11519
Reputation: 49
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));
Upvotes: 0
Reputation: 54639
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);
}
}
}
Upvotes: 2
Reputation: 298153
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.
Upvotes: 10
Reputation: 328608
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];
}
Upvotes: 8