簡體   English   中英

兩個 ArrayList/List 對象的排序

[英]Sorting of two ArrayList/List Objects

例如,我將兩個數組轉換為 ArrayList,即 firstName 和 lastName。 我想使用名字對這兩個列表進行排序,姓氏將跟隨名字。

預期輸出:

firstNameList = {Andrew, Johnson, William}
lastNameList = {Wiggins, Beru, Dasovich};

我的初始程序:

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

String [] firstName = {William, Johnson, Andrew};
String [] lastName = {Dasovich, Beru, Wiggins};

//Will convert arrays above into list.
List <String> firstNameList= new ArrayList<String>();
List <String> lastNameList= new ArrayList<String>();

//Conversion
Collections.addAll(firstNameList, firstName);
Collections.addAll(lastNameList, lastName);

領域

正如我在評論中所述,我建議使用Person - POJO以語義方式綁定firstNamelastName

class Person {
    public static final String PERSON_TO_STRING_FORMAT = "{f: %s, l: %s}";

    private final String firstName;
    private final String lastName;

    private Person(final String firstName, final String lastName) {
        this.firstName = Objects.requireNonNull(firstName);
        this.lastName = Objects.requireNonNull(lastName);
    }

    public static Person of(final String firstName, final String lastName) {
        return new Person(firstName, lastName);
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    @Override
    public String toString() {
        return String.format(PERSON_TO_STRING_FORMAT, getFirstName(), getLastName());
    }
}

要將兩個String[]firstNameslastNames轉換為List<Person> ,可以提供一種方法:

    public static List<Person> constructPersons(
            final String[] firstNames,
            final String[] lastNames) {
        if (firstNames.length != lastNames.length) {
            throw new IllegalArgumentException("firstNames and lastNames must have same length");
        }
        return IntStream.range(0, firstNames.length)
                .mapToObj(index -> Person.of(firstNames[index], lastNames[index]))
                .collect(Collectors.toCollection(ArrayList::new));
    }

關於此方法的備注:在這里,我們使用collect(Collectors.toCollection(...))而不是collect(Collectors.toList())來控制列表可變性,因為我們要對列表進行排序。

從這里開始有兩種一般路線:一種是通過public class Person implements Comparable<Person>使Person 具有可比性,另一種是編寫Comparator<Person> 我們將討論這兩種可能性。


挑戰

目標是對Person對象進行排序。 排序的主要標准是人的名字。 如果兩個人的名字相同,則應按姓氏排序。 名字和姓氏都是String對象,應該按字典順序排序,這是String的自然順序。


解決方案 1:在Person上實現Comparable<Person>

實現比較的邏輯很簡單:

  1. 使用equals(...)比較兩個人的firstName
  2. 如果它們相等,則使用compareTo(...)比較lastName並返回結果。
  3. 否則,將firstNamecompareTo(...)進行比較並返回結果。

相應的方法將如下所示:

public class Person implements Comparable<Person> {
    ...
    @Override
    public final int compareTo(final Person that) {
        if (Objects.equals(getFirstName(), that.getFirstName())) {
            return getLastName().compareTo(that.getLastName());
        }
        return getFirstName().compareTo(that.getFirstName());
    }
    ...
}

雖然不是絕對必要的,但建議類的自然順序(即Comparable實現)與其equals(...)一致。 由於現在情況並非如此,我建議覆蓋equals(...)hashCode()

public class Person implements Comparable<Person> {
    ...
    @Override
    public final boolean equals(Object thatObject) {
        if (this == thatObject) {
            return true;
        }
        if (thatObject == null || getClass() != thatObject.getClass()) {
            return false;
        }
        final Person that = (Person) thatObject;
        return Objects.equals(getFirstName(), that.getFirstName()) &&
                Objects.equals(getLastName(), that.getLastName());
    }

    @Override
    public final int hashCode() {
        return Objects.hash(getFirstName(), getLastName());
    }
    ...
}

以下代碼演示了如何從兩個String[]創建和排序List<Person>

final List<Person> persons = constructPersons(
        new String[]{"Clair", "Alice", "Bob", "Alice"},
        new String[]{"Clear", "Wonder", "Builder", "Ace"}
);
Collections.sort(persons);
System.out.println(persons);

解決方案 2:實現Comparator<Person>

實現挑戰部分中給出的排序比較的比較器的傳統實現可能如下所示:

class PersonByFirstNameThenByLastNameComparator implements Comparator<Person> {
    public static final PersonByFirstNameThenByLastNameComparator INSTANCE =
            new PersonByFirstNameThenByLastNameComparator();

