按多个字段对 Java 个对象列表进行排序并按特定字段分组

Sort a list of Java objects by multiple fields and group by a particular field

我正在尝试根据多个字段对 Java 个对象的列表进行排序。对象的类型:

public class Employee {
    String empId;
    String groupId;
    String salary;
    ...
}

必须将具有相同groupId 的所有员工分组在一起。 groupId 可以为空。工资总额(一组中所有员工的工资总和)最高的组必须位于列表的顶部。该列表必须按降序排列。在每个组中,员工必须按照工资的降序排列。

示例:给定数据:

+-------+---------+--------+--+
| empId | groupId | salary |  |
+-------+---------+--------+--+
| emp1  | grp1    |    500 |  |
| emp2  | null    |    600 |  |
| emp3  | null    |    700 |  |
| emp4  | grp2    |    800 |  |
| emp5  | grp1    |    700 |  |
| emp6  | grp2    |   1000 |  |
| emp7  | grp1    |    800 |  |
| emp8  | null    |   1000 |  |
| emp9  | grp2    |    600 |  |
+-------+---------+--------+--+

预期输出:

+-------+---------+--------+
| empId | groupId | salary |
+-------+---------+--------+
| emp6  | grp2    |   1000 |
| emp4  | grp2    |    800 |
| emp9  | grp2    |    600 |
| emp8  | null    |   1000 |
| emp3  | null    |    700 |
| emp2  | null    |    600 |
| emp7  | grp1    |    800 |
| emp5  | grp1    |    700 |
| emp1  | grp1    |    500 |
+-------+---------+--------+

我的解决方案:

public class Employee {
    String empId;
    String groupId;
    int salary;

    ...

    public String getEmpId() {
        return empId;
    }

    public void setEmpId(String empId) {
        this.empId = empId;
    }

    public String getGroupId() {
        return groupId;
    }

    public void setGroupId(String groupId) {
        this.groupId = groupId;
    }

    public int getSalary() {
        return salary;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    ...

}

class EmployeeChainedComparator implements Comparator<Employee> {

    private List<Comparator<Employee>> listComparators;

    public EmployeeChainedComparator(Comparator<Employee>... comparators) {
        this.listComparators = Arrays.asList(comparators);
    }

    @Override
    public int compare(Employee o1, Employee o2) {
        for (Comparator<Employee> comparator : listComparators) {
            int result = comparator.compare(o1, o2);
            if (result != 0)
                return result;
        }

        return 0;
    }

}

class EmployeeGroupComparator implements Comparator<Employee> {

    @Override
    public int compare(Employee o1, Employee o2) {
        if(o2.getGroupId() == null)
            return (o1.getGroupId() == null) ? 0 : -1;
        if(o1.getGroupId() == null)
            return 1;
        return o1.getGroupId().compareTo(o2.getGroupId());
    }

}


class EmployeeSalaryComparator implements Comparator<Employee> {

    @Override
    public int compare(Employee o1, Employee o2) {
        return o2.getSalary() - o1.getSalary();
    }

}

class Solution {
    void sortEmployees(List<Employee> employees) {
        Collections.sort(employees, new EmployeeChainedComparator(new EmployeeGroupComparator(), new EmployeeSalaryComparator()))
    }
}

这是我想出的答案。这是一个相当粗糙的解决方案,但它的工作。

Collections.sort(list,
                new EmployeeChainedComparator(new EmployeeGroupComparator(), new EmployeeSalaryComparator()));

        List<Employee> emps = new ArrayList<>();

