異なるタイミングで異なるパラメーターでリストを並べ替える方法


95

Personたとえば、複数のプロパティを持つ名前のクラスがあります。

public class Person {
    private int id;
    private String name, address;
    // Many more properties.
}

に多くのPersonオブジェクトが保存されますArrayList<Person>。このリストを複数の並べ替えパラメータで並べ替えたいのですが、時々異なります。たとえば、name昇順とaddress降順でソートしたい場合もあれば、降順でソートしたい場合もありますid

また、独自の並べ替えメソッドを作成したくない(つまり、を使用したい)Collections.sort(personList, someComparator)。これを実現する最もエレガントなソリューションは何ですか?

回答:


193

私はあなたの列挙型アプローチは基本的に健全だと思いますが、switchステートメントには、よりオブジェクト指向のアプローチが本当に必要です。検討してください:

enum PersonComparator implements Comparator<Person> {
    ID_SORT {
        public int compare(Person o1, Person o2) {
            return Integer.valueOf(o1.getId()).compareTo(o2.getId());
        }},
    NAME_SORT {
        public int compare(Person o1, Person o2) {
            return o1.getFullName().compareTo(o2.getFullName());
        }};

    public static Comparator<Person> decending(final Comparator<Person> other) {
        return new Comparator<Person>() {
            public int compare(Person o1, Person o2) {
                return -1 * other.compare(o1, o2);
            }
        };
    }

    public static Comparator<Person> getComparator(final PersonComparator... multipleOptions) {
        return new Comparator<Person>() {
            public int compare(Person o1, Person o2) {
                for (PersonComparator option : multipleOptions) {
                    int result = option.compare(o1, o2);
                    if (result != 0) {
                        return result;
                    }
                }
                return 0;
            }
        };
    }
}

使用例(静的インポートを使用)。

public static void main(String[] args) {
    List<Person> list = null;
    Collections.sort(list, decending(getComparator(NAME_SORT, ID_SORT)));
}

12
列挙型のスマートな使用を+1。列挙型とのエレガントな組み合わせ、「降順」と「複合」が好きです。null値の処理は欠落していると思いますが、「降順」と同じ方法を追加するのは簡単です。
KLE、

1
思考の糧を提供する多くの素晴らしい答え。明確な代替案として際立った答えはなかったので、私はエレガントさを気に入っているのでこれを受け入れますが、この答えを見ている人には他のアプローチもチェックするように強く勧めます。
runaros 09/09/15

1
@ TheLittleNaruto、compareメソッドは、o2が大きい場合は負の数を返し、o1が大きい場合は正の数を返し、等しい場合はゼロを返します。-1を乗算すると、結果が逆になります。これは、降順(通常の昇順の逆)の考え方ですが、等しい場合はゼロのままにします。
Yishai

5
Java 8以降comparator.reversed()、降順で使用したり、comparator1.thenComparing(comparator2)コンパレーターのチェーンに使用したりできることに注意してください。
GuiSim

1
@JohnBaum、最初のコンパレータがゼロ以外の結果を返す場合、その結果が返され、チェーンの残りの部分は実行されません。
Yishai、2015年

26

並べ替えたいプロパティごとにコンパレータを作成してから、次のように「コンパレータチェーン」を試すことができます。

public class ChainedComparator<T> implements Comparator<T> {
    private List<Comparator<T>> simpleComparators; 
    public ChainedComparator(Comparator<T>... simpleComparators) {
        this.simpleComparators = Arrays.asList(simpleComparators);
    }
    public int compare(T o1, T o2) {
        for (Comparator<T> comparator : simpleComparators) {
            int result = comparator.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }
}

これを使用すると、おそらく警告が表示されます(JDK7ではそれを抑制できるはずです)。
トム・ホーティン-タックライン

私もこれが好きです。特定の例でこれを使用する方法のサンプルを提供できますか?
runaros 09/09/15

@runaros:KLEの回答からコンパレーターを使用:Collections.sort(/ * Collection <Person> * / people、new ChainedComparator(NAME_ASC_ADRESS_DESC、ID_DESC));
Janus Troelsen、2011年

16

Comparatorこの例が示すように、1つの方法は、ソートするプロパティのリストを引数として取るを作成することです。

public class Person {
    private int id;
    private String name, address;

    public static Comparator<Person> getComparator(SortParameter... sortParameters) {
        return new PersonComparator(sortParameters);
    }

    public enum SortParameter {
        ID_ASCENDING, ID_DESCENDING, NAME_ASCENDING,
        NAME_DESCENDING, ADDRESS_ASCENDING, ADDRESS_DESCENDING
    }

    private static class PersonComparator implements Comparator<Person> {
        private SortParameter[] parameters;

        private PersonComparator(SortParameter[] parameters) {
            this.parameters = parameters;
        }

