複数のフィールドを持つCollections.sort


83

3つのフィールド(すべての文字列タイプ)を持つ「レポート」オブジェクトのリストがあります-

ReportKey
StudentNumber
School

私はソートコードが次のようになります-

Collections.sort(reportList, new Comparator<Report>() {

@Override
public int compare(final Report record1, final Report record2) {
      return (record1.getReportKey() + record1.getStudentNumber() + record1.getSchool())                      
        .compareTo(record2.getReportKey() + record2.getStudentNumber() + record2.getSchool());
      }

});

どういうわけか、並べ替え順がありません。フィールド間にスペースを入れることをお勧めしますが、なぜですか?

コードに何か問題がありますか?


それらは固定長フィールドですか?record1.getReportKey()が「AB」でrecord1.getStudentNumber()が「CD」であるが、record2.getReportKey()が「ABCD」の場合はどうなりますか?
mellamokb 2010年

固定長。申し訳ありませんが言及するのを忘れました。
Milli Szabo 2010年

回答:


136

コードに何か問題がありますか?

はい。比較する前に、なぜ3つのフィールドを足し合わせているのですか?

私はおそらく次のようなことをするでしょう:(フィールドがあなたがそれらをソートしたい順序であると仮定して)

@Override public int compare(final Report record1, final Report record2) {
    int c;
    c = record1.getReportKey().compareTo(record2.getReportKey());
    if (c == 0)
       c = record1.getStudentNumber().compareTo(record2.getStudentNumber());
    if (c == 0)
       c = record1.getSchool().compareTo(record2.getSchool());
    return c;
}

詳しく説明してください。それではどうすればよいですか?ありがとう。
Milli Szabo 2010年

こんにちは、(c == 0)の場合はさらに追加できますか?最初の条件が満たされた場合には、第二または第三..など。入ることはありませんので、正しいですが、何のことを思わない場合、私は知らない

10
私はあなたが理解したとは思わないa.compareTo(b); 条約は0の値は、平等を表すことで、負の整数は、を表しa < b、正の整数ことを示すa > bためにComparable a及びb
ジェイソンS

1
動いていない。私はそれを試しましたが、機能していません。それは1つだけで動作しますcompareTo
shimatai 2017年

107

(元々、複数のフィールドに基づいてJavaでオブジェクトのリストをソートする方法から

この要点の元の作業コード

Java 8ラムダの使用(2019年4月10日追加)

Java 8は、ラムダによってこれをうまく解決します(ただし、GuavaとApache Commonsはさらに柔軟性を提供する可能性があります)。

Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
            .thenComparing(Report::getStudentNumber)
            .thenComparing(Report::getSchool));

以下の@gaoagongの回答に感謝します

ここでの利点の1つは、ゲッターが遅延getSchool()評価されることです(たとえば、関連する場合にのみ評価されます)。

乱雑で複雑:手作業による並べ替え

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        int sizeCmp = p1.size.compareTo(p2.size);  
        if (sizeCmp != 0) {  
            return sizeCmp;  
        }  
        int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings);  
        if (nrOfToppingsCmp != 0) {  
            return nrOfToppingsCmp;  
        }  
        return p1.name.compareTo(p2.name);  
    }  
});  

これには多くの入力とメンテナンスが必要であり、エラーが発生しやすくなります。唯一の利点は、ゲッターが関連する場合にのみ呼び出されることです。

反射的な方法:BeanComparatorを使用した並べ替え

ComparatorChain chain = new ComparatorChain(Arrays.asList(
   new BeanComparator("size"), 
   new BeanComparator("nrOfToppings"), 
   new BeanComparator("name")));

Collections.sort(pizzas, chain);  

明らかにこれはより簡潔ですが、代わりに文字列を使用してフィールドへの直接参照を失うため、エラーが発生しやすくなります(タイプセーフなし、自動リファクタリング)。これで、フィールドの名前が変更されても、コンパイラは問題を報告しません。さらに、このソリューションはリフレクションを使用するため、並べ替えははるかに遅くなります。

