对java集合进行排序和分组

pme*_*idl 17 java

我有一个有名字和分数的对象.我想对这些对象的集合进行排序,以便按名称对它们进行分组,并按每组中的最高分数进行排序(并且在组内也按降序分数排序).

让我展示一下我打算实现的目标.假设我有这些对象(名称,分数):

(a,3)
(a,9)
(b,7)
(b,10)
(c,8)
(c,3)

然后我希望他们像这样排序:

(b,10)
(b,7)
(a,9)
(a,3)
(c,8)
(c,3)

这对比较器来说是可行的吗?我无法弄明白,所以任何提示都会受到赞赏.

Chr*_*röm 22

不,你不能用单一的单一种类做到这一点Comparator.

你必须按名称,并在组群,由最高得分进行排序.

然后,您需要将组展平回列表.

使用Java 8

编辑:因为我写了这个答案,Java 8已经问世,这简化了很多问题:

import java.util.*;
import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;
Run Code Online (Sandbox Code Playgroud)
List<Record> result = records.stream()
    .sorted(comparingInt(Record::getScore).reversed())
    .collect(groupingBy(Record::getName, LinkedHashMap::new, toList()))
    .values().stream()
    .flatMap(Collection::stream)
    .collect(toList());
Run Code Online (Sandbox Code Playgroud)

首先我们按分数排序,然后我们使用a进行分组LinkedHashMap,这将保留键的插入顺序,因此具有较高分数的键将首先出现.

如果组很小,则首先排序是正常的,因此不同组中的对象之间的冗余比较不会造成太大影响.

此外,使用此方法,将保留重复项.


或者,如果您不关心保留重复项,您可以:

Comparator<Record> highestScoreFirst = comparingInt(Record::getScore).reversed();

List<Record> result = records.stream()
        .collect(groupingBy(Record::getName,
                toCollection(() -> new TreeSet<>(highestScoreFirst))))
        .values().stream()
        .sorted(comparing(SortedSet::first, highestScoreFirst))
        .flatMap(Collection::stream)
        .collect(toList());
Run Code Online (Sandbox Code Playgroud)

将记录分组为已排序的TreeSets,而不是将值排序为流的第一个操作,然后按照第一个最高值对这些集进行排序.

如果组很大,则在排序之前进行分组是合适的,以减少冗余比较.


实施Comparable:

你可以通过制作记录来缩短它 Comparable

public class Record implements Comparable<Record> {
    @Override
    public int compareTo(Record other) {
        // Highest first
        return -Integer.compare(getScore(), other.getScore());

        /* Or equivalently:
           return Integer.compare(other.getScore(), getScore());
        */
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)
List<Record> result = records.stream()
    .collect(groupingBy(Record::getName, toCollection(TreeSet::new)))
    .values().stream()
    .sorted(comparing(SortedSet::first))
    .flatMap(Collection::stream)
    .collect(toList());
Run Code Online (Sandbox Code Playgroud)

在Java 8之前

编辑:这是一个非常粗略的单元测试,演示了一种方法.我没有像我希望的那样清理它.

这样的东西在Java中是痛苦的,我通常会使用Google Guava.

import org.junit.Test;

import java.util.*;

import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;

public class GroupSortTest {

    @Test
    public void testGroupSort() {
        List<Record> records = asList(
                new Record("a", 3),
                new Record("a", 9),
                new Record("b", 7),
                new Record("b", 10),
                new Record("c", 8),
                new Record("c", 3));

        List<SortedMap<Integer, Record>> recordsGroupedByName = groupRecordsByNameAndSortedByScoreDescending(records);
        Collections.sort(recordsGroupedByName, byHighestScoreInGroupDescending());
        List<Record> result = flattenGroups(recordsGroupedByName);

        List<Record> expected = asList(
                new Record("b", 10),
                new Record("b", 7),
                new Record("a", 9),
                new Record("a", 3),
                new Record("c", 8),
                new Record("c", 3));

        assertEquals(expected, result);
    }

    private List<Record> flattenGroups(List<SortedMap<Integer, Record>> recordGroups) {
        List<Record> result = new ArrayList<Record>();
        for (SortedMap<Integer, Record> group : recordGroups) {
            result.addAll(group.values());
        }
        return result;
    }

