【问题标题】:Sorting of two ArrayList/List Objects两个 ArrayList/List 对象的排序
【发布时间】:2020-05-24 11:42:08
【问题描述】:

例如,我将两个数组转换为 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-class,从firstName 和lastName 构造Person-instances,然后通过在Person 上实现Comparable&lt;Person&gt; 对List&lt;Person&gt; 进行排序或写一个单独的Comparator&lt;Person&gt;。
  • 嘿,老实说,在这种情况下使用String 似乎是一个非常糟糕的选择。正如@Turing85 所提到的,您应该创建一个代表 Person 的类。如果列表不是静态的,则很难维持秩序。

标签: java arrays sorting arraylist


【解决方案1】:
    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));
    }

【讨论】:

  • OP 希望保留 frstName 和 lastName 之间的“耦合”。
【解决方案2】:

您可以通过将两个数组合并到一个名称流中来做到这一点,包括名字和姓氏,对该流进行排序,然后重新创建两个列表。

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

【讨论】:

    【解决方案3】:

    正如 cmets 所强调的,您的问题是您使用两个不同的姓名和姓氏列表,因此这两种数据的排序过程完全不相关。一个可能的解决方案是创建一个新类Person,包括两个字段name 和surname,并实现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 数组的顺序。

    【讨论】:

      【解决方案4】:

      因为firstName 和lastName 相互连接,您应该创建一个类来对它们进行建模。我们称这个类为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&lt;Person&gt;,它将被排序。 Comparator.comparing 函数将让您选择要排序的Person 类的哪个属性。像这样的:

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

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:
              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);
          

          【讨论】:

            【解决方案7】:

            如果你不想创建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());
            

            【讨论】:

              【解决方案8】:

              域

              正如我在评论中所说,我建议使用Person-POJO 以语义方式绑定firstName 和lastName:

              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[]sfirstNames和lastNames转换成List&lt;Person&gt;,可以提供一种方法:

                  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()) 来控制列表的可变性,因为我们要对列表进行排序。

              从这里开始,有两条一般路线:一种是Personcomparable by public class Person implements Comparable&lt;Person&gt;,一种是写Comparator&lt;Person&gt;。我们将讨论这两种可能性。


              挑战

              目标是对Person-objects 进行排序。排序的主要标准是人的名字。如果两个人的名字相同,则应按姓氏排序。名字和姓氏都是String-objects,应该按照字典顺序排序,这是String的自然顺序。


              解决方案 1:在 Person 上实现 Comparable&lt;Person&gt;

              实现比较的逻辑很简单:

              1. 使用equals(...)比较两个人的firstNames。
              2. 如果相等,则使用 compareTo(...) 比较 lastNames 并返回结果。
              3. 否则,将firstNames 与compareTo(...) 进行比较并返回结果。

              相应的方法将如下所示:

              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&lt;Person&gt;:

              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&lt;Person&gt;

              实现挑战部分中给出的排序比较的比较器的传统实现可能如下所示:

              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 的构造已通过 Comparator.comparing-API 进行了简化。要使用Comparator.comparing-API 定义一个实现挑战部分给出的顺序的Comparator,我们只需要一行代码:

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

              以下代码演示如何使用此Comparator 对List&lt;Person&gt; 进行排序:

              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 获得。

              我会质疑将名字和姓氏分成两个单独数组的初始设计决定。我选择不在类Person 中包含方法List&lt;Person&gt; constructPersons(String[] firstNames, String[] lastNames),因为这只是适配器代码。它应该包含在某个映射器中,但不是Person 存在的功能。

              【讨论】:

              • 惊人的答案(1+)
              猜你喜欢
              • 1970-01-01
              • 2010-11-22
              • 1970-01-01
              • 1970-01-01
              • 2016-09-05
              • 1970-01-01
              • 2015-10-28
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多