ฉันจะจัดเรียงรายการตามพารามิเตอร์ต่างๆในเวลาที่ต่างกันได้อย่างไร


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)อะไรคือวิธีการแก้ปัญหาที่สวยงามที่สุดที่บรรลุสิ่งนี้?

คำตอบ:


192

ฉันคิดว่าวิธีการ enum ของคุณนั้นฟังดูดี แต่คำสั่ง 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 การใช้ enums อย่างชาญฉลาด ฉันชอบการผสมผสานที่หรูหราที่คุณทำกับ enums "จากมากไปน้อย" และ "คอมโพสิต" ฉันเดาว่าการรักษาค่าว่างหายไป แต่ง่ายต่อการเพิ่มวิธีเดียวกับ "จากมากไปหาน้อย"
KLE

1
คำตอบที่ดีมากมายให้อาหารสำหรับความคิด เนื่องจากไม่มีคำตอบใดที่เป็นทางเลือกที่ชัดเจนฉันจะยอมรับข้อนี้เพราะฉันชอบความสง่างาม แต่ฉันขอให้ทุกคนที่ดูคำตอบนี้ตรวจสอบแนวทางอื่น ๆ ด้วย
runaros

1
@TheLittleNaruto วิธีการเปรียบเทียบจะส่งคืนจำนวนลบหาก o2 มากกว่าค่าบวกถ้า o1 มากกว่าและเป็นศูนย์หากมีค่าเท่ากัน การคูณด้วย -1 จะกลับผลลัพธ์ซึ่งเป็นแนวคิดของการหลอกลวง (ตรงข้ามกับลำดับจากน้อยไปหามาก) ในขณะที่ปล่อยให้มันเป็นศูนย์ถ้ามันเท่ากัน
Yishai

5
โปรดทราบว่าตั้งแต่ Java 8 คุณสามารถใช้comparator.reversed()สำหรับการลดลงและคุณสามารถใช้comparator1.thenComparing(comparator2)สำหรับการเปรียบเทียบการเชื่อมโยง
GuiSim

1
@JohnBaum หากตัวเปรียบเทียบตัวแรกส่งคืนผลลัพธ์ที่ไม่ใช่ศูนย์ผลลัพธ์นั้นจะถูกส่งกลับและส่วนที่เหลือของห่วงโซ่จะไม่ดำเนินการ
Yishai

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 คุณควรจะสามารถระงับได้)
Tom Hawtin - แทคไลน์

ฉันชอบสิ่งนี้เช่นกัน คุณสามารถให้ตัวอย่างวิธีใช้กับตัวอย่างที่ระบุได้หรือไม่?
runaros

@runaros: การใช้ตัวเปรียบเทียบจากคำตอบของ KLE: Collections.sort (/ * Collection <Person> * / people, ChainedComparator ใหม่ (NAME_ASC_ADRESS_DESC, ID_DESC));
Janus Troelsen

16

วิธีหนึ่งคือการสร้างComparatorรายการคุณสมบัติที่ใช้เป็นอาร์กิวเมนต์เพื่อจัดเรียงตามตัวอย่างนี้

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

ใช่. หากคุณต้องการให้โค้ดของคุณเป็นแบบทั่วไป enum ของคุณสามารถระบุเฉพาะคุณสมบัติที่จะอ่าน (คุณสามารถใช้การสะท้อนเพื่อรับคุณสมบัติโดยใช้ชื่อ enum) และคุณสามารถระบุส่วนที่เหลือด้วย enum ที่สอง: ASC & DESC และ อาจเป็นหนึ่งในสาม (NULL_FIRST หรือ NULL_LAST)
KLE

8

แนวทางหนึ่งคือการเขียน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

compose(nameComparator, compose(addressComparator, idComparator))ซึ่งจะอ่านได้ดีขึ้นเล็กน้อยหาก Java มีวิธีการขยาย
Tom Hawtin - แทคไลน์

4

เครื่องเปรียบเทียบช่วยให้คุณทำสิ่งนั้นได้อย่างง่ายดายและเป็นธรรมชาติ คุณสามารถสร้างอินสแตนซ์ของตัวเปรียบเทียบได้ทั้งในคลาส Person ของคุณเองหรือในคลาสบริการที่เกี่ยวข้องกับความต้องการของคุณ
ตัวอย่างการใช้คลาสภายในที่ไม่ระบุชื่อ:

    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 ที่เห็นด้วยกับตัวเปรียบเทียบที่มีอยู่บางตัว
  • มี NullComparator ที่จัดการกรณีว่างจากนั้นมอบหมายให้กับตัวเปรียบเทียบอื่น
  • ฯลฯ ...