行き方:GoogleGuavaのComparisonChainを使用した並べ替え

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result();  
        // or in case the fields can be null:  
        /* 
        return ComparisonChain.start() 
           .compare(p1.size, p2.size, Ordering.natural().nullsLast()) 
           .compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast()) 
           .compare(p1.name, p2.name, Ordering.natural().nullsLast()) 
           .result(); 
        */  
    }  
});  

これははるかに優れていますが、最も一般的なユースケースではボイラープレートコードが必要です。デフォルトでは、null値の値を小さくする必要があります。nullフィールドの場合、その場合に何をするかをGuavaに追加のディレクティブを提供する必要があります。これは、特定のことを実行したい場合は柔軟なメカニズムですが、多くの場合、デフォルトのケース(1、a、b、z、null)が必要です。

そして、以下のコメントに記載されているように、これらのゲッターはすべて、比較ごとにすぐに評価されます。

Apache CommonsCompareToBuilderを使用した並べ替え

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison();  
    }  
});  

GuavaのComparisonChainと同様に、このライブラリクラスは複数のフィールドで簡単に並べ替えられますが、null値(1、a、b、z、null)のデフォルトの動作も定義します。ただし、独自のコンパレータを提供しない限り、他に何も指定することはできません。

繰り返しになりますが、以下のコメントに記載されているように、これらのゲッターはすべて、比較ごとにすぐに評価されます。

したがって、

最終的には、フレーバーと柔軟性の必要性(GuavaのComparisonChain)と簡潔なコード(ApacheのCompareToBuilder)の違いになります。

ボーナス方式

CodeReviewの優先順位に従って複数のコンパレータを組み合わせた優れたソリューションを見つけましたMultiComparator

class MultiComparator<T> implements Comparator<T> {
    private final List<Comparator<T>> comparators;

    public MultiComparator(List<Comparator<? super T>> comparators) {
        this.comparators = comparators;
    }

    public MultiComparator(Comparator<? super T>... comparators) {
        this(Arrays.asList(comparators));
    }

    public int compare(T o1, T o2) {
        for (Comparator<T> c : comparators) {
            int result = c.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }

    public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
        Collections.sort(list, new MultiComparator<T>(comparators));
    }
}

もちろん、ApacheCommonsCollectionsにはすでにこれに役立つユーティリティがあります。

ComparatorUtils.chainedComparator(comparatorCollection)

Collections.sort(list, ComparatorUtils.chainedComparator(comparators));

クリーンなコードのための完璧なソリューションであり、目的も果たします
cryptonkid 2018

Java 8ラムダに最適
率直な

素晴らしい答えをありがとうベニー。プロパティがオブジェクト内に直接ないシナリオがあります。しかし、ネストされたオブジェクトがあります。その場合はどうすればいいですか?たとえば、ここでは、Collections.sort(reportList、Comparator.comparing(Report :: getReportKey).thenComparing(Report :: getStudentNumber).thenComparing(Report :: getSchool));のようになります。レポートオブジェクトの内部には学生オブジェクトがあり、次に学生オブジェクトの内部には学生番号があります。その場合、ここでどのようにソートしますか?どんな助けでもいただければ幸いです。
Madhu Reddy

@MadhuReddyサンプルのメソッドでは、参照がラムダとして使用されますが、代わりに適切なネストされたフィールドを返す適切なラムダを提供することもできます。
ベニーボッテマ

44

私が使用して、コンパレータを作ると思いますグアバさんをComparisonChain

public class ReportComparator implements Comparator<Report> {
  public int compare(Report r1, Report r2) {
    return ComparisonChain.start()
        .compare(r1.getReportKey(), r2.getReportKey())
        .compare(r1.getStudentNumber(), r2.getStudentNumber())
        .compare(r1.getSchool(), r2.getSchool())
        .result();
  }
}

20

これは古い質問なので、Java8に相当するものは見当たりません。これは、この特定のケースの例です。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Compares multiple parts of the Report object.
 */
public class SimpleJava8ComparatorClass {

