StillLearningToCode
StillLearningToCode

Reputation: 2461

new Java List of objects by matching entries in two other lists

suppose I have these two lists

List<Person> persons = Arrays.asList(
                new Person(1, "Mike", "Canada"),
                new Person(2, "Jill", "England"),
                new Person(3, "Will", "Whales"),
                new Person(4, "Mary", "Spain"));


List<Metadata> metadata= Arrays.asList(
                new metadata(1, "2000-01-01", "Naturalized", "Bachelor's of Arts"),
                new metadata(2, "2001-01-01", "ExPat", "Masters of Chemestry"),
                new metadata(3, "2017-05-01", "Citizen", "Buiness Management"),
                new metadata(4, "2018-04-16", "Work Visa", "Nursing"));

where the end result is a new list:

List<PersonWithMetadata> personsAndMEtadata = Arrays.asList(
                new PersonWithMetadata(1, "Mike", "Canada", "2000-01-01", "Naturalized", "Bachelor's of Arts"),
                new PersonWithMetadata(2, "Jill", "England", "2001-01-01", "ExPat", "Masters of Chemestry"),
                new PersonWithMetadata(3, "Will", "Whales", "2017-05-01", "Citizen", "Buiness Management"),
                new PersonWithMetadata(4, "Mary", "Spain", "2018-04-16", "Work Visa", "Nursing"));

I am trying to find a Java streams way of combining the first two lists into the third--like an SQL join on the first input being an id number. It seems like there should be a way to do this, but I'm at a loss. how is this done? Also, suppose that there is at most one match between the two input lists.

Upvotes: 3

Views: 1572

Answers (4)

Mureinik
Mureinik

Reputation: 311163

YCF_L's solution should work, but it's an O(n2) solution. An O(n) solution could be achieved by converting one list to map from the id to the object, and then iterating over the other and getting the matching value from the map:

Map<Integer, Person> personMap = 
    persons.stream().collect(Collectors.toMap(Person::getId, Function.identity());

List<PersonWithMetadata> result = 
    metadata.stream()
            .map(m -> new PersonWithMetadata(personMap.get(m.getId()), m)
            .collect(Collectors.toList());

In the sample data the lists have matching objects in matching order. If this assumption is true for the real problem too, the solution could be must easier - you could stream the indexes and get the corresponding values from the lists:

List<PersonWithMetadata> result = 
    IntStream.reange(0, persons.size())
             .map(i -> new PersonWithMetadata(persons.get(i), metadata.get(i))
             .collect(Collectors.toList());

Upvotes: 5

Garreth Golding
Garreth Golding

Reputation: 1005

The below example builds Map of Metadata objects using the ID as the key. This will help with performance as there's no need to iterate over the Metadata list for each Person in the List

Code

public static void main(String[] args) {
    List<Person> persons = Arrays.asList(
            new Person(1, "Mike", "Canada"),
            new Person(2, "Jill", "England"),
            new Person(3, "Will", "Whales"),
            new Person(4, "Mary", "Spain"));


    List<Metadata> metadataList = Arrays.asList(
            new Metadata(1, "2000-01-01", "Naturalized", "Bachelor's of Arts"),
            new Metadata(2, "2001-01-01", "ExPat", "Masters of Chemestry"),
            new Metadata(3, "2017-05-01", "Citizen", "Buiness Management"),
            new Metadata(4, "2018-04-16", "Work Visa", "Nursing"));

    //Iterate over metadataList once and create map based on ID as key
    Map<Integer, List<Metadata>> metadataMap = metadataList.stream()
            .collect(Collectors.groupingBy(Metadata::getId));

    //Iterate over personList and fetch metadata from Map to build PersonWithMetadata
    List<PersonWithMetadata> personWithMetadataList = persons.stream().map(person -> {
        List<Metadata> metadata = metadataMap.get(person.id);
        if (metadata.isEmpty()) {
            //TODO: Handle scenario for no metadata for person
        }

        //TODO: Build PersonWithMetadata

        return new PersonWithMetadata();

    }).collect(Collectors.toList());

}

Upvotes: 1

Kraylog
Kraylog

Reputation: 7553

I believe what you're looking for is the zip function that was sadly omitted from the API.

The protonpack library provides it, which would allow you to zip and then map the tuple to the new structure.

StreamUtils.zip(persons, metadata, (person, metadata) -> ... )

Upvotes: 1

Youcef LAIDANI
Youcef LAIDANI

Reputation: 59960

You can try this way :

List<PersonWithMetadata> personsAndMEtadata = persons.stream()
        .map(p -> {
                    //search for the meta data based on the person id
                    Metadata meta = metadata.stream()
                            .filter(m -> m.getId() == p.getId())
                            .findFirst()
                            .get();
                    // then create a PersonWithMetadata object based on Person and metadata
                    return new PersonWithMetadata(
                            p.getId(), p.getFirstName(), p.getLastName(),
                            meta.getDate(), meta.getCity(), meta.getJob()
                    );

                }
        ).collect(Collectors.toList());

About this line :

Metadata meta = metadata.stream().filter(m -> m.getId() == p.getId()).findFirst().get();

I assume that you have a meta data with the id of person, else you will get NullPointerException.

Upvotes: 3

Related Questions