Java:使用Stream API在嵌套列表中查找常见项目

时间:2018-07-04 01:44:13

标签: java java-8 java-stream

假设我有一个List<List<Animal>> animals。此嵌套列表表示一个地方列表,每个地方都包含动物列表。

我需要找出至少出现在两个不同位置的动物类型列表。我知道我可以进行普通循环并执行该操作。有什么方法可以通过Stream API完成吗?

示例:

List<List<Animal>> animals = new ArrayList<>();
animals.add(Arrays.asList(new Dog(), new Cat()));
animals.add(Arrays.asList(new Dog(), new Bird()));
animals.add(Arrays.asList(new Bird()));

预期(等于):

List<Class<? extends Animal>> animalTypes = Arrays.asList(Dog.class, Bird.class);

至于尝试,我只设法将内部列表转换为一组类:

animals.stream().map(place -> place.stream().map(animal -> animal.getClass()).collect(Collectors.toSet()));

更新

无需Stream API即可执行此操作的代码:

final List<List<Animal>> animals = new ArrayList<>();
animals.add(Arrays.asList(new Dog(), new Cat()));
animals.add(Arrays.asList(new Dog(), new Bird()));
animals.add(Arrays.asList(new Bird()));

final Map<Class<? extends Animal>, Integer> count = new HashMap<>();

for (final List<Animal> place : animals) {
    final Set<Class<? extends Animal>> uniqueTypes = new HashSet<>();

    for (final Animal animal : place) {
        uniqueTypes.add(animal.getClass());
    }

    for (final Class<? extends Animal> type : uniqueTypes) {
        if (!count.containsKey(type))
        {
            count.put(type, 1);
        }
        else
        {
            count.put(type, count.get(type).intValue() + 1);
        }
    }
}

final List<Class<? extends Animal>> typesAppearingAtLeastAtTwoPlaces = new ArrayList<>();

for (final Class<? extends Animal> type : count.keySet()) {
    if (count.get(type).intValue() >= 2) {
        typesAppearingAtLeastAtTwoPlaces.add(type);
    }
}

System.out.println(typesAppearingAtLeastAtTwoPlaces);

输出:

[class Test$Dog, class Test$Bird]

3 个答案:

答案 0 :(得分:5)

首先,对所有动物进行计数,然后选择出现一次以上的动物:

import static java.util.stream.Collectors.*;
.....

Map<Class<? extends Animal>, Long> animalCounts = animals.stream()
        .flatMap(
                lst -> lst.stream()
                    .map(a -> a.getClass())
                    .distinct()   // in case several of the same animal are in the same place
        )
        .collect(groupingBy(x -> x, counting()));

List<Class<? extends Animal>> animalTypes = animalCounts.entrySet().stream()
        .filter(e -> e.getValue() > 1)
        .map(Map.Entry::getKey)
        .collect(toList());

答案 1 :(得分:2)

我认为您也可以尝试StreamEx。它使您有机会编写更简洁,更易读的代码:

StreamEx.of(animals)
    .flatMap(e -> e.stream().map(Animal::getClass).distinct())
    .distinct(2).toList();

答案 2 :(得分:0)

首先,也许您应该尝试使用 flatMap 而不是 map

  

animals.stream()。map(place-> place.stream()。map(animal-> animal.getClass())。collect(Collectors.toSet()));

第二,实际上我们可以使用外部 ConcurrentHashMap 来做到这一点,这将使我们能够在需要时使用parallel

    ConcurrentHashMap<Class, AtomicLong> theCounterMap = new ConcurrentHashMap<>();
    animals.stream().flatMap(list -> list.stream().map(animal -> animal.getClass()).distinct())
        .forEach(clazz -> theCounterMap.computeIfAbsent(clazz, k -> new AtomicLong()).getAndIncrement());
    List<Class> classList = theCounterMap.entrySet().stream()
            .filter(entry -> entry.getValue().get() > 1)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());

但是,如果您需要跟踪来源列表为两个不同的地方),则需要进一步修改上面的解决方案。

已更新

根据@shmosel的建议,您可以直接使用一种更简单的方法来达到以下目的:

    Map<Class, Long> theCounterMap = animals.stream().flatMap(list -> list.stream().map(animal -> animal.getClass()).distinct())
        .collect(Collectors.groupingBy(e -> e, Collectors.counting()));
    List<Class> classList = theCounterMap.entrySet().stream()
            .filter(entry -> entry.getValue() > 1)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());