假设对象有
ArrayList<Integer>
并且这些对象的 ArrayList 需要按其 ArrayList<Integer>
值排序,以便 ArrayList 的第一个索引具有最高优先级,如果两个对象的列表具有相同的第一个索引,则比较第二个索引等。那要怎么做呢?
例如,想象一个
OlympicWinner
类,其中包含奥运会冠军的 name
以及他们赢得的 medals
public Class OlympicWinner {
//contains medals in order: [gold, silver, bronze]
private ArrayList<Integer> medals
private String name;
OlympicWinner (String name, int goldMedals, int silverMedals, int bronzeMedals) {
this.name = name;
this.medals.add(goldMedals);
this.medals.add(silverMedals);
this.medals.add(bronzeMedals);
}
public String getName() {return this.name;}
public ArrayList<Integer> getMedals() {return this.medals;}
}
现在,如果有一个
ArrayList<OlympicWinner> olympicWinners
列表,并且该列表需要按 ArrayList<Integer> medals
排序,以便列表中的第一个是 OlympicWinner
,它拥有最多的黄金,如果两个拥有相同数量的黄金,则获得银牌最多的一方将是第一名,如果两者的银牌数量相同,则获得铜牌多的一方将是第一名。如何实施?
我可以这样想:
Collections.sort(olympicWinners, new Comparator<OlympicWinner> () {
@Override
public int compare(OlympicWinner a, OlympicWinner b) {
//comparison ...
//ridiculous if comparison
if(a.getMedals().get(0) > b.getMedals().get(0)) {
return 1;
} else if (a.getMedals().get(0) < b.getMedals().get(0)) {
return -1;
} else { //both have equal amount of gold medals, then compares silvers
if(a.getMedals().get(1) > b.getMedals().get(1)) {
return 1;
} else if (a.getMedals().get(1) < b.getMedals().get(1)) {
return -1;
} else {} // and so on
}
} // comparison ends here
});
但我不确定如何实现
ArrayList<Integer>
的比较方法,也不知道这种排序方法Collections.sort()
作为一个整体是否是最好的方法。荒谬的 if 方法不仅看起来很糟糕,而且不适用于该方法的抽象版本,其中 ArrayList<Integer>
的长度可能为 n。 (其中 n 是任意整数)
让我们把这个问题分成两个单独的部分:
List<Integer>
对象列表进行排序:第一个整数获胜;仅当第一个相等时才查看第二个,依此类推。List<List<Integer>> example = new ArrayList<List<Integer>>();
example.add(List.of(2, 1, 1, 1, 1));
example.add(List.of(1, 2, 3, 4, 6));
example.add(List.of(1, 2, 3, 4, 5));
example.sort((a, b) -> {
for (int i = 0; i < Math.min(a.size(), b.size()); i++) {
int c = a.get(i).compareTo(b.get(i));
if (c != 0) return c;
}
// We ran out of elements; the larger list thus wins
return Integer.compare(a.size(), b.size());
});
for (var list : example) System.out.println(list);
打印:
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 6]
[2, 1, 1, 1, 1]
这样就可以了!
现在我们可以做到这一点,我们可以简单地求解 1:
class Foo {
List<Integer> ints;
Foo(int... args) {
this.ints = new ArrayList<Integer>();
for (int arg : args) this.ints.add(arg);
}
@Override public String toString() {
return ints.toString();
}
}
List<Foo> example = new ArrayList<>();
example.add(new Foo(1, 2, 3, 4, 6));
example.add(new Foo(1, 2, 3, 4, 5));
example.add(new Foo(2, 1, 1, 1, 1));
Comparator<List<Integer>> intListComparator = (a, b) -> {
for (int i = 0; i < Math.min(a.size(), b.size()); i++) {
int c = a.get(i).compareTo(b.get(i));
if (c != 0) return c;
}
// We ran out of elements; the larger list thus wins
return Integer.compare(a.size(), b.size());
};
example.sort(Comparator.comparing(f -> f.ints, intListComparator));
for (var f : example) System.out.println(f);
Comparator.comparing(x, y)
就是这里的魔力。 x
是一个导出属性的函数。在这里,给定一个 f
,它会导出整数列表。 y
是一个比较器,可以通过返回正数、负数或 0 来决定给定任何 2 个派生属性(此处给定 2 个整数列表),哪一个“更高”/如果它们按顺序相同。您对实施
Comparator
的想法是正确的,但这里有一些可以(应该?)改进的地方。
首先,不需要手动比较每组对应的奖牌。您可以使用
Integer.compare
并让它为您完成繁重的工作。OlympicWinner
是等效的:
Comparator<OlympicWinner> medalsComparator = new Comparator<OlympicWinner>() {
@Override
public int compare(OlympicWinner o1, OlympicWinner o2) {
// Don't assume how many medals an OlypicWinner has,
// but assume o1 and o2 have the same length of the medals member
List<Integer> m1 = o1.getMedals();
List<Integer> m2 = o2.getMedals();
for (int i = 0; i < m1.size(); ++i) {
int res = Integer.compare(m1.get(i), m2.get(i));
if (res != 0) {
return res;
}
}
// No pair of corresponding medals has a different count,
// so o1 and o2 must be equivalent
return 0;
}
如评论中所述,该比较器并不假设只有三种类型的奖牌,而是假设两个
OlympicWinner
具有相同数量的类型(即 medals
列表的大小相同)。如果这个假设并不总是正确,则应该修改代码以适应它,但为了保持示例简单,我采用了这个假设。