        public int compare(Person o1, Person o2) {
            int comparison;
            for (SortParameter parameter : parameters) {
                switch (parameter) {
                    case ID_ASCENDING:
                        comparison = o1.id - o2.id;
                        if (comparison != 0) return comparison;
                        break;
                    case ID_DESCENDING:
                        comparison = o2.id - o1.id;
                        if (comparison != 0) return comparison;
                        break;
                    case NAME_ASCENDING:
                        comparison = o1.name.compareTo(o2.name);
                        if (comparison != 0) return comparison;
                        break;
                    case NAME_DESCENDING:
                        comparison = o2.name.compareTo(o1.name);
                        if (comparison != 0) return comparison;
                        break;
                    case ADDRESS_ASCENDING:
                        comparison = o1.address.compareTo(o2.address);
                        if (comparison != 0) return comparison;
                        break;
                    case ADDRESS_DESCENDING:
                        comparison = o2.address.compareTo(o1.address);
                        if (comparison != 0) return comparison;
                        break;
                }
            }
            return 0;
        }
    }
}

次に、たとえば次のようなコードで使用できます。

cp = Person.getComparator(Person.SortParameter.ADDRESS_ASCENDING,
                          Person.SortParameter.NAME_DESCENDING);
Collections.sort(personList, cp);

はい。コードを非常に一般的なものにしたい場合、列挙型は読み取るプロパティのみを指定でき(リフレクションを使用して、列挙型名を使用してプロパティを取得できます)、残りを2番目の列挙型で指定できます:ASC&DESC、およびおそらく3番目(NULL_FIRSTまたはNULL_LAST)。
KLE、

8

1つのアプローチは、Comparators を作成することです。これはライブラリメソッドである可能性があります(それはどこかに存在するはずです)。

public static <T> Comparator<T> compose(
    final Comparator<? super T> primary,
    final Comparator<? super T> secondary
) {
    return new Comparator<T>() {
        public int compare(T a, T b) {
            int result = primary.compare(a, b);
            return result==0 ? secondary.compare(a, b) : result;
        }
        [...]
    };
}

使用する:

Collections.sort(people, compose(nameComparator, addressComparator));

または、これCollections.sortは安定したソートです。パフォーマンスが絶対に重要でない場合は、プライマリの前にセカンダリの順序で並べ替えます。

Collections.sort(people, addressComparator);
Collections.sort(people, nameComparator);

しかし、巧妙なアプローチは、それをより一般的にすることができ、可変数のコンパレーターを含み、おそらくゼロを含むことができるでしょうか?
runaros 09/09/14

compose(nameComparator, compose(addressComparator, idComparator))Javaに拡張メソッドがある場合は、少し読みやすくなります。
トム・ホーティン-タックライン09/09/14

4

コンパレータを使用すると、非常に簡単かつ自然にそれを行うことができます。Personクラス自体、またはニーズに関連付けられたServiceクラスのいずれかに、コンパレーターの単一インスタンスを作成できます。
匿名の内部クラスを使用した例:

    public static final Comparator<Person> NAME_ASC_ADRESS_DESC
     = new Comparator<Person>() {
      public int compare(Person p1, Person p2) {
         int nameOrder = p1.getName().compareTo(p2.getName);
         if(nameOrder != 0) {
           return nameOrder;
         }
         return -1 * p1.getAdress().comparedTo(p2.getAdress());
         // I use explicit -1 to be clear that the order is reversed
      }
    };

    public static final Comparator<Person> ID_DESC
     = new Comparator<Person>() {
      public int compare(Person p1, Person p2) {
         return -1 * p1.getId().comparedTo(p2.getId());
         // I use explicit -1 to be clear that the order is reversed
      }
    };
    // and other comparator instances as needed... 

多くの場合、コンパレータコードを好きなように構成することもできます。たとえば、次のことができます。

  • 別のコンパレータから継承し、
  • 一部の既存のコンパレーターを集約するCompositeComparatorがある
  • nullケースを処理するNullComparatorがあり、別のコンパレータに委任する
  • 等...

2

あなたの答えのように、ソーターをPersonクラスに結合することは、比較(通常はビジネス主導)とモデルオブジェクトを相互に結合するため、良い考えではないと思います。ソーターを変更または追加するたびに、通常は実行したくないpersonクラスに触れる必要があります。

KLEが提案するように、Comparatorインスタンスを提供するサービスまたは類似のものを使用すると、より柔軟で拡張可能に聞こえます。


私に関しては、これは密結合につながります。なぜなら、コンパレーターホルダークラスは、Personクラスの詳細なデータ構造(基本的に、Personクラスのどのフィールドを比較するか)を知っている必要があり、Personsフィールドで何かを変更する場合、これは同じトレースにつながるからですコンパレータクラスの変更。PersonコンパレータはPersonクラスの一部であるべきだと思います。blog.sanaulla.info/2008/06/26/...
スタン・

2

私のアプローチはYishaiのアプローチに基づいています。主なギャップは、最初に属性の昇順でソートし、その後に他の属性の降順でソートする方法がないことです。これは列挙型では実行できません。そのためにクラスを使用しました。SortOrderは型に強く依存するため、個人の内部クラスとして実装することを選択しました。

内部クラス「SortOrder」を持つクラス「Person」:

import java.util.Comparator;

public class Person {
    private int id;
    private String firstName; 
    private String secondName;

