对java集合进行排序和分组

时间:2011-03-01 17:13:13

标签: 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)

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

5 个答案:

答案 0 :(得分:20)

不,你不能只用一个Comparator进行单一排序。

您必须按名称分组按群组中的最高分对群组进行排序。

然后您需要将群组展平回到列表中。

使用Java 8

编辑:自从我写了这个答案以来,Java 8问世了,这大大简化了问题:

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

List<Record> result = records.stream()
    .sorted(comparingInt(Record::getScore).reversed())
    .collect(groupingBy(Record::getName, LinkedHashMap::new, toList()))
    .values().stream()
    .flatMap(Collection::stream)
    .collect(toList());

首先我们按分数排序,然后我们使用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());

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

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


实施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());
        */
    }
    ...
}

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

在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);
        }
    }
}

答案 1 :(得分:1)

Foreach over the collection,并将对象放入Map<String, SortedSet<YourObject>>,按名称键入,其中SortedSet是一个TreeSet,其中包含一个自定义比较器,按比例进行比较。

然后预测地图的values()集合,并将这些组放入SortedSet<SortedSet<YourObject>>,并使用第二个自定义比较器根据最大元素比较SortedSets。实际上,您可以简单地使用addAll()。而不是使用foreached。

以下是代码:

public class SortThings {

    static class Thing {
        public final String name;
        public final int score;
        public Thing(String name, int score) {
            this.name = name;
            this.score = score;
        }
        @Override
        public String toString() {
            return "(" + name + ", " + score + ")";
        }
    }

    public static void main(String[] args) {
        Collection<Thing> things = Arrays.asList(
            new Thing("a", 3),
            new Thing("a", 9),
            new Thing("b", 7),
            new Thing("b", 10),
            new Thing("c", 8),
            new Thing("c", 3)
        );

        SortedSet<SortedSet<Thing>> sortedGroups = sortThings(things);

        System.out.println(sortedGroups);
    }

    private static SortedSet<SortedSet<Thing>> sortThings(Collection<Thing> things) {
        final Comparator<Thing> compareThings = new Comparator<Thing>() {
            public int compare(Thing a, Thing b) {
                Integer aScore = a.score;
                Integer bScore = b.score;
                return aScore.compareTo(bScore);
            }
        };

        // first pass
        Map<String, SortedSet<Thing>> groups = new HashMap<String, SortedSet<Thing>>();
        for (Thing obj: things) {
            SortedSet<Thing> group = groups.get(obj.name);
            if (group == null) {
                group = new TreeSet<Thing>(compareThings);
                groups.put(obj.name, group);
            }
            group.add(obj);
        }

        // second pass
        SortedSet<SortedSet<Thing>> sortedGroups = new TreeSet<SortedSet<Thing>>(new Comparator<SortedSet<Thing>>() {
            public int compare(SortedSet<Thing> a, SortedSet<Thing> b) {
                return compareThings.compare(a.last(), b.last());
            }
        });
        sortedGroups.addAll(groups.values());
        return sortedGroups;
    }

}

请注意,输出的顺序从最小到最大。这是Java收藏的自然顺序;如果这就是你所需要的那样,那么修改它以便以另一种方式进行排序将是微不足道的。

答案 2 :(得分:1)

public class ScoreComparator implements Comparator<Item>
{

  public int compare(Item a, Item b){

    if (a.name.equals(b.name){
      return a.score.compareTo(b.score);
    }

    return a.name.compareTo(b.Name);    

  }

}

答案 3 :(得分: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);

更新

克里斯指出。

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);

    }
}

答案 4 :(得分:0)

我认为你可以做到这一点。首先检查一下该组是否相等。如果是则比较得分。否则返回您想要更多的组。让我编写代码。

    class Item{
      String name;
      int score;
    }

   new Comparator<Item>(){

       @Override
       public int compare(Item o1, Item o2) {
            if (o1.name.equals(o2.name)) {
                return o1.score > o2.score ? 1 : -1; // might have to flip this. I didn't test
            }else {
                return o1.name.compareTo(o2.name);
            }
       }
    };