2

ฉันคิดว่าการเชื่อมโยงตัวเรียงลำดับกับคลาส Person เช่นเดียวกับในคำตอบของคุณไม่ใช่ความคิดที่ดีเพราะมันจับคู่การเปรียบเทียบ (โดยปกติจะขับเคลื่อนทางธุรกิจ) และโมเดลวัตถุให้อยู่ใกล้กัน ทุกครั้งที่คุณต้องการเปลี่ยน / เพิ่มสิ่งที่ตัวเรียงลำดับคุณต้องสัมผัสคลาสบุคคลซึ่งโดยปกติแล้วคุณจะไม่ต้องการทำ

การใช้บริการหรือสิ่งที่คล้ายกันซึ่งมีอินสแตนซ์เปรียบเทียบเช่น KLE เสนอฟังดูมีความยืดหยุ่นและขยายได้มากกว่า


สำหรับฉันสิ่งนี้นำไปสู่การมีเพศสัมพันธ์ที่แน่นหนาเนื่องจากคลาสผู้ถือตัวเปรียบเทียบ HAS สามารถทราบโครงสร้างข้อมูลโดยละเอียดของคลาส Person (โดยทั่วไปจะเปรียบเทียบฟิลด์ใดของคลาส Person) และถ้าคุณจะเปลี่ยนบางสิ่งในฟิลด์ Persons สิ่งนี้จะนำไปสู่การติดตามเดียวกัน การเปลี่ยนแปลงในคลาสตัวเปรียบเทียบ ฉันเดาว่านักเปรียบเทียบบุคคลควรเป็นส่วนหนึ่งของคลาส Person blog.sanaulla.info/2008/06/26/…
สแตน

2

แนวทางของฉันสร้างขึ้นจาก Yishai's ช่องว่างหลักคือไม่มีวิธีการเรียงลำดับจากน้อยไปหามากเป็นอันดับแรกสำหรับแอตทริบิวต์และหลังจากนั้นก็จะหลอกอีก สิ่งนี้ไม่สามารถทำได้ด้วยการแจงนับ สำหรับที่ฉันใช้ชั้นเรียน เนื่องจาก SortOrder ขึ้นอยู่กับประเภทที่ฉันต้องการใช้เป็นชั้นในของบุคคล

คลาส 'บุคคล' กับคลาสภายใน 'SortOrder':

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

oRUMOo


อะไรคือความซับซ้อนของการเชื่อมโยงของตัวเปรียบเทียบประเภทนี้? เราเรียงลำดับทุกครั้งที่เชื่อมโยงตัวเปรียบเทียบหรือไม่? ดังนั้นเราจึงทำการดำเนินการ * log (n) สำหรับตัวเปรียบเทียบแต่ละตัว?
John Baum

0

ฉันเพิ่งเขียนตัวเปรียบเทียบเพื่อจัดเรียงหลายเขตข้อมูลภายในระเบียนสตริงที่มีตัวคั่น ช่วยให้คุณสามารถกำหนดตัวคั่นโครงสร้างเรกคอร์ดและกฎการเรียงลำดับ (ซึ่งบางส่วนเป็นแบบเฉพาะ) คุณสามารถใช้สิ่งนี้ได้โดยการแปลงระเบียนบุคคลเป็นสตริงที่มีตัวคั่น

ข้อมูลที่จำเป็นจะถูกส่งไปยังตัวเปรียบเทียบเองไม่ว่าจะโดยทางโปรแกรมหรือผ่านไฟล์ XML

XML ได้รับการตรวจสอบความถูกต้องโดยไฟล์ XSD ที่ฝังอยู่ในแพ็คเกจ ตัวอย่างเช่นด้านล่างนี้เป็นเค้าโครงเรกคอร์ดที่คั่นด้วยแท็บที่มีสี่ฟิลด์ (สองฟิลด์นี้เรียงลำดับได้)

<?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 จำเป็นต้องมีตัวเปรียบเทียบดิฟเฟอร์เน็ตสองตัวสำหรับมัน ด้านล่างนี้คือตัวอย่าง

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.