Bir listeyi farklı zaman aralıklarında farklı parametrelere göre nasıl sıralayabilirim


95

PersonBirden çok özelliğe sahip bir sınıfım var , örneğin:

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

Bir çok Personnesne bir ArrayList<Person>. Bu listeyi birden çok sıralama parametresine göre ve zaman zaman farklı olarak sıralamak istiyorum. Örneğin, bir keresinde nameyükselen sonra addressalçalan ve başka bir sefer de alçalan sıralama yapmak isteyebilirim id.

Ve kendi sıralama yöntemlerimi oluşturmak istemiyorum (yani kullanmak istiyorum Collections.sort(personList, someComparator). Bunu başaran en zarif çözüm nedir?

Yanıtlar:


192

Enum yaklaşımınızın temelde sağlam olduğunu düşünüyorum, ancak switch ifadelerinin gerçekten daha nesne yönelimli bir yaklaşıma ihtiyacı var. Düşünmek:

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;
            }
        };
    }
}

Bir kullanım örneği (statik içe aktarmalı).

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

12
+1 akıllı numaralandırma kullanımı. "Azalan" ve "Bileşik" numaralandırmalarla yaptığınız zarif kombinasyonu seviyorum. Boş değerler işleminin eksik olduğunu sanıyorum, ancak "azalan" ile aynı şekilde eklemek kolaydır.
KLE

1
Düşünceye yiyecek sağlayan birçok güzel cevap. Açık bir alternatif olarak hiçbir cevap göze çarpmadığından, bunu kabul edeceğim çünkü zarafeti seviyorum, ancak bu yanıtı gören herkesi diğer yaklaşımları da kontrol etmeye çağırıyorum.
runaros

1
@TheLittleNaruto, karşılaştırma yöntemi o2 büyükse negatif bir sayı, o1 büyükse pozitif bir sayı ve eşitse sıfır döndürür. -1 ile çarpmak, sonucu tersine çevirir; bu, azalan fikirdir (normal artan düzenin tersi), eşitse sıfır olarak bırakılır.
Yishai

5
Java 8'den beri comparator.reversed()alçalma için ve comparator1.thenComparing(comparator2)zincirleme karşılaştırıcılar için kullanabileceğinizi unutmayın .
GuiSim

1
@JohnBaum, ilk karşılaştırıcı sıfır olmayan bir sonuç döndürürse, bu sonuç döndürülür ve zincirin geri kalanı çalıştırılmaz.
Yishai

26

Sıralamak isteyebileceğiniz her özellik için karşılaştırıcılar oluşturabilir ve ardından "karşılaştırıcı zincirleme" :-) deneyebilirsiniz:

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;
    }
}

Muhtemelen bu kullanıldığında bir uyarı alacaksınız (JDK7'de bunu bastırabilmeniz gerekse de).
Tom Hawtin - tackline

Bunu ben de beğendim. Verilen örnekle bunun nasıl kullanılacağına dair bir örnek verebilir misiniz?
runaros

@runaros: KLE'nin cevabındaki karşılaştırıcıları kullanma: Collections.sort (/ * Collection <Person> * / people, new ChainedComparator (NAME_ASC_ADRESS_DESC, ID_DESC));
Janus Troelsen

16

Bunun bir yolu, Comparatorbu örnekte gösterildiği gibi, sıralanacak özelliklerin bir listesini bağımsız değişken olarak alan bir oluşturmaktır .

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;
        }
    }
}

Daha sonra, örneğin şu şekilde kodda kullanılabilir:

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

Evet. Kodunuzun çok genel olmasını istiyorsanız, numaralandırmanız yalnızca okunacak özelliği belirtebilir (özelliği enum adını kullanarak almak için yansıma kullanabilirsiniz) ve geri kalanını ikinci bir numaralandırma ile belirtebilirsiniz: ASC & DESC ve muhtemelen üçüncü (NULL_FIRST veya NULL_LAST).
KLE

8

Bir yaklaşım, e-postaları oluşturmak olacaktır Comparator. Bu bir kütüphane yöntemi olabilir (eminim oralarda bir yerlerde vardır).

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;
        }
        [...]
    };
}

Kullanım:

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

Alternatif olarak, Collections.sortbunun kararlı bir tür olduğunu unutmayın . Performans kesinlikle çok önemli değilse, birincilden önceki ikincil sıra olursunuz.

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

Ancak akıllı yaklaşım, muhtemelen sıfır dahil olmak üzere değişken sayıda karşılaştırıcı içerecek şekilde daha genel hale getirilebilir mi?
runaros

