使用 Java 流从列表中获取具有重复属性的所有对象

问题描述 投票:0回答:3

我有以下问题:

目标:我实际上想要所有具有基于“名称”的重复项的“城市”实例。

限制:我需要使用流。

错误:没有错误。

描述:

我有“城市”类的实例列表。
目标是使用流获得一个列表,其中包含根据“名称”找到的所有重复项,而不仅仅是一个。

我试图研究我的问题的答案,但找到的所有答案都只解释如何添加重复一次的元素,而不是在情况需要时添加两次或三次。

我找到的研究材料:

再次目标:随着下面显示的列表,将创建一个新列表来接收重复项,其中将包含“Lisboa”3x 的实例、“Braga”3x 的实例和“Guarda”2x 的实例,这是因为这些是基于名称的重复元素。

该列表将包含“城市”类型的实例,其名称重复。

主要:

public class Main {
    public static void main(String[] args) {
        List<City> portugalCities = List.of(
                new City("Lisboa", "Estremadura", 2275591),
                new City("Lisboa", "Estremadura", 2275591),
                new City("Faro", "Algarve", 67650),
                new City("Braga", "Minho", 193324),
                new City("Braga", "Esposende", 193324),
                new City("Braga", "Fafe", 193324),
                new City("Alentejo", "Ribatejo", 704533),
                new City("Viseu", "Beira Alta", 99561),
                new City("Lisboa", "Alenquer", 2275591),
                new City("Guarda", "Almeida", 143019),
                new City("Guarda", "Aguiar da Beira", 143019)
        );
    }
}

城市:

public class City {
    private String name;
    private String province;
    private int population;
    // ----- Properties

    public City(String name, String province, int population) {
        this.name = name;
        this.province = province;
        this.population = population;
    }
    // ----- Constructor

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String district) {
        this.province = district;
    }

    public int getPopulation() {
        return population;
    }

    public void setPopulation(int population) {
        this.population = population;
    }
    // ----- Getter & Setters

    @Override
    public String toString() {
        return String.format("|name: %s; district: %s; population: %s|", name, province, population);
    }
    // ----- toString

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        City city = (City) o;
        return population == city.population && Objects.equals(name, city.name) && Objects.equals(province, city.province);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, province, population);
    }

    // ----- Equals & hashCode
}
java java-stream
3个回答
3
投票

理想情况下,流将一次处理一个元素,而不需要存储中间结果。然而,在这种情况下,我们需要存储按名称分组的城市,因为除非我们处理了所有元素,否则我们无法知道哪些是重复的。因此,鉴于类

City
的定义和问题的数据集,我提出以下两步过程:

Map<String, List<City>> citiesByName =
  portugalCities.stream().collect(Collectors.groupingBy(City::getName));
List<City> result =
  citiesByName.values().stream()
    .filter(list -> list.size() > 1)
    .flatMap(List::stream)
    .toList();

可以在更高级的单链中完成,但是仍然有一个中间结果,

citiesByName

结果包含所有重复项(据我了解是问题的对象),而不仅仅是计数。


2
投票

首先收集姓名并进行计数,然后用它来过滤掉非重复者:

Map<String, Long> freq = portugalCities.stream()
  .map(City::getName)
  .collect(groupingBy(n -> n, counting()));

List<City> dupes = portugalCities.stream()
  .filter(c -> freq.get(c.getName()) > 1)
  .collect(toList());

然后根据需要打印它们:

dupes.forEach(System.out::println);

0
投票

您可能需要使用自定义收集器,这与 https://mdn.io/Array.reduce:

不同

此示例产生输出:

{Alentejo=1, Braga=3, Faro=1, Guarda=2, Lisboa=3, Viseu=1}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collector;


public class Example {

    public static void main(String[] args) {

        List<City> portugalCities = List.of(
                new City("Lisboa", "Estremadura", 2275591),
                new City("Lisboa", "Estremadura", 2275591),
                new City("Faro", "Algarve", 67650),
                new City("Braga", "Minho", 193324),
                new City("Braga", "Esposende", 193324),
                new City("Braga", "Fafe", 193324),
                new City("Alentejo", "Ribatejo", 704533),
                new City("Viseu", "Beira Alta", 99561),
                new City("Lisboa", "Alenquer", 2275591),
                new City("Guarda", "Almeida", 143019),
                new City("Guarda", "Aguiar da Beira", 143019)
        );

        Map<String, Integer> counts = portugalCities.stream()
                .collect(Collector.<City, Map<String, Integer>>of(
                        // this means our collector starts by creating a tree map
                        // using the default constructor,
                        // which we passed here
                        TreeMap::new,
                        //
                        // then, process each element collecting to a map.
                        // basically:
                        // [{ name: 'a' }, { name: 'a' }, { name: 'b' }]
                        //  .reduce((acc /* accumulator*/, next) => {
                        //    acc[next.name] = (acc[next.name] || 0) + 1;
                        //    return acc; }, {} /* initial value of acc */)
                        (map, next) -> map.put(next.getName(),
                                map.computeIfAbsent(next.getName(), k -> 0) + 1),
                        // this is not needed, as you only have 1 map
                        // if given, it would be how to combine 2 maps (a, and b)
                        (a, b) -> {
                            throw new UnsupportedOperationException();
                        }
                ));

        System.out.println(counts);
    }

    @AllArgsConstructor
    @Accessors(chain = true)
    @Data
    static class City {
        private String name;
        private String province;
        private int population;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.