    private PersonByFirstNameThenByLastNameComparator() {}

    @Override
    public int compare(final Person lhs, final Person rhs) {
        if (Objects.equals(lhs.getFirstName(), rhs.getFirstName())) {
            return lhs.getLastName().compareTo(rhs.getLastName());
        }
        return lhs.getFirstName().compareTo(rhs.getFirstName());
    }
}

示例調用可能如下所示:

final List<Person> persons = constructPersons(
        new String[]{"Clair", "Alice", "Bob", "Alice"},
        new String[]{"Clear", "Wonder", "Builder", "Ace"}
);
persons.sort(PersonByFirstNameThenByLastNameComparator.INSTANCE);
System.out.println(persons);

在 Java 8 中,通過Comparator.comparing -API簡化了Comparator的構造。 要使用Comparator -API 定義實現挑戰部分中給出的順序的Comparator.comparing ,我們只需要一行代碼:

Comparator.comparing(Person::getFirstName)
    .thenComparing(Person::getLastName)

以下代碼演示了如何使用此ComparatorList<Person>進行排序:

final List<Person> persons = constructPersons(
        new String[]{"Clair", "Alice", "Bob", "Alice"},
        new String[]{"Clear", "Wonder", "Builder", "Ace"}
);
persons.sort(Comparator.comparing(Person::getFirstName)
    .thenComparing(Person::getLastName));
System.out.println(persons);

結束語

一個MRE可以用Ideone

我會質疑將名字和姓氏拆分為兩個單獨數組的初始設計決定。 我選擇不包括該方法List<Person> constructPersons(String[] firstNames, String[] lastNames)Person因為這僅僅是適配器代碼。 它應該包含在某個映射器中,但不是Person存在的功能。

您可以通過將兩個數組合並到一個 Names 流中來實現這一點,使用名字和姓氏,對該流進行排序,然后重新創建兩個列表。

    String[] firstName = {"William", "Johnson", "Andrew"};
    String[] lastName = {"Dasovich", "Beru", "Wiggins"};

    final var sortedNames = IntStream.range(0, firstName.length)
            .mapToObj(i -> new Name(firstName[i], lastName[i]))
            .sorted(Comparator.comparing(n -> n.firstName))
            .collect(Collectors.toList());

    final var sortedFirstNames = sortedNames.stream()
            .map(n -> n.firstName)
            .collect(Collectors.toList());
    final var sortedLastNames = sortedNames.stream()
            .map(n -> n.lastName)
            .collect(Collectors.toList()); 

正如評論所強調的那樣,您的問題是您對姓名和姓氏使用了兩個不同的列表,因此這兩種類型的數據的排序過程完全無關。 一個可能的解決方案是創建一個包含兩個字段namesurname的新類Person並實現Comparable接口,如下所示:

public class Person implements Comparable<Person> {
    public String firstName;
    public String lastName;

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

    @Override
    public String toString() {
        return "Person [firstName=" + firstName + ", lastName=" + lastName + "]";
    }

    @Override
    public int compareTo(Person o) {
        return this.firstName.compareTo(o.firstName);
    }

    public static void main(String[] args) {
        Person[] persons = { new Person("William", "Dasovich"),
                             new Person("Johnson", "Beru"),
                             new Person("Andrew", "Wiggins") };

        Collections.sort(Arrays.asList(persons));
        for (Person person : persons) {
            System.out.println(person);
        }
    }
}

Collections.sort方法按firstName提供Person數組的順序。

因為firstNamelastName相互連接,所以您應該創建一個類來為它們建模。 讓我們稱這個類為Person

class Person {
    private final String firstName;
    private final String lastName;

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

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    // Add toString, equals and hashCode as well.
}

現在,創建一個人員列表:

List<Person> persons = Arrays.asList(
    new Person("Andrew", "Wiggins"),
    new Person("Johnson", "Beru"),
    new Person("William", "Dasovich"));

現在,要對其進行排序,您可以在帶有比較器的流上使用sorted方法。 這將創建一個新的List<Person>將被排序。 Comparator.comparing函數將讓您選擇要排序的Person類的哪個屬性。 像這樣的東西:

List<Person> sortedPersons = persons.stream()
        .sorted(Comparator.comparing(Person::getFirstName))
        .collect(Collectors.toList());

TreeSet 可以做到:
(使用 Turing85 建議的 Person 類)

import java.util.Set;
import java.util.TreeSet;

public class PersonTest {

