我需要一种基于第三个对象的属性对对象集合进行排序的方法.我将尝试使用简化的案例来描述它.
假设我们有一个Person对象
class Person {
String firstName;
String lastName;
...
}
我们想对一个人的一系列人物进行排序.例如:John Doe是我们想要找到的人,或者如果我们找不到,我们希望最“相似”的人在排序集合的顶部.
相似性定义如下:如果只有第一个名称匹配,那么当只有姓氏匹配时,它是更好的匹配.当然,如果两者都匹配,那就是宾果游戏.
我提出了一个解决方案,但我不确定它是否完美无缺.想法是使用如下的比较器:
public static class PersonComparator implements Comparator<Person> {
String firstName;
String lastName;
public PersonComparator(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public int compare(Person p1, Person p2) {
int p1Match = calcMatch(p1);
int p2Match = calcMatch(p2);
int result = p1Match - p2Match;
if (result == 0) {
//not very sure about this part
result = p1.firstName.compareTo(p2.firstName);
if (result == 0) {
result = p1.lastName.compareTo(p2.lastName);
}
}
return result;
}
public int calcMatch(Person p) {
StringBuilder builder = new StringBuilder();
builder.append(firstName.equals(p.firstName) ? "1" : "0");
builder.append(lastName.equals(p.lastName) ? "1" : "0");
return Integer.parseInt(builder.toString(), 2);
}
}
因此,如果Person的第一个名称匹配而lastname不匹配,则将二进制匹配’10’转换为整数2,而如果Person 2的first和lastnames都匹配,则二进制值将为’11’,转换为3然后,compareTo将简单地返回2 – 3 = -1,表示一个’少于’然后是两个.
但是,如果这个人的名字和姓氏都与我们正在寻找的名字不匹配,该怎么办.匹配的“二进制值”将是相同的,并且返回0将指示两个人彼此相等(例如,至少对于TreeSet).当在TreeSet中使用这样的比较器时,两个人中只有一个将在结果集中持续.
这不是期望的行为,因此在两个人都得到相同匹配值的情况下,我基于两个人的字段比较来计算compareTo返回的值.
运行以下简单测试用例会显示一个示例:
public static void main(String[] args) {
List<Person> persons = new ArrayList<Person>();
persons.add(new Person("Pietje", "Puk"));
persons.add(new Person("Jan", "Jansen"));
persons.add(new Person("John", "Doe"));
Comparator<Person> comparator = new PersonComparator("John", "Doe")
int firstCompare = comparator.compare(persons.get(0), persons.get(1));
int secondCompare = comparator.compare(persons.get(1), persons.get(2));
int thirdCompare = comparator.compare(persons.get(0), persons.get(2));
System.out.println(firstCompare + " vs " + secondCompare + " vs " + thirdCompare);
TreeSet<Person> personsSet = new TreeSet<Person>(comparator);
personsSet.addAll(persons);
personsSet.add(new Person("Baby", "Doe"));
personsSet.add(new Person("John", "Roe"));
personsSet.add(new Person("Jane", "Doe"));
int i = 0;
for (Person person : personsSet) {
System.out.println(i++ + ") " + person + " [" + comparator.calcMatch(person) + "]");
}
}
执行上面的代码会导致:
6 vs -3 vs -3
0) Jan Jansen [0]
1) Pietje Puk [0]
2) Baby Doe [1]
3) Jane Doe [1]
4) John Roe [2]
5) John Doe [3]
第一次比较是基于名字(Pietje Puk vs Jan Jansen)而得出的结果是6.第二次比较基于姓氏与枢轴(Jan Jansen vs John Doe)的比较,结果为-3,而最后一个是同样基于姓氏与枢轴(Pietje Puk vs John Doe)相比,也导致了-3.
正如在代码中评论的那样,我不确定compareTo中问题的解决方案,其中两个字段匹配相似,但具有不同的值.由于“匹配”代码总是计算0到3之间的值,因此“字段比较”可以有更高的值,我不确定“混合”这些数字是否是个好主意.
有没有人遇到类似的问题,或者可以确认我的解决方案是否符合合同并且没有缺陷?理想情况下,我希望有一个可以由TreeSet使用的比较器,因此,如果人们真的不相等,那么应该只返回0.
我的另一个解决方案是将’pivot’作为“普通”“Person”对象放在树集中,并使用一个简单的比较器,该比较器基于提供给compareTo方法的两个人的字段.对集合进行排序后,我可以搜索pivot对象,然后我知道它附近的元素具有最高匹配.然而,这种解决方案听起来并不优雅,并且可能并不总是适用.
解决方法:
你的问题归结为:比较器是否会产生一个总数(在精确的数学意义上)的排序?
我相信它确实如此.首先将所有值映射到0到3之间的范围.这是排序的最重要属性,因此首先对其进行测试.现在,如果它们不同,则使用整数差异来指示“完全”正常的排序.如果它们是相同的,则首先按名字排序,然后按姓氏开始按字典顺序排序.词典排序当然是完整的.所以你再好了.
正如在其他答案中所说,没有其他问题.您不必担心比较器返回的int的实际大小.
非常重要,但是你没有在这里显示,当且仅当compareTo返回0时,Person上的equals方法应该返回true.如果两个Persons具有相同的名字和姓氏,则compareTo方法只能返回0.所以,如果这是真的,那么equals也应该这样做.检查一下.好.然后是另一个方向.检查没有其他场合你的等于返回0.完成.
最后,如果你不相信你的推理,那么存在一种相当好的测试方法.创建随机人员生成器,生成人员和三人组,并测试数百万组合的总排序规则.即如果a<那么!(b< a)等等.如果我们确实遗漏了某些东西,那么这个设置的几次运行可能会指出我们推理中的缺陷.