        while (!list.isEmpty()) {
            int max = list.get(0).getSalary();
            int currMax = 0;
            String currMaxGroup = "";
            for (int i = 0; i < list.size(); i++) {
                if (i != list.size() - 1 && ((list.get(i).getGroupId() != null && list.get(i + 1).getGroupId() != null
                        && list.get(i).getGroupId().equals(list.get(i + 1).getGroupId()))
                        || (list.get(i).getGroupId() == list.get(i + 1).getGroupId())))
                    max = max + list.get(i + 1).getSalary();
                else if (i == list.size() - 2) {
                    max = max + list.get(i + 1).getSalary();
                    if (max > currMax) {
                        currMax = max;
                        currMaxGroup = list.get(i).getGroupId();
                        max = 0;
                    }
                } else if (max > currMax) {
                    currMax = max;
                    currMaxGroup = list.get(i).getGroupId();
                    max = 0;
                }

                if (i != list.size() - 1 && ((list.get(i).getGroupId() != null && list.get(i + 1).getGroupId() != null
                        && !list.get(i).getGroupId().equals(list.get(i + 1).getGroupId()))
                        || (list.get(i).getGroupId() != list.get(i + 1).getGroupId())))
                    max = list.get(i + 1).getSalary();
            }
            for (int i = 0; i < list.size(); i++) {
                if (currMaxGroup == null && list.get(i).getGroupId() == null) {
                    emps.add(list.get(i));
                    list.remove(list.get(i));
                    i--;
                } else if (currMaxGroup != null && currMaxGroup.equals(list.get(i).getGroupId())) {
                    emps.add(list.get(i));
                    list.remove(list.get(i));
                    i--;
                }
            }
        }

在此之后,emps ArrayList 将具有所需的结果。

您发布的解决方案对于这个问题来说似乎太复杂了。下面给出了一个解决它的干净方法:

  1. 使用 class、Solution 中定义的比较器对员工进行排序。
  2. 以工资总和为分组函数,按组ID对员工进行分组。换句话说,创建一个 Map,其中 groupId 将是键,属于 groupId 的工资总和将是值。
  3. 迭代step#2创建的map排序后的条目集,将每个条目对应的记录放入结果列表中。

    下面给出了实现上述算法的代码:

    // Sort employees using the comparators defined in the class, Solution
    new Solution().sortEmployees(empList);
    
    // Group employees by group ID with the sum of salary as the grouping function
    Map<String, Integer> map = new HashMap<>();
    for (Employee e : empList) {
        String grp = e.getGroupId();
        if (grp == null) {
            grp = "null";
        }
        Integer salary = map.get(grp);
        map.put(grp, salary == null ? e.getSalary() : e.getSalary() + salary);
    }
    
    // Result list
    List<Employee> result = new ArrayList<>();
    
    // Iterate the sorted entry set of `map` and put the records corresponding to
    // an entry into the result list
    for (Entry<String, Integer> entry : entriesSortedByValues(map)) {
        String grp = entry.getKey();
        int i;
    
        // Find the starting index of `grp` in empList
    
        if ("null".equals(grp)) {// Special handling for employees with `null` group
            // Find the index in `empList` where employees with the group as `null` starts
            for (i = 0; i < empList.size() && empList.get(i).getGroupId() != null; i++)
                ;
    
            // Add elements before a different group is encountered
            for (int j = i; j < empList.size() && empList.get(j).getGroupId() == null; j++) {
                result.add(empList.get(j));
            }
        } else {
            // Find the index in `empList` where employees with the group as `grp` starts
            for (i = 0; i < empList.size() && !grp.equals(empList.get(i).getGroupId()); i++)
                ;
    
            // Add elements before a different group is encountered
            for (int j = i; j < empList.size() && grp.equals(empList.get(j).getGroupId()); j++) {
                result.add(empList.get(j));
            }
        }
    }
    

    演示

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Objects;
    import java.util.SortedSet;
    import java.util.TreeSet;
    
    class Employee {
        String empId;
        String groupId;
        int salary;
    
        public Employee(String empId, String groupId, int salary) {
            this.empId = empId;
            this.groupId = groupId;
            this.salary = salary;
        }
    
        public String getEmpId() {
            return empId;
        }
    
        public String getGroupId() {
            return groupId;
        }
    
        public int getSalary() {
            return salary;
        }
    
        @Override
        public boolean equals(Object obj) {
            Employee other = (Employee) obj;
            return Objects.equals(empId, other.empId) && Objects.equals(groupId, other.groupId)
                    && Objects.equals(salary, other.salary);
        }
    
        @Override
        public String toString() {
            return "Employee [empId=" + empId + ", groupId=" + groupId + ", salary=" + salary + "]";
        }
    }
    
    class EmployeeChainedComparator implements Comparator<Employee> {
    
        private List<Comparator<Employee>> listComparators;
    
        public EmployeeChainedComparator(Comparator<Employee>... comparators) {
            this.listComparators = Arrays.asList(comparators);
        }
    
        @Override
        public int compare(Employee o1, Employee o2) {
            for (Comparator<Employee> comparator : listComparators) {
                int result = comparator.compare(o1, o2);
                if (result != 0)
                    return result;
            }
    
            return 0;
        }
    
    }
    
