Làm thế nào để sắp xếp theo hai trường trong Java?


170

Tôi có mảng các đối tượng person (int age; String name;).

Làm thế nào tôi có thể sắp xếp mảng này theo thứ tự abc theo tên và sau đó theo độ tuổi?

Thuật toán nào bạn sẽ sử dụng cho việc này?

Câu trả lời:


221

Bạn có thể sử dụng Collections.sortnhư sau:

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> bây giờ được sắp xếp theo tên, sau đó theo tuổi.

String.compareTo"So sánh hai chuỗi từ vựng" - từ các tài liệu .

Collections.sortlà một phương thức tĩnh trong thư viện Bộ sưu tập gốc. Nó thực hiện sắp xếp thực tế, bạn chỉ cần cung cấp Trình so sánh xác định cách so sánh hai yếu tố trong danh sách của bạn: điều này đạt được bằng cách cung cấp comparephương thức thực hiện của riêng bạn .


10
Bạn cũng có thể thêm một tham số loại Comparatorđể tránh phải bỏ các đầu vào.
biziclop

@Ralph: Tôi đã sửa đổi câu trả lời của mình và thêm một mô tả ngắn gọn.
Richard H

Vì OP đã có lớp đối tượng riêng của họ, nên sẽ có ý nghĩa hơn khi thực hiện Comparable. Xem câu trả lời của @ berry120
Zulaxia

1
Xem lại mã mini: mệnh đề khác là dự phòng vì trả về đầu tiên đóng vai trò là mệnh đề bảo vệ. Câu trả lời tuyệt vời mặc dù, đã làm việc một điều trị cho tôi.
Tom Saleeba

29
Vì câu hỏi / câu trả lời này vẫn được liên kết, xin lưu ý rằng với Java SE 8, việc này trở nên đơn giản hơn nhiều. Nếu có getters bạn có thể viếtComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparingInt(Person::getAge);
Puce

142

Đối với những người có thể sử dụng API phát trực tuyến Java 8, có một cách tiếp cận gọn gàng hơn được ghi lại ở đây: Lambdas và sắp xếp

Tôi đang tìm kiếm tương đương với C # LINQ:

.ThenBy(...)

Tôi đã tìm thấy cơ chế trong Java 8 trên Trình so sánh:

.thenComparing(...)

Vì vậy, đây là đoạn trích thể hiện thuật toán.

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

Kiểm tra liên kết ở trên để biết cách gọn gàng hơn và giải thích về cách suy luận kiểu Java làm cho nó khó hiểu hơn một chút so với LINQ.

Dưới đây là bài kiểm tra đơn vị đầy đủ để tham khảo:

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

Điều gì sẽ là phức tạp của loại chuỗi so sánh này? Có phải chúng ta cơ bản sắp xếp mỗi khi chúng ta xâu chuỗi các bộ so sánh? Vì vậy, chúng tôi làm một hoạt động NlogN cho mỗi so sánh?
John Baum

17
Nếu có getters bạn có thể viếtComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparing(Person::getAge);
Puce

1
Sử dụng thenComparingIntcho tuổi (int)
Puce

Cú pháp với labda '->' không hoạt động với tôi. Người :: getLastName hiện.
Noldy 30/03/2016

Sau khi tạo bộ so sánh, cần tạo một luồng, sắp xếp nó với bộ so sánh và sau đó thu thập nó là gì? Bạn chỉ có thể sử dụng Collections.sort(people, comparator);thay thế?
Anish Sana

105

Sử dụng cách tiếp cận Luồng Java 8 ...

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

Và cách tiếp cận Lambda Java 8 ...

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

Cuối cùng ...

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

19

Bạn cần phải tự thực hiện Comparatorvà sau đó sử dụng nó: ví dụ:

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

Bộ so sánh của bạn có thể trông giống như thế này:

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

Đầu tiên bộ so sánh so sánh các tên, nếu chúng không bằng nhau, nó trả về kết quả từ việc so sánh chúng, nếu không, nó sẽ trả về kết quả so sánh khi so sánh độ tuổi của cả hai người.

Mã này chỉ là một bản nháp: bởi vì lớp này không thay đổi, bạn có thể nghĩ đến việc xây dựng một singleton của nó, thay vào đó tạo ra một thể hiện mới cho mỗi cách sắp xếp.


16

Bạn có thể sử dụng phương pháp Lambda Java 8 để đạt được điều này. Như thế này:

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

15

