我有一个名字和分数的对象。我想对这些对象的集合进行排序,以便按名称对它们进行分组,并按每个组中的最高分数进行排序(并在组内按降序分数排序)。
让我展示一下我打算实现的目标。假设我有这些对象(名称,分数):
(a,3)
(a,9)
(b,7)
(b,10)
(c,8)
(c,3)
然后我希望他们像这样排序:
(b,10)
(b,7)
(a,9)
(a,3)
(c,8)
(c,3)
这对比较器来说是可行的吗?我无法弄清楚,所以任何提示都会受到赞赏。
答案 0 :(得分:20)
不,你不能只用一个Comparator
进行单一排序。
您必须按名称分组,按群组中的最高分对群组进行排序。
然后您需要将群组展平回到列表中。
编辑:自从我写了这个答案以来,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中很痛苦,我通常会使用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);
}
}
};