    class EmployeeGroupComparator implements Comparator<Employee> {
    
        @Override
        public int compare(Employee o1, Employee o2) {
            if (o2.getGroupId() == null)
                return (o1.getGroupId() == null) ? 0 : -1;
            if (o1.getGroupId() == null)
                return 1;
            return o1.getGroupId().compareTo(o2.getGroupId());
        }
    
    }
    
    class EmployeeSalaryComparator implements Comparator<Employee> {
    
        @Override
        public int compare(Employee o1, Employee o2) {
            return o2.getSalary() - o1.getSalary();
        }
    
    }
    
    class Solution {
        void sortEmployees(List<Employee> employees) {
            Collections.sort(employees,
                    new EmployeeChainedComparator(new EmployeeGroupComparator(), new EmployeeSalaryComparator()));
        }
    }
    
    public class Q62447064 {
        public static void main(String[] args) {
            List<Employee> empList = new ArrayList<>(List.of(new Employee("emp1", "grp1", 500),
                    new Employee("emp2", null, 600), new Employee("emp3", null, 700), new Employee("emp4", "grp2", 800),
                    new Employee("emp5", "grp1", 700), new Employee("emp6", "grp2", 1000),
                    new Employee("emp7", "grp1", 800), new Employee("emp8", null, 1000),
                    new Employee("emp9", "grp2", 600)));
    
            // Sort employees using the comparators defined in the class, Solution
            new Solution().sortEmployees(empList);
    
            // Group employees by group ID with the sum of salary as the grouping function
            Map<String, Integer> map = new HashMap<>();
            for (Employee e : empList) {
                String grp = e.getGroupId();
                if (grp == null) {
                    grp = "null";
                }
                Integer salary = map.get(grp);
                map.put(grp, salary == null ? e.getSalary() : e.getSalary() + salary);
            }
    
            // Result list
            List<Employee> result = new ArrayList<>();
    
            // Iterate the sorted entry set of `map` and put the records corresponding to
            // an entry into the result list
            for (Entry<String, Integer> entry : entriesSortedByValues(map)) {
                String grp = entry.getKey();
                int i;
    
                // Find the starting index of `grp` in empList
    
                if ("null".equals(grp)) {// Special handling for employees with `null` group
                    // Find the index in `empList` where employees with the group as `null` starts
                    for (i = 0; i < empList.size() && empList.get(i).getGroupId() != null; i++)
                        ;
    
                    // Add elements before a different group is encountered
                    for (int j = i; j < empList.size() && empList.get(j).getGroupId() == null; j++) {
                        result.add(empList.get(j));
                    }
                } else {
                    // Find the index in `empList` where employees with the group as `grp` starts
                    for (i = 0; i < empList.size() && !grp.equals(empList.get(i).getGroupId()); i++)
                        ;
    
                    // Add elements before a different group is encountered
                    for (int j = i; j < empList.size() && grp.equals(empList.get(j).getGroupId()); j++) {
                        result.add(empList.get(j));
                    }
                }
            }
    
            // Display result list
            for (Employee e : result) {
                System.out.println(e);
            }
        }
    
        private static <K, V extends Comparable<? super V>> SortedSet<Map.Entry<K, V>> entriesSortedByValues(
                Map<K, V> map) {
            SortedSet<Map.Entry<K, V>> sortedEntries = new TreeSet<Map.Entry<K, V>>(new Comparator<Map.Entry<K, V>>() {
                @Override
                public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) {
                    int res = e2.getValue().compareTo(e1.getValue());
                    return res != 0 ? res : 1;
                }
            });
            sortedEntries.addAll(map.entrySet());
            return sortedEntries;
        }
    }
    

    输出:

    Employee [empId=emp6, groupId=grp2, salary=1000]
    Employee [empId=emp4, groupId=grp2, salary=800]
    Employee [empId=emp9, groupId=grp2, salary=600]
    Employee [empId=emp8, groupId=null, salary=1000]
    Employee [empId=emp3, groupId=null, salary=700]
    Employee [empId=emp2, groupId=null, salary=600]
    Employee [empId=emp7, groupId=grp1, salary=800]
    Employee [empId=emp5, groupId=grp1, salary=700]
    Employee [empId=emp1, groupId=grp1, salary=500]
    

    注意:方法entriesSortedByValues是从this post复制过来的。