Stream Filter of 1 list based on another list

JavaArraylistJava 8Java Stream

Java Problem Overview


I am posting my query after having searched in this forum & google, but was unable to resolve the same. eg: Link1 Link2 Link3

I am trying to filter List 2 (multi column) based on the values in List 1.

List1:
 - [Datsun]
 - [Volvo]
 - [BMW]
 - [Mercedes]

List2: 
 - [1-Jun-1995, Audi, 25.3, 500.4, 300]
 - [7-Apr-1996, BMW, 35.3, 250.2, 500]
 - [3-May-1996, Porsche, 45.3, 750.8, 200]
 - [2-Nov-1998, Volvo, 75.3, 150.2, 100]
 - [7-Dec-1999, BMW, 95.3, 850.2, 900]

expected o/p:
 - [7-Apr-1996, BMW, 35.3, 250.2, 500]
 - [2-Nov-1998, Volvo, 75.3, 150.2, 100]
 - [7-Dec-1999, BMW, 95.3, 850.2, 900]

Code

// List 1 in above eg
List<dataCarName> listCarName = new ArrayList<>(); 
// List 2 in above eg
List<dataCar> listCar = new ArrayList<>(); 

// Values to the 2 lists are populated from excel

List<dataCar> listOutput = listCar.stream().filter(e -> e.getName().contains("BMW")).collect(Collectors.toList());

In the above code if I provide a specific value I can filter, but not sure how to check if Car Name in List 2 exits in List 1.

Hope the issue I face is clear, await guidance (Am still relatively new to Java, hence forgive if the above query is very basic).

Edit I believe the link-3 provided above should resolve, but in my case it is not working. Maybe because the values in list-1 are populated as org.gradle04.Main.Cars.dataCarName@4148db48 .. etc. I am able to get the value in human readable format only when I do a forEach on List 1 by calling the getName method.

Java Solutions


Solution 1 - Java

It's not clear why you have a List<DataCarName> in first place instead of a List/Set<String>.

The predicate you have to provide must check if for the corresponding data car instance, there's its name in the list.

e -> e.getName().contains("BMW") will only check if the name of the data car contains BMW which is not what you want. Your first attempt then may be

e -> listCarName.contains(e.getName())

but since listCarName is a List<DataCarName> and e.getName() a string (I presume), you'll get an empty list as a result.

The first option you have is to change the predicate so that you get a stream from the list of data car names, map them to their string representation and check that any of these names corresponds to the current data car instance's name you are currently filtering:

List<DataCar> listOutput =
    listCar.stream()
           .filter(e -> listCarName.stream().map(DataCarName::getName).anyMatch(name -> name.equals(e.getName())))
           .collect(Collectors.toList());

Now this is very expensive because you create a stream for each instance in the data car stream pipeline. A better way would be to build a Set<String> with the cars' name upfront and then simply use contains as a predicate on this set:

Set<String> carNames = 
    listCarName.stream()
               .map(DataCarName::getName)
               .collect(Collectors.toSet());

List<DataCar> listOutput =
     listCar.stream()
            .filter(e -> carNames.contains(e.getName()))
            .collect(Collectors.toList());
    

Solution 2 - Java

in your DataCar type, does getName() return a String or the DataCarName enum type? If it is the enum, you might follow Alexis C's approach but instead of building a HashSet using Collectors.toSet(), build an EnumSet, which gives O(1) performance. Modifying Alexis' suggestion, the result would look like:

Set<DataCarName> carNames = 
    listCarName.stream()
               .collect(Collectors.toCollection(
                   ()-> EnumSet.noneOf(DataCarName.class)));

List<DataCar> listOutput =
    listCar.stream()
               .filter(car -> carNames.contains(car.getName()))  
               .collect(Collectors.toList());

Solution 3 - Java

@Alexis'a answer is nice, but I have another way around to get use of performance from Map and improve the part you do listCarName.stream().map(DataCarName::getName).anyMatch(name -> name.equals(e.getName())) for each item, first I make a map from listCar and making the key with the field that I want to compare, in this instance is car's name and filter out null values when I map the list1 to be CarData.

So it should be something like:

final Map<String, CarData> allCarsMap = listCar // Your List2
    .stream().collect(Collectors.toMap(CarData::getName, o -> o));

final List<CarData> listOutput = // Your expected result
    listCarName // Your List1
      .stream()
      .map(allCarsMap::get) // will map each name with a value in the map
      .filter(Objects::nonNull) // filter any null value for any car name that does not exist in the map
      .collect(Collectors.toList());

I hope this helps, maybe a little better performance in some scenarios?

Solution 4 - Java

Try this: 

SortedMap<String, Account> accountMap, List<AccountReseponse> accountOwnersList

 List<Map.Entry<String, Account>> entryList = accountMap.entrySet().stream().filter(account -> accountOwnersList.stream()
                .anyMatch(accountOwner -> accountOwner.getAccount()
                                .getIdentifier().equals(account.getValue().getIdentifier())))
                .collect(Collectors.toList());

Can also use .noneMatch().

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioniCoderView Question on Stackoverflow
Solution 1 - JavaAlexis C.View Answer on Stackoverflow
Solution 2 - JavaHank DView Answer on Stackoverflow
Solution 3 - JavaAl-MothafarView Answer on Stackoverflow
Solution 4 - JavaSmart CoderView Answer on Stackoverflow