Java'da iki alana göre sıralama nasıl yapılır?


171

Bir dizi nesnem var person (int age; String name;).

Bu diziyi alfabetik olarak ada ve sonra yaşa göre nasıl sıralayabilirim?

Bunun için hangi algoritmayı kullanırsınız?

Yanıtlar:


221

Aşağıdaki gibi kullanabilirsiniz Collections.sort:

private static void order(List<Person> persons) {

    Collections.sort(persons, new Comparator() {

        public int compare(Object o1, Object o2) {

            String x1 = ((Person) o1).getName();
            String x2 = ((Person) o2).getName();
            int sComp = x1.compareTo(x2);

            if (sComp != 0) {
               return sComp;
            } 

            Integer x1 = ((Person) o1).getAge();
            Integer x2 = ((Person) o2).getAge();
            return x1.compareTo(x2);
    }});
}

List<Persons> artık ada, sonra yaşa göre sıralanıyor.

String.compareTo"İki dizgiyi sözlükbilimsel olarak karşılaştırır" - dokümanlardan .

Collections.sortyerel Koleksiyonlar kitaplığındaki statik bir yöntemdir. Gerçek sıralamayı yapar, sadece listenizdeki iki öğenin nasıl karşılaştırılması gerektiğini tanımlayan bir Karşılaştırıcı sağlamanız gerekir: bu, compareyöntemi kendi uygulamanızla sağlayarak elde edilir .


10
ComparatorGirişleri yayınlamaktan kaçınmak için bir tür parametresi de ekleyebilirsiniz .
biziclop

@Ralph: Cevabımı değiştirdim ve kısa bir açıklama ekledim.
Richard H

OP zaten kendi nesne sınıfına sahip olduğundan, uygulanması daha mantıklı olacaktır Comparable. @ Berry120
Zulaxia

1
Mini kod incelemesi: ilk cümle gereksizdir çünkü ilk dönüş bir nöbet cümle işlevi görür. Büyük cevap olsa da, benim için bir tedavi çalıştı.
Tom Saleeba

30
Bu soru / cevap hala bağlantılı olduğundan, Java SE 8 ile bunun çok daha basit hale geldiğini lütfen unutmayın. Yazabileceğiniz alıcılar varsaComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparingInt(Person::getAge);
Puce

144

Java 8 akış API'sini kullanabilenler için, burada iyi belgelenmiş daha temiz bir yaklaşım var: Lambdas ve sıralama

C # LINQ eşdeğeri arıyordu:

.ThenBy(...)

Karşılaştırıcıda Java 8'de mekanizmayı buldum:

.thenComparing(...)

İşte algoritmayı gösteren pasaj.

    Comparator<Person> comparator = Comparator.comparing(person -> person.name);
    comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));

Daha düzgün bir yol ve Java'nın tür çıkarımının LINQ'ya kıyasla tanımlamayı biraz daha karmaşık hale getirdiği hakkında bir açıklama için yukarıdaki bağlantıya göz atın.

İşte referans için tam birim testi:

@Test
public void testChainedSorting()
{
    // Create the collection of people:
    ArrayList<Person> people = new ArrayList<>();
    people.add(new Person("Dan", 4));
    people.add(new Person("Andi", 2));
    people.add(new Person("Bob", 42));
    people.add(new Person("Debby", 3));
    people.add(new Person("Bob", 72));
    people.add(new Person("Barry", 20));
    people.add(new Person("Cathy", 40));
    people.add(new Person("Bob", 40));
    people.add(new Person("Barry", 50));

    // Define chained comparators:
    // Great article explaining this and how to make it even neater:
    // http://blog.jooq.org/2014/01/31/java-8-friday-goodies-lambdas-and-sorting/
    Comparator<Person> comparator = Comparator.comparing(person -> person.name);
    comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));

    // Sort the stream:
    Stream<Person> personStream = people.stream().sorted(comparator);

    // Make sure that the output is as expected:
    List<Person> sortedPeople = personStream.collect(Collectors.toList());
    Assert.assertEquals("Andi",  sortedPeople.get(0).name); Assert.assertEquals(2,  sortedPeople.get(0).age);
    Assert.assertEquals("Barry", sortedPeople.get(1).name); Assert.assertEquals(20, sortedPeople.get(1).age);
    Assert.assertEquals("Barry", sortedPeople.get(2).name); Assert.assertEquals(50, sortedPeople.get(2).age);
    Assert.assertEquals("Bob",   sortedPeople.get(3).name); Assert.assertEquals(40, sortedPeople.get(3).age);
    Assert.assertEquals("Bob",   sortedPeople.get(4).name); Assert.assertEquals(42, sortedPeople.get(4).age);
    Assert.assertEquals("Bob",   sortedPeople.get(5).name); Assert.assertEquals(72, sortedPeople.get(5).age);
    Assert.assertEquals("Cathy", sortedPeople.get(6).name); Assert.assertEquals(40, sortedPeople.get(6).age);
    Assert.assertEquals("Dan",   sortedPeople.get(7).name); Assert.assertEquals(4,  sortedPeople.get(7).age);
    Assert.assertEquals("Debby", sortedPeople.get(8).name); Assert.assertEquals(3,  sortedPeople.get(8).age);
    // Andi     : 2
    // Barry    : 20
    // Barry    : 50
    // Bob      : 40
    // Bob      : 42
    // Bob      : 72
    // Cathy    : 40
    // Dan      : 4
    // Debby    : 3
}

