Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grouping elements of a list into sublists (maybe by using guava)

I want to group elements of a list. I'm currently doing it this way:

public static <E> List<List<E>> group(final List<E> list, final GroupFunction<E> groupFunction) {      List<List<E>> result = Lists.newArrayList();      for (final E element : list) {          boolean groupFound = false;         for (final List<E> group : result) {             if (groupFunction.sameGroup(element, group.get(0))) {                 group.add(element);                 groupFound = true;                 break;             }         }         if (! groupFound) {              List<E> newGroup = Lists.newArrayList();             newGroup.add(element);             result.add(newGroup);         }     }      return result; }  public interface GroupFunction<E> {     public boolean sameGroup(final E element1, final E element2); } 

Is there a better way to do this, preferably by using guava?

like image 585
Fabian Zeindl Avatar asked Dec 11 '11 11:12

Fabian Zeindl


1 Answers

Sure it is possible, and even easier with Guava :) Use Multimaps.index(Iterable, Function):

ImmutableListMultimap<E, E> indexed = Multimaps.index(list, groupFunction); 

If you give concrete use case it would be easier to show it in action.

Example from docs:

List<String> badGuys =    Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde"); Function<String, Integer> stringLengthFunction = ...; Multimap<Integer, String> index =    Multimaps.index(badGuys, stringLengthFunction); System.out.println(index); 

prints

{4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]} 

In your case if GroupFunction is defined as:

GroupFunction<String> groupFunction = new GroupFunction<String>() {   @Override public String sameGroup(final String s1, final String s2) {     return s1.length().equals(s2.length());   } } 

then it would translate to:

Function<String, Integer> stringLengthFunction = new Function<String, Integer>() {   @Override public Integer apply(final String s) {     return s.length();   } } 

which is possible stringLengthFunction implementation used in Guava's example.


Finally, in Java 8, whole snippet could be even simpler, as lambas and method references are concise enough to be inlined:

ImmutableListMultimap<E, E> indexed = Multimaps.index(list, String::length); 

For pure Java 8 (no Guava) example using Collector.groupingBy see Jeffrey Bosboom's answer, although there are few differences in that approach:

  • it doesn't return ImmutableListMultimap but rather Map with Collection values,
  • There are no guarantees on the type, mutability, serializability, or thread-safety of the Map returned (source),

  • it's a bit more verbose than Guava + method reference.

EDIT: If you don't care about indexed keys you can fetch grouped values:

List<List<E>> grouped = Lists.transform(indexed.keySet().asList(), new Function<E, List<E>>() {         @Override public List<E> apply(E key) {             return indexed.get(key);         } });  // or the same view, but with Java 8 lambdas: List<List<E>> grouped = Lists.transform(indexed.keySet().asList(), indexed::get); 

what gives you Lists<List<E>> view which contents can be easily copied to ArrayList or just used as is, as you wanted in first place. Also note that indexed.get(key) is ImmutableList.

// bonus: similar as above, but not a view, instead collecting to list using streams: List<List<E>> grouped = indexed.keySet().stream()     .map(indexed::get)     .collect(Collectors.toList()); 

EDIT 2: As Petr Gladkikh mentions in comment below, if Collection<List<E>> is enough, above example could be simpler:

Collection<List<E>> grouped = indexed.asMap().values(); 
like image 97
Xaerxess Avatar answered Oct 07 '22 17:10

Xaerxess