Random
Random

Reputation: 1

Finding matches in a list to create a new list using Java 8

I have a list which contains Objects, i.e. Dogs. The Dog can either be male or female. This is a variable within the Object.

I need to iterate through Dogs, for each male dog I need to see if there is a female that matches two variables of the male dog.

As in Dog.getName(), Dog.getAge(). So I would have a male and female matching their name and age.

If I find a match I need to add both Dogs to a new list.

Its been driving me mad figuring out how to do it using Java 8 and could really use some advice.

I have a bit of code done but wondering if I could make it more efficient.

What I have to so far:

List<Dog> dogsA = unmatchedDogs
        .stream()
        .filter(
                dogA ->{
                    return dogA.getGender().equalsIgnoreCase("Male");
                }
        )
        .collect(Collectors.toList());

List<Dog> dogsB = unmatchedDogs
        .stream()
        .filter(
                dogB ->{
                    return dogB.getGender().equalsIgnoreCase("Female");
                }
        )
        .collect(Collectors.toList());

List<Dog> dogsToUpdate = new ArrayList<>();

    dogsA.stream()
            .flatMap(x -> dogsB
            .stream()
            .filter(y -> x.getAge().equalsIgnoreCase(y.getAge()))
            .filter(y -> x.getName().equalsIgnoreCase(y.getName()))
            .limit(1))
            .forEach(product -> dogsToUpdate.add(product));

            dogsB.stream()
            .flatMap(x -> dogsA
            .stream()
            .filter(y -> x.getAge().equalsIgnoreCase(y.getAge()))
            .filter(y -> x.getName().equalsIgnoreCase(y.getName()))
            .limit(1))
            .forEach(product -> dogsToUpdate.add(product));

Upvotes: 0

Views: 1063

Answers (3)

Steyrix
Steyrix

Reputation: 3236

Ironically, it would be readable and shorter with loops approach, and it is still O(n^2). :

for (int i = 0; i < dogsA.size(); i++) {
    for (int j = 0; j < dogsB.size(); j++) {
        Dog male = dogsA.get(i);
        Dog female = dogsB.get(j);

        if (male.getAge() == female.getAge() 
            && male.getName().equals(female.getName())) {
            dogsToUpdate.add(male);
            dogsToUpdate.add(female);
        }
    }
}

P.S. Stream API should be used as mush as possible, however, if it makes the logic look more complicated and does not provide additional efficiency, you should take a look at simple approach. Let me know if you do not want to use loops, I will edit my answer and try to propose stream API solution.

Upvotes: 2

Bentaye
Bentaye

Reputation: 9766

All examples below assume you first sort your dogs between male and females, then call the match method:

List<Dog> females = dogs.stream()
        .filter(d -> d.getGender().equalsIgnoreCase("Female"))
        .collect(Collectors.toList());

List<Dog> males = dogs.stream()
        .filter(d -> d.getGender().equalsIgnoreCase("Male"))
        .collect(Collectors.toList());

return match(males, females);

Version 1: with Streams

I would first create a method to find a matching female in a list of females for a given male. This method returns an Optional<List<Dog>>. If there is a match it contains the male and female, if no match, it returns Optional.empty

public Optional<List<Dog>> findMatchingFemale(Dog male, List<Dog> females) {
    return females.stream()
            .filter(female -> female.getName().equalsIgnoreCase(male.getName()) &&
                              female.getAge() == male.getAge())
            .findFirst()
            .map(female -> Arrays.asList(male, female));
}

Then I'd create the female list, then go through the male list and look for a matching female for each of them in the female list using the method above. then filter out the Optional.empty and flatMap the whole thing:

public List<Dog> match(List<Dog> males, List<Dog> females) {
    return males.stream()
            .map(male -> findMatchingFemale(male, females)) // find matching couples
            .filter(Optional::isPresent)                    // filter out single males
            .map(Optional::get)                             // get the couples a List of lists
            .flatMap(List::stream)                          // flatten to a list
            .collect(Collectors.toList());
}

Version 2: with foreach

You could also do it with loops, but not for, use foreach instead, this way you get rid of the indexes, it has a Stream fill to it (but it is not)

public List<Dog> match(List<Dog> males, List<Dog> females) {
    List<Dog> dogsToUpdate = new ArrayList<>();
    males.forEach(male ->
            females.forEach(female ->
                    addIfMatch(male, female, dogsToUpdate)));
    return dogsToUpdate;
}

private void addIfMatch(Dog male, Dog female, List<Dog> dogs) {
    if (male.getAge() == female.getAge() &&
        male.getName().equalsIgnoreCase(female.getName())) {
        dogs.add(male);
        dogs.add(female);
    }
}

Version 3 another version with Streams because you want examples of Streams

public List<Dog> match(List<Dog> males, List<Dog> females) {
    return males.stream()
            .map(male -> females.stream()
                    .map(female -> keepIfMatch(male, female))
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .flatMap(List::stream)
                    .collect(Collectors.toList()))
            .flatMap(List::stream)
            .collect(Collectors.toList());
}

private Optional<List<Dog>> keepIfMatch(Dog male, Dog female) {
    return male.getAge() == female.getAge() && male.getName().equalsIgnoreCase(female.getName()) ?
            Optional.of(Arrays.asList(male, female)) :
            Optional.empty();
}

Upvotes: 1

Joop Eggen
Joop Eggen

Reputation: 109593

The following should do

List<Dog> maleDogs = unmatchedDogs
        .stream()
        .filter(dog -> dog.getGender().equalsIgnoreCase("Male"));
        .collect(Collectors.toList());

List<Dog> femaleDogs = unmatchedDogs
        .stream()
        .filter(dog -> return dog.getGender().equalsIgnoreCase("Female"));
        .collect(Collectors.toList());

List<Dog[]> dogPairs = new ArrayList<>();
maleDogs.forEach(male -> {
    femaleDogs.stream()
              .filter(female -> dogsMatch(male, female))
              .findAny()
              .ifPresent(female -> {
                  femaleDogs.remove(female);
                  dogPairs.add(new Dog[] { male, female };

                  unmatchedDogs.remove(male));
                  unmatchedDogs.remove(female));
              });

boolean dogsMatch(Dog male, Dog female) {
    return male.getAge() == female.getAge()
        && male.getKind().equalsIgnoreCase(female.getKind());
}

It does not let one female match several males.

Upvotes: 0

Related Questions