将列表的元素分组到子列表中(可能通过使用番石榴)

2022-09-01 04:13:38

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

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

当然是可能的,使用番石榴:)更容易使用 Multimaps.index(可迭代,函数)

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

这是番石榴示例中使用的可能实现。stringLengthFunction


最后,在Java 8中,整个代码段可能更简单,因为lamba和方法引用足够简洁,可以内联:

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

对于使用 Collector.grouping 的纯 Java 8(无番石榴)示例,请参阅 Jeffrey Bosboom 的答案,尽管该方法几乎没有区别:

  • 它不会返回,而是使用值,ImmutableListMultimapMapCollection
  • 对返回的 Map 的类型、可变性、可序列化性或线程安全性(源)没有保证,

  • 它比番石榴+方法参考更详细一些。

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

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>>ArrayListindexed.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在下面的评论中提到的,如果足够了,上面的例子可以更简单:Collection<List<E>>

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

答案 2

Collector.grouping来自Java 8流库,提供与Guava相同的功能。以下是Xaerxess答案中的示例,重写为使用Java 8流:Multimaps.index

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]}