/**
 * A person in our system.
 */
public static class Person
{
    /**
     * Creates a new person.
     * @param name The name of the person.
     * @param age The age of the person.
     */
    public Person(String name, int age)
    {
        this.age = age;
        this.name = name;
    }

    /**
     * The name of the person.
     */
    public String name;

    /**
     * The age of the person.
     */
    public int age;

    @Override
    public String toString()
    {
        if (name == null) return super.toString();
        else return String.format("%s : %d", this.name, this.age);
    }
}

Bu tür karşılaştırıcı zincirlemesinin karmaşıklığı ne olurdu? Temel olarak karşılaştırıcıları her zincirlediğimizde sıralama yapıyor muyuz? Yani her karşılaştırıcı için bir NlogN işlemi yapıyoruz?
John Baum

17
Yazabileceğiniz alıcılar varsaComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparing(Person::getAge);
Puce

1
thenComparingIntYaş (int) için kullanın
Puce

Labda ile sözdizimi '->' benim için çalışmıyor. Person :: getLastName yapar.
Noldy

Karşılaştırıcıyı oluşturduktan sonra, bir akış oluşturma, karşılaştırıcıyla sıralama ve sonra toplama ihtiyacı nedir? Onun Collections.sort(people, comparator);yerine kullanabilir misin?
Anish Sana

107

Java 8 Akışları yaklaşımını kullanarak ...

//Creates and sorts a stream (does not sort the original list)       
persons.stream().sorted(Comparator.comparing(Person::getName).thenComparing(Person::getAge));

Java 8 Lambda yaklaşımı ...

//Sorts the original list Lambda style
persons.sort((p1, p2) -> {
        if (p1.getName().compareTo(p2.getName()) == 0) {
            return p1.getAge().compareTo(p2.getAge());
        } else {
            return p1.getName().compareTo(p2.getName());
        } 
    });

Son olarak ...

//This is similar SYNTAX to the Streams above, but it sorts the original list!!
persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));

19

Kendinizinkini uygulamanız Comparatorve sonra kullanmanız gerekir: örneğin

Arrays.sort(persons, new PersonComparator());

Karşılaştırıcınız biraz şöyle görünebilir:

public class PersonComparator implements Comparator<? extends Person> {

  public int compare(Person p1, Person p2) {
     int nameCompare = p1.name.compareToIgnoreCase(p2.name);
     if (nameCompare != 0) {
        return nameCompare;
     } else {
       return Integer.valueOf(p1.age).compareTo(Integer.valueOf(p2.age));
     }
  }
}

Karşılaştırıcı önce adları karşılaştırır, eğer eşit değilse, onları karşılaştırmaktan sonuç döndürür, aksi takdirde her iki kişinin yaşlarını karşılaştırırken karşılaştırma sonucunu döndürür.

Bu kod yalnızca bir taslaktır: sınıf değişmez olduğu için, her bir sıralama için yeni bir örnek oluşturmak yerine bunun bir tektonunu oluşturmayı düşünebilirsiniz.


16