compose(nameComparator, compose(addressComparator, idComparator))Java'nın uzantı yöntemleri varsa, bu biraz daha iyi okunur.
Tom Hawtin - tackline

4

Karşılaştırıcılar bunu çok kolay ve doğal bir şekilde yapmanızı sağlar. Kişi sınıfınızın kendisinde veya ihtiyacınızla ilişkili bir Hizmet sınıfında tek karşılaştırıcı örnekleri oluşturabilirsiniz.
Anonim iç sınıfları kullanma örnekleri:

    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... 

Çok sayıda varsa, karşılaştırıcılarınızın kodunu istediğiniz gibi yapılandırabilirsiniz . Örneğin şunları yapabilirsiniz:

  • başka bir karşılaştırıcıdan miras alır,
  • bazı mevcut karşılaştırıcıları bir araya getiren bir CompositeComparator'a sahip olmak
  • boş durumları işleyen bir NullComparator var, sonra başka bir karşılaştırıcıya delege ediyor
  • vb...

2

Ayırıcıları, cevabınızda olduğu gibi Kişi sınıfıyla birleştirmenin iyi bir fikir olmadığını düşünüyorum, çünkü karşılaştırmayı (genellikle iş odaklı) ve model nesnesini birbirine yakın olacak şekilde birleştiriyor. Sıralayıcıdaki bir şeyi her değiştirmek / eklemek istediğinizde, kişi sınıfına dokunmanız gerekir, bu genellikle yapmak istemediğiniz bir şeydir.

KLE'nin önerdiği gibi Karşılaştırıcı örnekleri sağlayan bir Hizmet veya benzeri bir şey kullanmak kulağa çok daha esnek ve genişletilebilir geliyor.


Bana gelince, bu sıkı bir bağlantıya yol açar, çünkü karşılaştırıcıların sahibi sınıfın bir Kişi sınıfının ayrıntılı veri yapısını (temelde Kişi sınıfının hangi alanlarının karşılaştırılacağını) bilmesi ZORUNLUDUR ve Kişiler alanlarında bir şeyi değiştirecekseniz bu aynı izlemeye yol açar. karşılaştırıcılar sınıfındaki değişiklikler. Kişi karşılaştırıcılarının bir Kişi sınıfının parçası olması gerektiğini düşünüyorum. blog.sanaulla.info/2008/06/26/…
Stan

2

Benim yaklaşımım Yishai'nin üzerine inşa edilmiştir. Ana boşluk, bir öznitelik için önce yükselen ve sonra başka bir öznitelik için azalan sıralama yapmanın bir yolu olmamasıdır. Bu, numaralandırma ile yapılamaz. Bunun için dersleri kullandım. Çünkü SortOrder, onu bir iç kişi sınıfı olarak uygulamayı tercih ettiğim türe büyük ölçüde bağlıdır.

'SortOrder' iç sınıfına sahip 'Kişi' sınıfı:

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();
    }
}

Kişi sınıfını ve SortOrder'ı kullanmaya bir örnek:

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());
         }
    }
}

oRUMOo


Bu tür karşılaştırıcılar zincirlemesinin karmaşıklığı ne olabilir? Karşılaştırıcıları her zincirlediğimizde esasen sıralıyor muyuz? Yani her karşılaştırıcı için bir * log (n) işlemi yapıyoruz?
John Baum

0

Yakın zamanda, sınırlandırılmış bir Dize kaydı içindeki birden çok alanı sıralamak için bir Karşılaştırıcı yazdım. Sınırlayıcıyı, kayıt yapısını ve sıralama kurallarını (bazıları türe özgüdür) tanımlamanıza olanak tanır. Bunu, bir Kişi kaydını sınırlandırılmış bir Dizeye dönüştürerek kullanabilirsiniz.

Gerekli bilgiler, program aracılığıyla veya bir XML dosyası aracılığıyla Karşılaştırıcının kendisine ekilir.

XML, pakete gömülü bir XSD dosyasıyla doğrulanır. Örneğin, aşağıda iki alan içeren (ikisi sıralanabilir) sekmeyle ayrılmış bir kayıt düzeni bulunmaktadır:

<?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>

Daha sonra bunu java'da şu şekilde kullanırsınız:

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

Kütüphane burada bulunabilir:

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


0

Bir sınıfın Coordinateolduğunu ve X koordinatına ve Y koordinatına göre her iki şekilde de sıralaması gerektiğini varsayalım . Bunun için iki farklı karşılaştırıcıya ihtiyaç vardır. Aşağıda örnek

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
        };
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.