    public static void main(String[] args) {
        List<Report> reportList = new ArrayList<>();
        reportList.add(new Report("reportKey2", "studentNumber2", "school1"));
        reportList.add(new Report("reportKey4", "studentNumber4", "school6"));
        reportList.add(new Report("reportKey1", "studentNumber1", "school1"));
        reportList.add(new Report("reportKey3", "studentNumber2", "school4"));
        reportList.add(new Report("reportKey2", "studentNumber2", "school3"));

        System.out.println("pre-sorting");
        System.out.println(reportList);
        System.out.println();

        Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
            .thenComparing(Report::getStudentNumber)
            .thenComparing(Report::getSchool));

        System.out.println("post-sorting");
        System.out.println(reportList);
    }

    private static class Report {

        private String reportKey;
        private String studentNumber;
        private String school;

        public Report(String reportKey, String studentNumber, String school) {
            this.reportKey = reportKey;
            this.studentNumber = studentNumber;
            this.school = school;
        }

        public String getReportKey() {
            return reportKey;
        }

        public void setReportKey(String reportKey) {
            this.reportKey = reportKey;
        }

        public String getStudentNumber() {
            return studentNumber;
        }

        public void setStudentNumber(String studentNumber) {
            this.studentNumber = studentNumber;
        }

        public String getSchool() {
            return school;
        }

        public void setSchool(String school) {
            this.school = school;
        }

        @Override
        public String toString() {
            return "Report{" +
                   "reportKey='" + reportKey + '\'' +
                   ", studentNumber='" + studentNumber + '\'' +
                   ", school='" + school + '\'' +
                   '}';
        }
    }
}

comparingそしてthenComparing勝利のために!
asgs 2016年

1
それは24.最小のAndroidバージョンを要求します
マムシ

14

レポートキー、学生番号、学校の順に並べ替える場合は、次のようにする必要があります。

public class ReportComparator implements Comparator<Report>
{
    public int compare(Report r1, Report r2)
    {
        int result = r1.getReportKey().compareTo(r2.getReportKey());
        if (result != 0)
        {
            return result;
        }
        result = r1.getStudentNumber().compareTo(r2.getStudentNumber());
        if (result != 0)
        {
            return result;
        }
        return r1.getSchool().compareTo(r2.getSchool());
    }
}

もちろん、これはどの値もnullにできないことを前提としています。レポート、レポートキー、学生番号、または学校にnull値を許可する必要がある場合は、さらに複雑になります。

あなたが間にできた空間を利用して仕事に文字列連結バージョンを取得しますが、それ自体は、上記のコードがあるなど、スペースを含ま奇数データがあった場合、それはまだ奇妙なケースでは失敗した論理希望のコードを...最初のレポートキーによって比較し、レポートキーが同じである場合などにのみ、学生番号を気にします。


6
このコードには「間違い」はありませんが、私はそれを理解しています。Jasonのreturnステートメントは1つしかないため、Jasonの実装の方がわかりやすいので、私はJasonの実装を好みます。
jzd 2010年

あなたのコードを使おうとすると、compareTo()メソッドが使えません。問題の解決を手伝っていただけませんか。
viper 2017

@viper:いいえ、実装していないのでComparable<T>、実装していComparator<T>ます。あなたが何を達成しようとしているのか、何を試みたのか、何がうまくいかなかったのかはわかりません。おそらく、さらに調査を行った後、新しい質問をする必要があります。(すでに質問されている可能性があります。)
Jon Skeet 2017

7

Java 8Lambdaアプローチを使用することをお勧めします。

List<Report> reportList = new ArrayList<Report>();
reportList.sort(Comparator.comparing(Report::getRecord1).thenComparing(Report::getRecord2));

5

Java8の複数のフィールドでの並べ替え