Bunu başarmak için Java 8 Lambda yaklaşımını kullanabilirsiniz. Bunun gibi:

persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));

15

Kişi sınıfınızın Comparable<Person>CompareTo yöntemini uygulamasını ve uygulamasını sağlayın , örneğin:

public int compareTo(Person o) {
    int result = name.compareToIgnoreCase(o.name);
    if(result==0) {
        return Integer.valueOf(age).compareTo(o.age);
    }
    else {
        return result;
    }
}

Bu önce isme (büyük / küçük harf duyarsız) ve sonra yaşa göre sıralanır. Daha sonra çalıştırabilir Arrays.sort()veya Collections.sort()Kişi nesnelerin toplanması veya dizi.


Bunu genellikle bir Karşılaştırıcı yapmaktan çok tercih ederim, çünkü berry120'nin dediği gibi, her zaman özel Karşılaştırıcıyı kullanmak zorunda kalmak yerine yerleşik yöntemlerle sıralayabilirsiniz.
Zulaxia

4

Guava's ComparisonChainbunu yapmanın temiz bir yolunu sunar. Bu bağlantıya bakın .

Zincirleme bir karşılaştırma deyimi gerçekleştirmek için bir yardımcı program. Örneğin:

   public int compareTo(Foo that) {
     return ComparisonChain.start()
         .compare(this.aString, that.aString)
         .compare(this.anInt, that.anInt)
         .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
         .result();
   }

4

Bunu şöyle yapabilirsiniz:

List<User> users = Lists.newArrayList(
  new User("Pedro", 12), 
  new User("Maria", 10), 
  new User("Rafael",12)
);

users.sort(
  Comparator.comparing(User::getName).thenComparing(User::getAge)
);

3

ComparatorNesneleri kullanın ve içine koyun Collection, ardındanCollections.sort();

class Person {

    String fname;
    String lname;
    int age;

    public Person() {
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getFname() {
        return fname;
    }

    public void setFname(String fname) {
        this.fname = fname;
    }

    public String getLname() {
        return lname;
    }

    public void setLname(String lname) {
        this.lname = lname;
    }

    public Person(String fname, String lname, int age) {
        this.fname = fname;
        this.lname = lname;
        this.age = age;
    }

    @Override
    public String toString() {
        return fname + "," + lname + "," + age;
    }
}

public class Main{