    private List<SortedMap<Integer, Record>> groupRecordsByNameAndSortedByScoreDescending(List<Record> records) {
        Map<String, SortedMap<Integer, Record>> groupsByName = new HashMap<String, SortedMap<Integer, Record>>();
        for (Record record : records) {
            SortedMap<Integer, Record> group = groupsByName.get(record.getName());
            if (null == group) {
                group = new TreeMap<Integer, Record>(descending());
                groupsByName.put(record.getName(), group);
            }
            group.put(record.getScore(), record);
        }
        return new ArrayList<SortedMap<Integer, Record>>(groupsByName.values());
    }

    private DescendingSortComparator descending() {
        return new DescendingSortComparator();
    }

    private ByFirstKeyDescending byHighestScoreInGroupDescending() {
        return new ByFirstKeyDescending();
    }

    private static class ByFirstKeyDescending implements Comparator<SortedMap<Integer, Record>> {
        public int compare(SortedMap<Integer, Record> o1, SortedMap<Integer, Record> o2) {
            return o2.firstKey().compareTo(o1.firstKey());
        }
    }

    private static class DescendingSortComparator implements Comparator<Comparable> {
        public int compare(Comparable o1, Comparable o2) {
            return o2.compareTo(o1);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Amir:这不是问题所在.这个问题希望所有b:s首先排序,因为其中一个b:s总得分最高,然后所有a:s之后,因为其中一个a:s总体上有第二个得分,依此类推.如果你知道SQL,他想要`GROUP BY name ORDER BY MAX(得分),得分DESC`,而不是`ORDER BY name,score`(排序). (3认同)

Jig*_*shi 0

是的 去吧Comparator

先比较name,再评分。它也将与排序分数分组

    List<Score> scores = new ArrayList<Score>();
    scores.add(new Score("a", 58));
    scores.add(new Score("a", 10));
    scores.add(new Score("b", 165));
    scores.add(new Score("a", 1));
    scores.add(new Score("b", 1658));
    scores.add(new Score("c", 1));
    scores.add(new Score("c", 10));
    scores.add(new Score("c", 0));

    Collections.sort(scores, new Comparator<Score>() {

        public int compare(Score o1, Score o2) {
            if (o1.getName().compareTo(o2.getName()) == 0) {
                return o2.getScore() - o1.getScore();
            } else {
                return o1.getName().compareTo(o2.getName());
            }
        }
    });
    System.out.println(scores);
Run Code Online (Sandbox Code Playgroud)

更新

正如克里斯指出的那样。

import java.util.*;

/**
 *
 * @author Jigar
 */
class Score {

    private String name;
    private List<Integer> scores;

    public Score() {
    }

    public Score(String name, List<Integer> scores) {
        this.name = name;
        this.scores = scores;
    }

    public String getName() {
        return name;
    }

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

    public List<Integer> getScores() {
        return scores;
    }

    public void setScores(List<Integer> scores) {
        this.scores = scores;
    }

    @Override
    public String toString() {
        return name + " , " + scores + "\n";
    }
}

public class ScoreDemo { 

    public static void main(String[] args) {
        List<Score> scores = new ArrayList<Score>();


        List<Integer> lstA = new ArrayList<Integer>();
        lstA.add(3);
        lstA.add(9);
        lstA.add(7);
        Collections.sort(lstA);
        Collections.reverse(lstA);

        List<Integer> lstB = new ArrayList<Integer>();
        lstB.add(10);
        lstB.add(8);
        lstB.add(3);
        Collections.sort(lstB);
        Collections.reverse(lstB);

        List<Integer> lstC = new ArrayList<Integer>();
        lstC.add(8);
        lstC.add(3);
        Collections.sort(lstC);
        Collections.reverse(lstC);


        scores.add(new Score("a", lstA));
        scores.add(new Score("b", lstB));
        scores.add(new Score("c", lstC));





        Collections.sort(scores, new Comparator<Score>() {

            public int compare(Score o1, Score o2) {
                return o2.getScores().get(0).compareTo(o1.getScores().get(0));
            }
        });
        System.out.println(scores);

    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我投了反对票,因为这按名称排序,然后按分数排序,这与按名称分组并按组内最高分数排序不同。 (3认同)