Comparable<Person>Ví dụ, có lớp người của bạn triển khai và sau đó thực hiện phương thức so sánh:

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

Điều đó sẽ sắp xếp đầu tiên theo tên (trường hợp không nhạy cảm) và sau đó theo tuổi. Sau đó, bạn có thể chạy Arrays.sort()hoặc Collections.sort()trên bộ sưu tập hoặc mảng các đối tượng Person.


Tôi thường thích điều này hơn là tạo Bộ so sánh, vì, như berry120 nói, sau đó bạn có thể sắp xếp với các phương thức được xây dựng, thay vì luôn luôn phải sử dụng Trình so sánh tùy chỉnh của mình.
Zulaxia

4

Quả ổi ComparisonChaincung cấp một cách làm sạch. Tham khảo liên kết này .

Một tiện ích để thực hiện một tuyên bố so sánh chuỗi. Ví dụ:

   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

Bạn có thể làm như thế này:

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

Sử dụng Comparatorvà sau đó đặt các đối tượng vào Collection, sau đóCollections.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

Tạo càng nhiều bộ so sánh khi cần thiết. Sau đó, gọi phương thức "thenCompared" cho mỗi danh mục đơn hàng. Đó là một cách làm của Streams. Xem:

//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));        

Nhìn: Sắp xếp đối tượng do người dùng xác định trên nhiều trường - Trình so sánh (luồng lambda)


3

Tôi sẽ cẩn thận khi sử dụng Guava ComparisonChainvì nó tạo ra một thể hiện của nó cho mỗi phần tử được so sánh để bạn sẽ xem xét việc tạo N x Log Nchuỗi so sánh chỉ để so sánh nếu bạn đang sắp xếp hoặc các Ntrường hợp nếu bạn đang lặp lại và kiểm tra sự bằng nhau.

Thay vào đó, tôi sẽ tạo một tĩnh Comparatorbằng cách sử dụng API Java 8 mới nhất nếu có thể hoặc OrderingAPI của Guava cho phép bạn làm điều đó, đây là một ví dụ với Java 8:

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

Dưới đây là cách sử dụng OrderingAPI của Guava : https://github.com/google/guava/wiki/OrderingExplained


1
"... tạo một thể hiện của nó cho mỗi phần tử được so sánh ..." - điều này không đúng. Ít nhất trong các phiên bản hiện đại của ổi, gọi đến comparephương pháp không tạo ra bất cứ điều gì, nhưng lợi nhuận một trong những trường hợp singleton LESS, GREATERhoặc ACTIVEtùy thuộc vào kết quả của sự so sánh. Đây là một cách tiếp cận được tối ưu hóa cao và không thêm bất kỳ chi phí bộ nhớ hoặc hiệu năng nào.
Yoory N.

Đúng; Bây giờ tôi chỉ nhìn vào mã nguồn, tôi hiểu ý của bạn, nhưng tôi có xu hướng sử dụng API so sánh Java 8 mới hơn cho mục đích phụ thuộc.
Guido Medina

2

Hoặc bạn có thể khai thác thực tế là Collections.sort()(hoặc Arrays.sort()) ổn định (nó không sắp xếp lại các phần tử bằng nhau) và sử dụng một Comparatorđể sắp xếp theo độ tuổi trước và sau đó một phần tử khác để sắp xếp theo tên.

Trong trường hợp cụ thể này, đây không phải là một ý tưởng hay nhưng nếu bạn phải thay đổi thứ tự sắp xếp trong thời gian chạy, nó có thể hữu ích.


2

Bạn có thể sử dụng Trình so sánh nối tiếp chung để sắp xếp các bộ sưu tập theo nhiều trường.

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

}

Phiên bản cập nhật:

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;

   }
 }

Việc xử lý ngoại lệ nullpulum là tốt và làm rõ rằng nó sẽ không hoạt động với null, nhưng dù sao nó cũng sẽ được nâng lên
Ralph

Bạn hoàn toàn đúng. Gần đây tôi đã sử dụng để kiểm tra một số giá trị để sao chép từ nơi này sang nơi khác và bây giờ tôi tiếp tục thực hiện ở mọi nơi.
fmucar

0

Đối với một lớp học Booknhư thế này:

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

sắp xếp lớp chính với các đối tượng giả

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

Tôi không chắc việc viết người đồng hành bên trong lớp Người trong trường hợp này có xấu không. Đã làm như thế này:

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()); 
     }  
}    
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.