    public static void main(String[] args) {
        List<Person> persons = new java.util.ArrayList<Person>();
        persons.add(new Person("abc3", "def3", 10));
        persons.add(new Person("abc2", "def2", 32));
        persons.add(new Person("abc1", "def1", 65));
        persons.add(new Person("abc4", "def4", 10));
        System.out.println(persons);
        Collections.sort(persons, new Comparator<Person>() {

            @Override
            public int compare(Person t, Person t1) {
                return t.getAge() - t1.getAge();
            }
        });
        System.out.println(persons);

    }
}

3

Gerektiği kadar karşılaştırıcı oluşturun. Ardından, her sipariş kategorisi için "thenComparing" yöntemini çağırın. Bu Akışlar tarafından yapmanın bir yolu. Görmek:

//Sort by first and last name
System.out.println("\n2.Sort list of person objects by firstName then "
                                        + "by lastName then by age");
Comparator<Person> sortByFirstName 
                            = (p, o) -> p.firstName.compareToIgnoreCase(o.firstName);
Comparator<Person> sortByLastName 
                            = (p, o) -> p.lastName.compareToIgnoreCase(o.lastName);
Comparator<Person> sortByAge 
                            = (p, o) -> Integer.compare(p.age,o.age);

//Sort by first Name then Sort by last name then sort by age
personList.stream().sorted(
    sortByFirstName
        .thenComparing(sortByLastName)
        .thenComparing(sortByAge)
     ).forEach(person->
        System.out.println(person));        

Görünüm: Kullanıcı tanımlı nesneyi birden çok alanda sırala - Karşılaştırıcı (lambda akışı)


3

Guava'ları kullanırken dikkatli olurum ComparisonChainçünkü karşılaştırılan öğe başına bir örnek oluşturur, bu yüzden N x Log Nsadece sıralama yapıyorsanız karşılaştırmak için bir karşılaştırma zincirleri oluşturmaya ya da Neşitlik için yineliyor ve kontrol ediyorsanız örneklere bakacaksınız.

Bunun yerine Comparatormümkünse en yeni Java 8 API'sını veya Orderingbunu yapmanıza izin veren Guava API'sını kullanarak bir statik oluşturabilirim , burada Java 8 ile bir örnek:

import java.util.Comparator;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.nullsLast;

private static final Comparator<Person> COMPARATOR = Comparator
  .comparing(Person::getName, nullsLast(naturalOrder()))
  .thenComparingInt(Person::getAge);

@Override
public int compareTo(@NotNull Person other) {
  return COMPARATOR.compare(this, other);
}

Guava'nın OrderingAPI'sını şu şekilde kullanabilirsiniz : https://github.com/google/guava/wiki/OrderingExplained


1
"... karşılaştırılan öğe başına bunun bir örneğini oluşturur ..." - bu doğru değil. Modern çağıran Guava sürümlerinde, En azından comparebir şey yaratmaz yöntemle ancak getiri tekil örneklerden birini LESS, GREATERya da ACTIVEkarşılaştırmanın sonucuna göre. Bu oldukça optimize edilmiş bir yaklaşımdır ve bellek veya performans yükü eklemez.
Yoory N.

Evet; Şimdi kaynak koduna baktım, ne demek istediğini anlıyorum, ama bağımlılıklar uğruna yeni Java 8 karşılaştırma API'sini kullanmaya daha meyilli olacağım.
Guido Medina

2

Ya da Collections.sort()(veya Arrays.sort()) kararlı olduğu gerçeğinden faydalanabilir ( eşit olan öğeleri yeniden sıralamaz) ve Comparatorönce yaşa göre sıralamak için a'yı, sonra ada göre sıralamak için başka birini kullanın.

Bu özel durumda, bu çok iyi bir fikir değildir, ancak çalışma zamanında sıralama düzenini değiştirmeniz gerekiyorsa, yararlı olabilir.


2

Koleksiyonları birden çok alana göre sıralamak için genel seri Karşılaştırıcı'yı kullanabilirsiniz.

import org.apache.commons.lang3.reflect.FieldUtils;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
* @author MaheshRPM
*/
public class SerialComparator<T> implements Comparator<T> {
List<String> sortingFields;

public SerialComparator(List<String> sortingFields) {
    this.sortingFields = sortingFields;
}

public SerialComparator(String... sortingFields) {
    this.sortingFields = Arrays.asList(sortingFields);
}

@Override
public int compare(T o1, T o2) {
    int result = 0;
    try {
        for (String sortingField : sortingFields) {
            if (result == 0) {
                Object value1 = FieldUtils.readField(o1, sortingField, true);
                Object value2 = FieldUtils.readField(o2, sortingField, true);
                if (value1 instanceof Comparable && value2 instanceof Comparable) {
                    Comparable comparable1 = (Comparable) value1;
                    Comparable comparable2 = (Comparable) value2;
                    result = comparable1.compareTo(comparable2);
                } else {
                    throw new RuntimeException("Cannot compare non Comparable fields. " + value1.getClass()
                            .getName() + " must implement Comparable<" + value1.getClass().getName() + ">");
                }
            } else {
                break;
            }
        }
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
    return result;
}
}

0
Arrays.sort(persons, new PersonComparator());



import java.util.Comparator;

public class PersonComparator implements Comparator<? extends Person> {

    @Override
    public int compare(Person o1, Person o2) {
        if(null == o1 || null == o2  || null == o1.getName() || null== o2.getName() ){
            throw new NullPointerException();
        }else{
            int nameComparisonResult = o1.getName().compareTo(o2.getName());
            if(0 == nameComparisonResult){
                return o1.getAge()-o2.getAge();
            }else{
                return nameComparisonResult;
            }
        }
    }
}


class Person{
    int age; String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Güncellenmiş versiyon:

public class PersonComparator implements Comparator<? extends Person> {

   @Override
   public int compare(Person o1, Person o2) {

      int nameComparisonResult = o1.getName().compareToIgnoreCase(o2.getName());
      return 0 == nameComparisonResult?o1.getAge()-o2.getAge():nameComparisonResult;

   }
 }

Nullpointer istisna yönetimi güzeldir ve null ile çalışmayacağını açıkça belirtir, ancak yine de yükseltilir
Ralph

Kesinlikle haklısın. Geçenlerde bir yerden bir yere kopyalamak için bazı değerleri kontrol etmek için kullandım ve şimdi her yerde yapmaya devam ediyorum.
fmucar

0

BookBöyle bir sınıf için :

package books;

public class Book {

