Grouping elements of a list into sublists (maybe by using guava)
function, grouping, guava, java, list
Solution
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();
Problem
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?