package com.java8.chapter1;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static java.util.Comparator.*;



 public class Example1 {

    public static void main(String[] args) {
        List<Employee> empList = getEmpList();


        // Before Java 8 
        empList.sort(new Comparator<Employee>() {

            @Override
            public int compare(Employee o1, Employee o2) {
                int res = o1.getDesignation().compareTo(o2.getDesignation());
                if (res == 0) {
                    return o1.getSalary() > o2.getSalary() ? 1 : o1.getSalary() < o2.getSalary() ? -1 : 0;
                } else {
                    return res;
                }

            }
        });
        for (Employee emp : empList) {
            System.out.println(emp);
        }
        System.out.println("---------------------------------------------------------------------------");

        // In Java 8

        empList.sort(comparing(Employee::getDesignation).thenComparing(Employee::getSalary));
        empList.stream().forEach(System.out::println);

    }
    private static List<Employee> getEmpList() {
        return Arrays.asList(new Employee("Lakshman A", "Consultent", 450000),
                new Employee("Chaitra S", "Developer", 250000), new Employee("Manoj PVN", "Developer", 250000),
                new Employee("Ramesh R", "Developer", 280000), new Employee("Suresh S", "Developer", 270000),
                new Employee("Jaishree", "Opearations HR", 350000));
    }
}

class Employee {
    private String fullName;
    private String designation;
    private double salary;

    public Employee(String fullName, String designation, double salary) {
        super();
        this.fullName = fullName;
        this.designation = designation;
        this.salary = salary;
    }

    public String getFullName() {
        return fullName;
    }

    public String getDesignation() {
        return designation;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return "Employee [fullName=" + fullName + ", designation=" + designation + ", salary=" + salary + "]";
    }

}

empList.sort(comparing(Employee::getDesignation).thenComparing(Employee::getSalary));このコードは私を助けました。ありがとう
Sumit Badaya 2017年

これは、最小のAndroidバージョン24を使用するように要求
マムシ

4

StudentNumberが数値の場合、数値ではなく英数字でソートされます。期待しないでください

"2" < "11"

そうなる:

"11" < "2"

これは、なぜ結果が間違っているのかという実際の質問に答えます。
フロリアンF

3

最初にReportKey、次に学生番号、次に学校に基づいて並べ替える場合は、各文字列を連結するのではなく、比較する必要があります。各ReportKeyが同じ長さになるように文字列をスペースで埋めると、このメソッドは機能する可能性がありますが、実際には努力する価値はありません。代わりに、compareメソッドを変更してReportKeysを比較します。compareToが0を返した場合は、StudentNumber、Schoolの順に試してください。


3

ComparatorJDK1.8で導入されたメソッドとのインターフェースを使用します:comparingおよびthenComparing、またはより具体的なメソッド:comparingXXXおよびthenComparingXXX

たとえば、最初にIDで人のリストを並べ替え、次に年齢、次に名前で並べ替える場合は、次のようになります。

            Comparator<Person> comparator = Comparator.comparingLong(Person::getId)
                    .thenComparingInt(Person::getAge)
                    .thenComparing(Person::getName);
            personList.sort(comparator);

0

これは、オブジェクト内の2つのフィールド(1つはString、もう1つはint)を比較し、Collat​​orを使用して並べ替える完全な例です。

public class Test {

    public static void main(String[] args) {

        Collator myCollator;
        myCollator = Collator.getInstance(Locale.US);

        List<Item> items = new ArrayList<Item>();

        items.add(new Item("costrels", 1039737, ""));
        items.add(new Item("Costs", 1570019, ""));
        items.add(new Item("costs", 310831, ""));
        items.add(new Item("costs", 310832, ""));

        Collections.sort(items, new Comparator<Item>() {
            @Override
            public int compare(final Item record1, final Item record2) {
                int c;
                //c = record1.item1.compareTo(record2.item1); //optional comparison without Collator                
                c = myCollator.compare(record1.item1, record2.item1);
                if (c == 0) 
                {
                    return record1.item2 < record2.item2 ? -1
                            :  record1.item2 > record2.item2 ? 1
                            : 0;
                }
                return c;
            }
        });     

        for (Item item : items)
        {
            System.out.println(item.item1);
            System.out.println(item.item2);
        }       

    }

    public static class Item
    {
        public String item1;
        public int item2;
        public String item3;

