我想对列表的元素进行分组。我现在这样做:
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);
}
有没有更好的方法来做到这一点,最好是使用番石榴?
答案 0 :(得分:70)
确保它是可能的,甚至更容易使用番石榴:)使用Multimaps.index(Iterable, Function)
:
ImmutableListMultimap<E, E> indexed = Multimaps.index(list, groupFunction);
如果你给出具体的用例,就可以更容易地展示它。
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);
打印
{4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
在您的情况下,如果GroupFunction定义为:
GroupFunction<String> groupFunction = new GroupFunction<String>() {
@Override public String sameGroup(final String s1, final String s2) {
return s1.length().equals(s2.length());
}
}
然后它会转换为:
Function<String, Integer> stringLengthFunction = new Function<String, Integer>() {
@Override public Integer apply(final String s) {
return s.length();
}
}
这是Guava示例中使用的stringLengthFunction
实现。
最后,在Java 8中,整个代码段可能更简单,因为lambas和方法引用足够简洁,可以内联:
ImmutableListMultimap<E, E> indexed = Multimaps.index(list, String::length);
对于使用Collector.groupingBy
的纯Java 8(无番石榴)示例,请参阅Jeffrey Bosboom's answer,尽管该方法的差异很小:
ImmutableListMultimap
,而是返回Map
Collection
个值,无法保证返回的地图的类型,可变性,可串行性或线程安全性(source),
编辑:如果您不关心索引键,则可以获取分组值:
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);
是什么让您Lists<List<E>>
查看哪些内容可以轻松复制到ArrayList
或只是按原样使用,如您所希望的那样。另请注意,indexed.get(key)
为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());
编辑2 :正如Petr Gladkikh提到in comment below,如果Collection<List<E>>
足够,上面的示例可能更简单:
Collection<List<E>> grouped = indexed.asMap().values();
答案 1 :(得分:9)
Collector.groupingBy
提供与Guava&#39; Multimaps.index
相同的功能。这是Xaerxess's answer中的示例,重写为使用Java 8流:
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);
这将打印
{4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}
如果要以除创建列表之外的其他方式将值与相同的键组合,则可以使用带有另一个收集器的groupingBy
的重载。此示例使用分隔符连接字符串:
Map<Integer, String> index = badGuys.stream()
.collect(Collectors.groupingBy(String::length, Collectors.joining(" and ")));
这将打印
{4=Inky, 5=Pinky and Pinky and Clyde, 6=Blinky}
如果你有一个很大的列表或者你的分组功能很昂贵,你可以使用parallelStream
和并发收集器并行。
Map<Integer, List<String>> index = badGuys.parallelStream()
.collect(Collectors.groupingByConcurrent(String::length));
这可能会打印(订单不再具有确定性)
{4=[Inky], 5=[Pinky, Clyde, Pinky], 6=[Blinky]}
答案 2 :(得分:4)
最简单,最简单的方法是使用:Lamdaj grouping feature
上面的例子可以重写:
List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
Group group = group(badGuys, by(on(String.class).length)));
System.out.println(group.keySet());
答案 3 :(得分:1)
使用Java 8,Guava和一些辅助函数,您可以使用自定义Comparator实现分组
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);
}
实施例
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);
});