Reputation: 1
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
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
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
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