Je souhaite regrouper des éléments d'une liste. Je le fais actuellement de cette façon:
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);
}
Y a-t-il une meilleure façon de le faire, de préférence en utilisant de la goyave?
Bien sûr, c'est possible, et encore plus facile avec Guava :) Utilisez Multimaps.index(Iterable, Function)
:
ImmutableListMultimap<E, E> indexed = Multimaps.index(list, groupFunction);
Si vous donnez un cas d'utilisation concret, il serait plus facile de le montrer en action.
Exemple de documents:
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);
impressions
{4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
Dans votre cas, si GroupFunction est défini comme:
GroupFunction<String> groupFunction = new GroupFunction<String>() {
@Override public String sameGroup(final String s1, final String s2) {
return s1.length().equals(s2.length());
}
}
alors cela se traduirait par:
Function<String, Integer> stringLengthFunction = new Function<String, Integer>() {
@Override public Integer apply(final String s) {
return s.length();
}
}
qui est l'implémentation stringLengthFunction
possible utilisée dans l'exemple de Guava.
Enfin, dans Java 8, l'extrait entier pourrait être encore plus simple, car les références lambas et de méthode sont suffisamment concises pour être intégrées:
ImmutableListMultimap<E, E> indexed = Multimaps.index(list, String::length);
Pour un exemple Java 8 (pas de goyave) utilisant Collector.groupingBy
voir réponse de Jeffrey Bosboom , bien qu'il y ait peu de différences dans cette approche:
ImmutableListMultimap
mais plutôt Map
avec des valeurs de Collection
,Il n'y a aucune garantie sur le type, la mutabilité, la sérialisation ou la sécurité des threads de la carte retournée ( source ),
[~ # ~] modifier [~ # ~] : Si vous ne vous souciez pas des clés indexées, vous pouvez récupérer des valeurs groupées:
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);
ce qui vous donne Lists<List<E>>
voir quel contenu peut être facilement copié dans ArrayList
ou simplement utilisé tel quel, comme vous le vouliez en premier lieu. Notez également que indexed.get(key)
est 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 : Comme Petr Gladkikh le mentionne dans le commentaire ci-dessous , si Collection<List<E>>
Est suffisant, l'exemple ci-dessus pourrait être plus simple:
Collection<List<E>> grouped = indexed.asMap().values();
Collector.groupingBy
de la bibliothèque Java 8 streams fournit les mêmes fonctionnalités que Multimaps.index
De Guava. Voici l'exemple de Xaerxess's réponse , réécrit pour utiliser Java 8 flux:
List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
Map<Integer, List<String>> index = badGuys.stream()
.collect(Collectors.groupingBy(String::length));
System.out.println(index);
Cela imprimera
{4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}
Si vous souhaitez combiner les valeurs avec la même clé d'une autre manière que la création d'une liste, vous pouvez utiliser la surcharge de groupingBy
qui prend un autre collecteur. Cet exemple concatène les chaînes avec un délimiteur:
Map<Integer, String> index = badGuys.stream()
.collect(Collectors.groupingBy(String::length, Collectors.joining(" and ")));
Cela imprimera
{4=Inky, 5=Pinky and Pinky and Clyde, 6=Blinky}
Si vous avez une grande liste ou que votre fonction de regroupement est coûteuse, vous pouvez aller en parallèle en utilisant parallelStream
et un collecteur simultané.
Map<Integer, List<String>> index = badGuys.parallelStream()
.collect(Collectors.groupingByConcurrent(String::length));
Cela peut s'imprimer (la commande n'est plus déterministe)
{4=[Inky], 5=[Pinky, Clyde, Pinky], 6=[Blinky]}
La manière la plus simple et la plus simple serait d'utiliser: fonction de regroupement Lamdaj
L'exemple ci-dessus peut être réécrit:
List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
Group group = group(badGuys, by(on(String.class).length)));
System.out.println(group.keySet());
Avec Java 8, Guava et quelques fonctions d'assistance, vous pouvez implémenter le regroupement avec un comparateur personnalisé
public static <T> Map<T, List<T>> group(List<T> items, Comparator<T> comparator)
{
ListMultimap<T, T> blocks = LinkedListMultimap.create();
if (!ArrayUtils.isNullOrEmpty(items))
{
T currentItem = null;
for (T item : items)
{
if (currentItem == null || comparator.compare(currentItem, item) != 0)
{
currentItem = item;
}
blocks.put(currentItem, ObjectUtils.clone(item));
}
}
return Multimaps.asMap(blocks);
}
Exemple
Comparator<SportExercise> comparator = Comparator.comparingInt(SportExercise::getEstimatedTime)
.thenComparingInt(SportExercise::getActiveTime).thenComparingInt(SportExercise::getIntervalCount)
.thenComparingLong(SportExercise::getExerciseId);
Map<SportExercise, List<SportExercise>> blocks = group(sportWorkout.getTrainingExercises(), comparator);
blocks.forEach((key, values) -> {
System.out.println(key);
System.out.println(values);
});