将列表的元素分组为子列表(可能使用番石榴)

Posted

技术标签:

【中文标题】将列表的元素分组为子列表(可能使用番石榴)【英文标题】:Grouping elements of a list into sublists (maybe by using guava) 【发布时间】:2012-01-17 19:27:57 【问题描述】:

我想对列表的元素进行分组。我目前正在这样做:

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);

有没有更好的方法来做到这一点,最好是使用番石榴?

【问题讨论】:

【参考方案1】:

当然可以,而且使用 Guava 更容易:) 使用 Multimaps.index(Iterable, Function):

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

如果你给出具体的用例,那么在实际中展示它会更容易。

来自文档的示例:

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 中,整个 sn-p 可能会更简单,因为 Lambas 和方法引用足够简洁,可以内联:

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

对于使用 Collector.groupingBy 的纯 Java 8(无 Guava)示例,请参阅 Jeffrey Bosboom's answer,尽管这种方法几乎没有区别:

它不返回ImmutableListMultimap,而是返回带有Collection 值的Map

不保证返回的 Map 的类型、可变性、可序列化性或线程安全性 (source),

它比 Guava + 方法参考更冗长。

编辑:如果您不关心索引键,您可以获取分组值:

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&lt;List&lt;E&gt;&gt; 查看哪些内容可以轻松复制到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&lt;List&lt;E&gt;&gt; 足够,上面的例子可能更简单:

Collection<List<E>> grouped = indexed.asMap().values();

【讨论】:

这正是Multimap 的设计目的。 代替最后一个代码示例indexed.asMap().values() 可能就足以得到Collection&lt;List&lt;E&gt;&gt; 谢谢,这非常有用,我该如何根据多条件进行分组,例如,假设您收到一个在函数中包含两个字段的对象,并且您需要按此字段分组,我该怎么做做吗? java 7 和 8 Guava中如何使用String作为索引进行分组? @Alex78191 你到底是什么意思?似乎是一个单独的问题。【参考方案2】:

最简单的方法是使用: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】:

来自 Java 8 流库的Collector.groupingBy 提供与 Guava 的 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]

【讨论】:

对于多级分组,您可以按属性值的 List 分组。【参考方案4】:

使用 Java 8、Guava 和一些辅助函数,您可以使用自定义比较器实现分组

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);
        );

【讨论】:

以上是关于将列表的元素分组为子列表(可能使用番石榴)的主要内容,如果未能解决你的问题,请参考以下文章

根据唯一值将列表拆分为子列表

python - 如何首先根据初始列表的单个元素将列表拆分为子列表,然后在python中将列表的连续部分拆分为子列表?

将列表拆分为两个列表的所有可能性

Python:根据索引范围将列表拆分为子列表

如何将编号列表切片为子列表

通过将元素替换为 0 来生成所有可能的列表