    private Integer id;
    private Integer number;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getNumber() {
        return number;
    }

    public void setNumber(Integer number) {
        this.number = number;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "book{" +
                "id=" + id +
                ", number=" + number +
                ", name='" + name + '\'' + '\n' +
                '}';
    }
}

ana sınıfı sahte nesnelerle sıralama

package books;

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


public class Main {

    public static void main(String[] args) {
        System.out.println("Hello World!");

        Book b = new Book();

        Book c = new Book();

        Book d = new Book();

        Book e = new Book();

        Book f = new Book();

        Book g = new Book();
        Book g1 = new Book();
        Book g2 = new Book();
        Book g3 = new Book();
        Book g4 = new Book();




        b.setId(1);
        b.setNumber(12);
        b.setName("gk");

        c.setId(2);
        c.setNumber(12);
        c.setName("gk");

        d.setId(2);
        d.setNumber(13);
        d.setName("maths");

        e.setId(3);
        e.setNumber(3);
        e.setName("geometry");

        f.setId(3);
        f.setNumber(34);
        b.setName("gk");

        g.setId(3);
        g.setNumber(11);
        g.setName("gk");

        g1.setId(3);
        g1.setNumber(88);
        g1.setName("gk");
        g2.setId(3);
        g2.setNumber(91);
        g2.setName("gk");
        g3.setId(3);
        g3.setNumber(101);
        g3.setName("gk");
        g4.setId(3);
        g4.setNumber(4);
        g4.setName("gk");





        List<Book> allBooks = new ArrayList<Book>();

        allBooks.add(b);
        allBooks.add(c);
        allBooks.add(d);
        allBooks.add(e);
        allBooks.add(f);
        allBooks.add(g);
        allBooks.add(g1);
        allBooks.add(g2);
        allBooks.add(g3);
        allBooks.add(g4);



        System.out.println(allBooks.size());


        Collections.sort(allBooks, new Comparator<Book>() {

            @Override
            public int compare(Book t, Book t1) {
                int a =  t.getId()- t1.getId();

                if(a == 0){
                    int a1 = t.getNumber() - t1.getNumber();
                    return a1;
                }
                else
                    return a;
            }
        });
        System.out.println(allBooks);

    }


   }

0

Bu durumda karşılaştırıcıyı Person sınıfı içine yazmanın çirkin olup olmadığından emin değilim. Şöyle mi:

public class Person implements Comparable <Person> {

    private String lastName;
    private String firstName;
    private int age;

    public Person(String firstName, String lastName, int BirthDay) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = BirthDay;
    }

    public int getAge() {
        return age;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    @Override
    public int compareTo(Person o) {
        // default compareTo
    }

    @Override
    public String toString() {
        return firstName + " " + lastName + " " + age + "";
    }

    public static class firstNameComperator implements Comparator<Person> {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.firstName.compareTo(o2.firstName);
        }
    }

    public static class lastNameComperator implements Comparator<Person> {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.lastName.compareTo(o2.lastName);
        }
    }

    public static class ageComperator implements Comparator<Person> {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.age - o2.age;
        }
    }
}
public class Test {
    private static void print() {
       ArrayList<Person> list = new ArrayList();
        list.add(new Person("Diana", "Agron", 31));
        list.add(new Person("Kay", "Panabaker", 27));
        list.add(new Person("Lucy", "Hale", 28));
        list.add(new Person("Ashley", "Benson", 28));
        list.add(new Person("Megan", "Park", 31));
        list.add(new Person("Lucas", "Till", 27));
        list.add(new Person("Nicholas", "Hoult", 28));
        list.add(new Person("Aly", "Michalka", 28));
        list.add(new Person("Adam", "Brody", 38));
        list.add(new Person("Chris", "Pine", 37));
        Collections.sort(list, new Person.lastNameComperator());
        Iterator<Person> it = list.iterator();
        while(it.hasNext()) 
            System.out.println(it.next().toString()); 
     }  
}    
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.