        public Item(String item1, int item2, String item3)
        {
            this.item1 = item1;
            this.item2 = item2;
            this.item3 = item3;
        }       
    }

}

出力:

コストレル1039737

費用310831

費用310832

費用1570019


0

上記の回答の多くは、実際には機能していない単一のコンパレータ方式で比較されたフィールドを持っています。フィールドごとに異なるコンパレータが実装されているにもかかわらず、いくつかの答えがあります。この例は、私が信じていることをはるかに明確かつ簡単に理解できるため、これを投稿します。

class Student{
    Integer bornYear;
    Integer bornMonth;
    Integer bornDay;
    public Student(int bornYear, int bornMonth, int bornDay) {

        this.bornYear = bornYear;
        this.bornMonth = bornMonth;
        this.bornDay = bornDay;
    }
    public Student(int bornYear, int bornMonth) {

        this.bornYear = bornYear;
        this.bornMonth = bornMonth;

    }
    public Student(int bornYear) {

        this.bornYear = bornYear;

    }
    public Integer getBornYear() {
        return bornYear;
    }
    public void setBornYear(int bornYear) {
        this.bornYear = bornYear;
    }
    public Integer getBornMonth() {
        return bornMonth;
    }
    public void setBornMonth(int bornMonth) {
        this.bornMonth = bornMonth;
    }
    public Integer getBornDay() {
        return bornDay;
    }
    public void setBornDay(int bornDay) {
        this.bornDay = bornDay;
    }
    @Override
    public String toString() {
        return "Student [bornYear=" + bornYear + ", bornMonth=" + bornMonth + ", bornDay=" + bornDay + "]";
    }


}
class TestClass
{       

    // Comparator problem in JAVA for sorting objects based on multiple fields 
    public static void main(String[] args)
    {
        int N,c;// Number of threads

        Student s1=new Student(2018,12);
        Student s2=new Student(2018,12);
        Student s3=new Student(2018,11);
        Student s4=new Student(2017,6);
        Student s5=new Student(2017,4);
        Student s6=new Student(2016,8);
        Student s7=new Student(2018);
        Student s8=new Student(2017,8);
        Student s9=new Student(2017,2);
        Student s10=new Student(2017,9);

        List<Student> studentList=new ArrayList<>();
        studentList.add(s1);
        studentList.add(s2);
        studentList.add(s3);
        studentList.add(s4);
        studentList.add(s5);
        studentList.add(s6);
        studentList.add(s7);
        studentList.add(s8);
        studentList.add(s9);
        studentList.add(s10);

        Comparator<Student> byMonth=new Comparator<Student>() {
            @Override
            public int compare(Student st1,Student st2) {
                if(st1.getBornMonth()!=null && st2.getBornMonth()!=null) {
                    return st2.getBornMonth()-st1.getBornMonth();
                }
                else if(st1.getBornMonth()!=null) {
                    return 1;
                }
                else {
                    return -1;
                }
        }};

        Collections.sort(studentList, new Comparator<Student>() {
            @Override
            public int compare(Student st1,Student st2) {
                return st2.getBornYear()-st1.getBornYear();
        }}.thenComparing(byMonth));

        System.out.println("The sorted students list in descending is"+Arrays.deepToString(studentList.toArray()));



    }

}

出力

降順で並べ替えられた学生リストは、[Student [bornYear = 2018、bornMonth = null、bornDay = null]、Student [bornYear = 2018、bornMonth = 12、bornDay = null]、Student [bornYear = 2018、bornMonth = 12、bornDay = null]、Student [bornYear = 2018、bornMonth = 11、bornDay = null]、Student [bornYear = 2017、bornMonth = 9、bornDay = null]、Student [bornYear = 2017、bornMonth = 8、bornDay = null]、Student [ bornYear = 2017、bornMonth = 6、bornDay = null]、Student [bornYear = 2017、bornMonth = 4、bornDay = null]、Student [bornYear = 2017、bornMonth = 2、bornDay = null]、Student [bornYear = 2016、bornMonth = 8、bornDay = null]]

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.