endian
endian

Reputation: 4294

Java 8 groupingby with custom key

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

Answers (4)

Abhishek
Abhishek

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

Marco13
Marco13

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

Holger
Holger

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

assylias
assylias

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

Related Questions