    private static class Person implements Comparable<Person> {

        private final String firstName;
        private final String lastName;

        public Person(final String firstName, final String lastName) {
            this.firstName = firstName;
            this.lastName  = lastName;
        }

        @Override
        public int compareTo(final Person otherPerson) {
            return this.firstName.compareTo(otherPerson.firstName);
        }

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

    public static void main(final String[] args) {

        final Set<Person> people = new TreeSet<>();
        /**/              people.add(new Person("William", "Dasovich"));
        /**/              people.add(new Person("Johnson", "Beru"));
        /**/              people.add(new Person("Andrew",  "Wiggins"));

        people.forEach(System.out::println);
    }
}

但是 Streams 和一個稍微簡單的 Person 類也可以這樣做:

import java.util.stream.Stream;

public class PersonTest {

    private static class Person {

        private final String firstName;
        private final String lastName;

        public Person(final String firstName, final String lastName) {
            this.firstName = firstName;
            this.lastName  = lastName;
        }

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

    public static void main(final String[] args) {

        Stream.of(
                new Person("William", "Dasovich"),
                new Person("Johnson", "Beru"    ),
                new Person("Andrew",  "Wiggins" ) )

            .sorted ((p1,p2) -> p1.firstName.compareTo(p2.firstName))
            .peek   (System.out::println)

            .sorted ((p1,p2) -> p1.lastName .compareTo(p2.lastName))
            .forEach(System.out::println);
    }
}
    String[] firstName = {"William", "Johnson", "Andrew"};
    String[] lastName = {"Dasovich", "Beru", "Wiggins"};

    // combine the 2 arrays and add the full name to an Array List 
    // here using a special character to combine, so we can use the same to split them later
    // Eg. "William # Dasovich"
    List<String> combinedList = new ArrayList<String>();
    String combineChar = " # ";        
    for (int i = 0; i < firstName.length; i++) {
        combinedList.add(firstName[i] + combineChar + lastName[i]);
    }
    // Sort the list 
    Collections.sort(combinedList);

    // create 2 empty lists
    List<String> firstNameList = new ArrayList<String>();
    List<String> lastNameList = new ArrayList<String>();

    // iterate the combined array and split the sorted names to two lists
    for (String s : combinedList) {
        String[] arr = s.split(combineChar);
        firstNameList.add(arr[0]);
        lastNameList.add(arr[1]);
    }
    System.out.println(firstNameList);
    System.out.println(lastNameList);

如果你不想創建 DTO 來保持名字和姓氏在一起,你可以使用一種基於 java 流的函數方式:

  1. 用列表創建對來綁定這兩個值
  2. 根據名字對它們進行排序
  3. 把這對夫婦弄平,以便有一個一維的列表
 String[] firstName = {"William", "Johnson", "Andrew"};
 String[] lastName = {"Dasovich", "Beru", "Wiggins"};

//Will convert arrays above into list.
        List<String> firstNameList = new ArrayList<String>();
        List<String> lastNameList = new ArrayList<String>();

//Conversion
        Collections.addAll(firstNameList, firstName);
        Collections.addAll(lastNameList, lastName);

        List<String> collect = firstNameList
                .stream()
                .map(name -> {
                    List<String> couple = List.of(name, lastNameList.get(0));
                    lastNameList.remove(0);
                    return couple;
                })
                .sorted(Comparator.comparing(l -> l.get(0)))
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
    String[] firstNames = {William, Johnson, Andrew};
    String[] lastNames = {Dasovich, Beru, Wiggins};

    //Will convert arrays above into list.
    List<String> firstNameList = new ArrayList<String>();
    List<String> lastNameList = new ArrayList<String>();


    Map<String, String> lastNameByFirstName = new HashMap<>();
    for (int i = 0; i < firstNames.length; i++) {
        lastNameByFirstName.put(firstNames[i], lastNames[i]);
    }

    //Conversion
    Collections.addAll(firstNameList, firstNames);
    Collections.sort(firstNameList);
    for (String firstName : firstNameList) {
        lastNameList.add(lastNameByFirstName.get(firstName));
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM