带有自定义Comparator的Java PriorityQueue

时间:2017-10-23 14:10:51

标签: java priority-queue

我正在使用PriorityQueue和我自己的Comparator但不知何故最终结果并不总是好的。 我应按等级平均值而不是名字排序,而不是id.no.最后它应该返回队列中剩余的名称。其余的名字很好,但他们的顺序不是。 输入(name,grade avg,id.no):

add John 3,75 50
add Mark 3,8 24
add Shafaet 3,7 35
poll
poll
add Samiha 3,85 36
poll
add Ashley 3,9 42
add Maria 3,6 46
add Anik 3,95 49
add Dan 3,95 50
poll

预期产出:

Dan
Ashley
Shafaet
Maria

我的结果:

Dan
Ashley
Maria
Shafaet
你可以帮我找到问题吗? 提前谢谢!

class StComp implements Comparator<Students> {
        @Override
        public int compare(Students st1, Students st2) {
            if (st1.getCgpa() == st2.getCgpa()) {
                if (st1.getName().equals(st2.getName()))
                    return st1.getId() - st2.getId();
                else
                    return st1.getName().compareTo(st2.getName());
            }
            else
                return (st1.getCgpa() < st2.getCgpa()) ? 1 : -1;
        }
    }

    StComp stComp = new StComp();
    PriorityQueue<Students> pq = new PriorityQueue<Students>(2, stComp);

2 个答案:

答案 0 :(得分:6)

您的Comparator是正确的。问题是您最有可能使用其Iterator遍历列表。 PriorityQueue documentation州:

  

不保证方法iterator()中提供的迭代器   以任何特定顺序遍历优先级队列的元素。

如果你像这样迭代你的PriorityQueue,你应该看到正确的结果:

while (!pq.isEmpty())
    System.out.println(pq.poll().getName());
}

我在这个答案的最后列举了一个例子来充分展示。

如果您不想清除PriorityQueue,可以执行以下几项操作。我个人不推荐这两种方法,因为PriorityQueue的初始选择对于用例来说是不正确的,因为它们不打算迭代。

您可以将PriorityQueue复制到数组中,使用Comparator实现对其进行排序,迭代已排序的数组,例如:

Student[] students = pq.toArray(new Student[pq.size()]);
Arrays.sort(students, new StComp());
for (Student s : students) {
    System.out.println(s.getName() + " " + s.getCgpa() + " " + s.getId());
}

或在轮询时将它们添加到某种Collection,然后将它们添加回PriorityQueue,例如:

Collection<Student> temp = new LinkedList<>();
while (!pq.isEmpty()) {
    Student s = pq.poll();
    System.out.println(s.getName() + " " + s.getCgpa() + " " + s.getId());
    temp.add(s);
}
pq.addAll(temp);

使用您的数据演示的示例:

主要

public class Main {

    public static void main(String[] args) {
        PriorityQueue<Student> pq = new PriorityQueue<>(new StComp());
        pq.add(new Student("John", 75, 50)); // Student name, grade average, id
        pq.add(new Student("Mark", 8, 24));
        pq.add(new Student("Shafaet", 7, 35));
        pq.poll();
        pq.poll();
        pq.add(new Student("Samiha", 85, 36));
        pq.poll();
        pq.add(new Student("Ashley", 9, 42));
        pq.add(new Student("Maria", 6, 46));
        pq.add(new Student("Anik", 95, 49));
        pq.add(new Student("Dan", 95, 50));
        pq.poll();

        // Not guaranteed to be in priorty order
        System.out.println("Using PriorityQueue's Iterator, may not be in the correct priority order.");
        for (Student s : pq) {
            System.out.println(s.getName() + " " + s.getCgpa() + " " + s.getId());
        }

        // Correct order, but removes from the Priority Queue
        System.out.println("\nIterating until empty using PriorityQueue.poll(), will be in the correct order.");
        while (!pq.isEmpty()) {
            Student s = pq.poll();
            System.out.println(s.getName() + " " + s.getCgpa() + " " + s.getId());
        }
    }

}

学生(已重命名,应为单数)

public class Student {

    private double cgpa;
    private String name;
    private int id;

    public Student(String name, double cgpa, int id) {
        this.name = name;
        this.cgpa = cgpa;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    public double getCgpa() {
        return cgpa;
    }

}

StComp (逻辑与问题无关)

public class StComp implements Comparator<Student> {

    @Override
    public int compare(Student st1, Student st2) {
        if (st1.getCgpa() == st2.getCgpa()) {
            if (st1.getName().equals(st2.getName())) {
                return st1.getId() - st2.getId();
            } else {
                return st1.getName().compareTo(st2.getName());
            }
        } else {
            return (st1.getCgpa() < st2.getCgpa()) ? 1 : -1;
        }
    }
}

输出(至少对我来说,第一个Iterator变体的结果可能会有所不同)

Using PriorityQueue's Iterator, may not be in the correct priority order.
Dan 95.0 50
Ashley 9.0 42
Maria 6.0 46
Shafaet 7.0 35

Iterating until empty using PriorityQueue.poll(), will be in the correct order.
Dan 95.0 50
Ashley 9.0 42
Shafaet 7.0 35
Maria 6.0 46

答案 1 :(得分:1)

从Java 8开始,您可以用以下代码替换整个Comparator类:

Comparator.comparingDouble(Students::getCgpa)
    .thenComparing(Students::getName)
    .thenComparingInt(Students::getId)

如果您使用的是旧版本的Java,或者坚持保留显式比较器,则必须为相等的值返回零。您还必须编写比较器,使其与一致。来自documentation

  

比较器c对一组元素S施加的顺序被认为与equals 一致,当且仅当c.compare(e1, e2)==0具有相同的对于e1.equals(e2)中的每个e1e2,布尔值为S

     

当使用能够强加与equals不一致的排序的比较器来排序有序集(或有序映射)时,应该小心。假设具有显式比较器c的有序集(或有序映射)与从集合S中提取的元素(或键)一起使用。如果cS强加的排序与equals不一致,则排序集(或排序映射)将表现得“奇怪”。特别是有序集(或有序映射)将违反集合(或映射)的一般合同,该合同是根据equals定义的。

(简而言之,如果两个对象相等,比较器在比较它们时必须返回零。)

@Override
public int compare(Students st1, Students st2) {
    int comparison = Double.compare(st1.getCgpa(), st2.getCgpa());
    if (comparison == 0) {
        comparison = st1.getName().compareTo(st2.getName());
    }
    if (comparison == 0) {
        comparison = st1.getId() - st2.getId();
    }
    return comparison;
}

这假设您的Students类具有匹配的equals方法:

@Override
public boolean equals(Object obj) {
    if (obj instanceof Students) {
        Students other = (Students) obj;
        return Double.compare(this.getCga(), other.getCga()) == 0
            && this.getName().equals(other.getName())
            && this.getId() == other.getId();
    }
    return false;
}