    public Person(int id, String firstName, String secondName) {
        this.id = id;
        this.firstName = firstName;
        this.secondName = secondName;   
    }

    public abstract static class SortOrder implements Comparator<Person> {
        public static SortOrder PERSON_ID = new SortOrder() {
            public int compare(Person p1, Person p2) {
                return Integer.valueOf(p1.getId()).compareTo(p2.getId());
            }
        };
        public static SortOrder PERSON_FIRST_NAME = new SortOrder() {
            public int compare(Person p1, Person p2) {
                return p1.getFirstName().compareTo(p2.getFirstName());
            }
        };
        public static SortOrder PERSON_SECOND_NAME = new SortOrder() {
            public int compare(Person p1, Person p2) {
                return p1.getSecondName().compareTo(p2.getSecondName());
            }
        };

        public static SortOrder invertOrder(final SortOrder toInvert) {
            return new SortOrder() {
                public int compare(Person p1, Person p2) {
                    return -1 * toInvert.compare(p1, p2);
                }
            };
        }

        public static Comparator<Person> combineSortOrders(final SortOrder... multipleSortOrders) {
            return new Comparator<Person>() {
                public int compare(Person p1, Person p2) {
                    for (SortOrder personComparator: multipleSortOrders) {
                        int result = personComparator.compare(p1, p2);
                        if (result != 0) {
                            return result;
                        }
                    }
                    return 0;
                }
            };
        }
    }

    public int getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getSecondName() {
        return secondName;
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();

        result.append("Person with id: ");
        result.append(id);
        result.append(" and firstName: ");
        result.append(firstName);
        result.append(" and secondName: ");
        result.append(secondName);
        result.append(".");

        return result.toString();
    }
}

クラスPersonとそのSortOrderの使用例:

import static multiplesortorder.Person.SortOrder.*;

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

import multiplesortorder.Person;

public class Application {

    public static void main(String[] args) {
        List<Person> listPersons = new ArrayList<Person>(Arrays.asList(
                 new Person(0, "...", "..."),
                 new Person(1, "...", "...")
             ));

         Collections.sort(listPersons, combineSortOrders(PERSON_FIRST_NAME, invertOrder(PERSON_ID)));

         for (Person p: listPersons) {
             System.out.println(p.toString());
         }
    }
}

オルモ


この種のコンパレータの連鎖の複雑さは何でしょうか?コンパレータをチェーンするたびに、本質的に並べ替えを行っていますか?したがって、各コンパレータに対して* log(n)操作を実行しますか?
John Baum、2015年

0

私は最近、区切り文字列レコード内の複数のフィールドをソートするコンパレータを作成しました。区切り文字、レコード構造、およびソート規則(一部はタイプ固有のもの)を定義できます。これは、Personレコードを区切り文字列に変換することで使用できます。

必要な情報は、プログラムまたはXMLファイルを介して、コンパレータ自体にシードされます。

XMLは、パッケージに埋め込まれたXSDファイルによって検証されます。たとえば、以下は4つのフィールド(そのうち2つはソート可能)を含むタブ区切りのレコードレイアウトです。

<?xml version="1.0" encoding="ISO-8859-1"?> 
<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <delimiter>&#009;</delimiter>

    <column xsi:type="Decimal">
        <name>Column One</name>
    </column>

    <column xsi:type="Integer">
        <name>Column Two</name>
    </column>

    <column xsi:type="String">
        <name>Column Three</name>
        <sortOrder>2</sortOrder>
        <trim>true</trim>
        <caseSensitive>false</caseSensitive>        
        <stripAccents>true</stripAccents>
    </column>

    <column xsi:type="DateTime">
        <name>Column Four</name>
        <sortOrder>1</sortOrder>
        <ascending>true</ascending>
        <nullLowSortOrder>true</nullLowSortOrder>
        <trim>true</trim>
        <pattern>yyyy-MM-dd</pattern>
    </column>

</row>

次に、これを次のようにJavaで使用します。

Comparator<String> comparator = new RowComparator(
              new XMLStructureReader(new File("layout.xml")));

ライブラリはここにあります:

http://sourceforge.net/projects/multicolumnrowcomparator/


0

クラスCoordinateがあり、X座標とY座標に従って両方の方法でクラスをソートする必要があるとします。そのためには2つの異なるコンパレータが必要です。以下はサンプルです

class Coordinate
{

    int x,y;

    public Coordinate(int x, int y) {
        this.x = x;
        this.y = y;
    }

    static Comparator<Coordinate> getCoordinateXComparator() {
        return new Comparator<Coordinate>() {

            @Override
            public int compare(Coordinate Coordinate1, Coordinate Coordinate2) {
                if(Coordinate1.x < Coordinate2.x)
                    return 1;
                else
                    return 0;
            }
            // compare using Coordinate x
        };
    }

    static Comparator<Coordinate> getCoordinateYComparator() {
        return new Comparator<Coordinate>() {

            @Override
            public int compare(Coordinate Coordinate1, Coordinate Coordinate2) {
                if(Coordinate1.y < Coordinate2.y)
                    return 1;
                else
                    return 0;
            }
            // compare using Coordinate y
        };
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.