java - 使用流 API 在 Java 中按 n 个字段分组

标签 java algorithm java-8 java-stream

需要加群List<Object>基于 N 个属性字段,这些字段是在运行时决定的。我怎样才能做到这一点?

Group by multiple field names in java 8 提到这个问题,它使用固定数量的字段。

例如:

Person{ int age, String city, Date doj, double salary}
record1: 25, NYC, 02/25/2018, 50000
record2: 25, MEX, 02/25/2017, 70000
record3: 26, MEX, 02/25/2017, 80000

GroupBy(city, doj)

Record1: = MEX, 02/25/2017, 150000
Record2: = NYC, 02/25/2018, 50000

工资会增加。

我将结果存储在 Map<Object, List<Object>> 中 我已经实现了大部分。我面临的唯一问题是如何更改 groupingBy 中的键。

Collectors.groupingBy( date ) : 第二次迭代会弄乱所有按 city+date 分组的城市的数据。如果我可以将 key 更改为City+Date,这将得到解决。 我如何在第二次迭代中更改我的 key Collectors.groupingBy( date )

最佳答案

这是一个完整的例子:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class Grouping {

    static final class Person {
        private final int age;
        private final String city;
        private final String doj;
        private final double salary;

        public Person(int age, String city, String doj, double salary) {
            this.age = age;
            this.city = city;
            this.doj = doj;
            this.salary = salary;
        }

        public int getAge() {
            return age;
        }

        public String getCity() {
            return city;
        }

        public String getDoj() {
            return doj;
        }

        public double getSalary() {
            return salary;
        }

        @Override
        public String toString() {
            return "Person{" +
                "age=" + age +
                ", city='" + city + '\'' +
                ", doj='" + doj + '\'' +
                ", salary=" + salary +
                '}';
        }
    }

    enum Property {
        AGE {
            @Override
            protected Object extractValue(Person person) {
                return person.getAge();
            }
        },
        CITY {
            @Override
            protected Object extractValue(Person person) {
                return person.getCity();
            }
        },
        DOJ {
            @Override
            protected Object extractValue(Person person) {
                return person.getDoj();
            }
        };

        protected abstract Object extractValue(Person person);

        public PropertyValue toValue(Person person) {
            return new PropertyValue(this, extractValue(person));
        }
    }

    static final class PropertyValue {
        private final Property property;
        private final Object value;

        public PropertyValue(Property property, Object value) {
            this.property = property;
            this.value = value;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            PropertyValue that = (PropertyValue) o;
            return property == that.property &&
                Objects.equals(value, that.value);
        }

        @Override
        public int hashCode() {
            return Objects.hash(property, value);
        }

        @Override
        public String toString() {
            return "PropertyValue{" +
                "property=" + property +
                ", value=" + value +
                '}';
        }
    }

    private static List<PropertyValue> createGroupingKey(List<Property> properties, Person person) {
        return properties.stream().map(property -> property.toValue(person)).collect(Collectors.toList());
    }

    public static void main(String[] args) {
        List<Person> persons = Arrays.asList(
            new Person(25, "NYC", "02/25/2018", 50000),
            new Person(25, "MEX", "02/25/2017", 70000),
            new Person(26, "MEX", "02/25/2017", 80000)
        );

        // TODO ask the user, rather than hardcoding
        List<Property> groupingProperties = Arrays.asList(Property.CITY, Property.DOJ);

        Map<List<PropertyValue>, Double> salaryAggregatedByChosenProperties =
            persons.stream()
                   .collect(Collectors.groupingBy(p -> createGroupingKey(groupingProperties, p),
                                                  Collectors.summingDouble(Person::getSalary)));

        System.out.println("salaryAggregatedByChosenProperties = " + salaryAggregatedByChosenProperties);
    }
}

它的作用:

  1. 询问用户应该使用哪些属性进行分组(这实际上没有完成,而是模拟的,因为这不是您问题的核心)。你得到一个List<Property> ,包含(例如)属性 CITY 和 DOJ
  2. 您将每个人转换成一个分组键,类型为 List<PropertyValue> ,因此,第一个人将被转换为 [NYC, 02/25/2018],而第二和第三个人将被转换为 [MEX, 02/25/2017](因此具有相同的 key )。
  3. 您按关键字对人员进行分组
  4. 您将同一组人员的工资相加

关于java - 使用流 API 在 Java 中按 n 个字段分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48972047/

相关文章:

java - 如何在 Java 迭代中分割字符串

java - Java 中泛型类型的行为

java - lambda 表达式的向后兼容性?

Java8 解析给定字符串的日期或日期时间格式

java - 具有未经检查的异常的选项

java - 为什么我无法加载/找到主类?

java - Spring Boot Jar 执行内部属性文件

algorithm - 给定 N 个人,其中一些是敌人,找到没有敌人的区间数

algorithm - 给定无向图中的完整子图数

ruby-on-rails - Ruby on Rails 奇怪的运算符