web-dev-qa-db-ja.com

Javaカスタムコンパレータ付きPriorityQueue

私はPriorityQueueと自分のコンパレータを使用していますが、どういうわけか、最終結果が常に良いとは限りません。私はグレード平均、名前、id.noで並べ替える必要があります。最後に、順序付けされたキューに残っている名前を返します。残りの名前は問題ありませんが、順序は問題です。入力(名前、グレード平均、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);
7
Kokufuu

あなたの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());
        }
    }

}

Student(名前を変更し、単数形にする必要があります)

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
6
d.j.brown

Java 8以降、Comparatorクラス全体を次のように置き換えることができます。

_Comparator.comparingDouble(Students::getCgpa)
    .thenComparing(Students::getName)
    .thenComparingInt(Students::getId)
_

古いバージョンのJavaを使用している場合、または明示的なコンパレータを維持するように要求する場合は、等しい値に対してゼロを返す必要があります。また、 equals.と一致するようにコンパレータを記述する必要があります。/ documentation から:

一連の要素cにコンパレータSが課す順序付けは、 equals と一貫していると言われ、c.compare(e1, e2)==0は、Sのすべての_e1_および_e2_のe1.equals(e2)と同じブール値を持っています。

並べ替えられたセット(または並べ替えられたマップ)を並べ替えるために、equalsと矛盾する並べ替えを強制できるコンパレータを使用する場合は注意が必要です。明示的なコンパレータcを使用してソートされたセット(またはソートされたマップ)が、セットSから取り出された要素(またはキー)で使用されているとします。 cによってSに課された順序が等号と一致しない場合、ソートされたセット(またはソートされたマップ)は「奇妙な」動作をします。特に、ソートされたセット(またはソートされたマップ)は、equalsで定義されているセット(またはマップ)の一般規約に違反します。

(要するに、2つのオブジェクトが等しい場合、Comparatorはそれらを比較するときにゼロを返す必要があります。)

_@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;
}
_
1
VGR