简体   繁体   中英

Sorting of two ArrayList/List Objects

For example, I have two Arrays converted into ArrayList which is firstName and lastName. I want to sort these two lists using the first names, the last name will follow through the first names.

Expected output:

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

My Initial Program:

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

Domain

As I have stated in my comment, I would recommend using a Person - POJO to bind firstName and lastName in a semantic way:

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

To convert two String[] s firstNames and lastNames into a List<Person> , one can provide a method:

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

A remark on this method: Here, we use collect(Collectors.toCollection(...)) instead of collect(Collectors.toList()) to have some control with respect to list mutability since we are going to sort the list.

From here on there are two general routes: Either one makes Person comparable by public class Person implements Comparable<Person> or one writes a Comparator<Person> . We will discuss both possibilities.


Challenge

The goal is to sort Person -objects. The primary criteria for sorting is the first name of the person. If two persons have equal first names, then they should be ordered by their last names. Both first- and last name are String -objects and should be ordered in lexicographical order, which is String 's natural order.


Solution 1: Implementing Comparable<Person> on Person

The logic to implement the comparison is straight-forward:

  1. Compare the firstName s of two persons using equals(...) .
  2. If they are equal, compare the lastName s using compareTo(...) and return the result.
  3. Otherwise, compare the firstName s with compareTo(...) and return the result.

The corresponding method would then look like this:

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

While not strictly necessary, it is recommended that the natural ordering of a class (ie the Comparable -implementation) is consistent with its equals(...) -implementation. Since this is not the case right now, I would recommend overriding equals(...) and 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());
    }
    ...
}

The following code demonstrates how to create and order a List<Person> from two String[] :

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

Solution 2: Implementing a Comparator<Person>

A traditional implementation of a comparator realizing the sort comparison given in the challenge-section may look like this:

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

A example call may look like this:

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

With Java 8, the construction of a Comparator has been simplified through the Comparator.comparing -API . To define a Comparator realizing the order given in section Challenge with the Comparator.comparing -API, we only need one line of code:

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

The following code demonstrates how this Comparator is used to sort a List<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);

Closing Notes

A MRE is available on Ideone .

I would question the initial design decision to split up first- and last names into two separate arrays. I opted to not include the method List<Person> constructPersons(String[] firstNames, String[] lastNames) in class Person since this is just adapter-code. It should be contained in some mapper, but is not a functionality that is existential for Person .

You can do this by merging the two arrays into one stream of Names, with first and last name, sort that stream and then recreate the two lists.

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

As highlighted by comments , your problem is you are using two different lists for names and surnames so the ordering process for the two types of data are totally unrelated. A possible solution is creation of a new class Person including two fields name and surname and implementing Comparable interface like below:

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

The Collections.sort method provides the order of the Person array by firstName .

Because the firstName and lastName are connected to each other, you should create a class to model them as such. Let's call this class 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.
}

Now, create a list of persons instead:

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

Now, to sort it, you can use the sorted method on a stream with a comparator. This will create a new List<Person> which will be sorted. The Comparator.comparing function will let you pick which property of the Person class that you want to sort on. Something like this:

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

A TreeSet could do it:
(using a Person class as suggested by Turing85)

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

but Streams & a somewhat simpler Person class might do too:

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

If you don't want to create DTO to keep the first names and last names together, you can use a kind of functional way based on java streams :

  1. create couples with lists to bind those two values
  2. sort them, base on the first name
  3. flat the couple, in order to have a list with one dimension
 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));
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM