Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you filter nested loops using Java 8 streams and filters?

How do you filter nested loops using java8 streams and filters?

Suppose I have a list of cars (List<Car>), each car having a list of Engines (List<Engine>), each engine having a List<Parts>. In regular Java this structure can be described as:

for(Car car : cars) {
    for (Engine engine : car.getEngines()) {
        for (Part part : engine.getParts()) {
            // ...
        }
    }
}

Suppose I initialise the list of cars as:

List<Car> cars =  new ArrayList<Car>(Arrays.asList(new Car(), new Car(), new Car()));
cars.get(0).setEngines(null);
cars.get(1).setEngines(new ArrayList<Engine>());
cars.get(2).setEngines(new ArrayList<Engine>() {{
    add(new Engine());
    add(null);
    add(new Engine());  
}});

If I want to filter out nulls of List<Engine>, then I would do

cars.stream().filter(p -> p.getEngines() != null).forEach(System.out::println);

If I want to filter out empty arraylists of List, then I would do

cars.stream().filter(p -> !p.getEngines().isEmpty()).forEach(System.out::println);

But how do I remove the null Engine in a 3rd car and yet keep two other engines attached to the original list structure? In other words, can we go into the 2nd, 3rd, nth level of hierarchy with Java 8 filters or do filters only work on the top-most layer? I also tried to use .anyMatch(), without much luck.

just to further clarify, consider the following example: I have 3 cars in my garage. Each car has 3 placeholders for engine. Each engine has 3 placeholders for parts that make up the engine:

Car #1: 
 Engine#1: part1, part2, part3
 Engine#2: null, part2, empty
 Engine#3: part1, null, part3
Car #2: 
 Engine#1: part1, part2, part3
 empty:    null, null, null
 null:     null, null, null
Car #3: 
 Engine#1: null, empty, part3
 Engine#2: null, part2, empty
 Engine#3: part1, null, part3

Question: how do we use Java 8 .filter, such that when after filtering I get the following:

Car #1: 
 Engine#1: part1, part2, part3
 Engine#2: part2, 
 Engine#3: part1, part3
Car #2: 
 Engine#1: part1, part2, part3
Car #1: 
 Engine#1: part3
 Engine#2: part2,
 Engine#3: part1,part3

=======================

Another example

Guys I hope this example that I just made up is clearer:.. Essentially it is the same as above only it is more verbose and instead of cars we can think of banks to minimize abstraction. For conciseness I make all fields public, I hope you don't mind.

Suppose I am affiliated with 4 banks in my 'bank wallet'
Bank#1:
I physically bank here. I am forced to have 3 accounts, but only are 2 filled with some cash and 3rd is yet to opened (ie null)
Bank #2:
I plan to bank here. Account support structure is created (empty ArrayList), but no accounts are added
Bank #3:
I filled out some marketing form. They have me in their CRM but no accounts will ever be opened
Bank #4:
This bank burned down, there is an artifact placeholder in the wallet, which is null.

The following code describes this:

public class Bank_Wallet {

    public static void main(String[] args) {
        List<Bank> banks = new ArrayList<Bank>(Arrays.asList(new Bank(), new Bank(), new Bank(), null));
        // 1st bank with physical accounts, but one of them is null
        banks.get(0).accounts = Arrays.asList(new Account(), null, new Account());
        // 2nd bank with empty accounts  
        banks.get(1).accounts = new ArrayList<Account>();

        System.out.println("RAW original");
        banks.stream().forEach(System.out::println);

        System.out.println("\nFiltered result...   ");
        banks.stream()// get stream
                .filter(p -> p != null) // get rid of null banks
                .filter(p -> p.accounts != null) // get rid of null accounts
                .filter(p -> !p.accounts.isEmpty()) // get rid of empty accounts
                // .filter(p->p.accounts. ?????? ??) ?? how do I remove null account from the remaining bank entry?
                .forEach(System.out::println);

    }// main
}


The support classes are here:

public class Bank {
    public String name;
    public static int counter = 0;
    public List<Account> accounts;

    public Bank() {
        this.name = "Bank: #" + Bank.counter++;
    }

    @Override
    public String toString() {
        return "Bank [name=" + this.name + ", accounts=" + this.accounts + "]";
    }

public class Account {
    public String name;
    public static int counter;

    public Account() {
        this.name = "Account: " + Account.counter++;
    }

    @Override
    public String toString() {
        return "Account [name=" + this.name + "]";
    }

}

when you run this code you will see that after suggested filtering all I am left with is

Bank [name=Bank: #0, accounts=[Account [name=Account: 0], null, Account [name=Account: 1]]]

Question: What other filter do I need do add to the code to get the above result not show null in the accounts and yet retain the overall structure (Bank->Account->etc->etc)

Bank [name=Bank: #0, accounts=[Account [name=Account: 0], Account [name=Account: 1]]]
like image 423
JavaFan Avatar asked Jun 27 '16 21:06

JavaFan


People also ask

Which methods can you use to filter and slice a stream in Java 8 +?

Streams Filtering & Slicing Basics: Java 8 Streams support declarative filtering out of elements along with the ability to slice-off portions of a list. Streams support four operations to achieve this – filter() , distinct() , limit(n) and skip(n) .

What is stream filter in Java 8?

Stream filter() in Java with examples Stream filter(Predicate predicate) returns a stream consisting of the elements of this stream that match the given predicate. This is an intermediate operation.

How does filter work in Java stream?

The filter() function of the Java stream allows you to narrow down the stream's items based on a criterion. If you only want items that are even on your list, you can use the filter method to do this. This method accepts a predicate as an input and returns a list of elements that are the results of that predicate.

Can Java 8 have 2 filters?

Performance. We have seen that using multiple filters can improve the readability of our code.


2 Answers

The stream equivalent of

for(Car car : cars) {
    for (Engine engine : car.getEngines()) {
        for (Part part : engine.getParts()) {
            // ...
        }
    }
}

is

cars.stream()
    .flatMap(car -> car.getEngines().stream())
    .flatMap(engine -> engine.getParts().stream())
    .forEach(part -> { /* ... */ });

The ... code will however not have access to car and engine.

To check for null, you can check in two places:

cars.stream()
    .flatMap(car -> car.getEngines().stream())
    .filter(engine -> engine != null)
    .flatMap(engine -> engine.getParts().stream())
    .forEach(part -> { /* ... */ });

or

cars.stream()
    .flatMap(car -> car.getEngines()
                       .stream()
                       .filter(engine -> engine != null))
    .flatMap(engine -> engine.getParts().stream())
    .forEach(part -> { /* ... */ });
like image 72
Andreas Avatar answered Oct 20 '22 09:10

Andreas


And why do you not simply write this ?

cars.stream()
    .filter(car -> notEmpty(car.getEngines()))
    .filter(car -> car.getEngines().stream().allMatch(engine -> notEmpty(engine.getParts())))
    .forEach(System.out::println);

public static <T> boolean notEmpty(Collection<T> collection) {
   return collection != null && !collection.isEmpty();
}
like image 23
Dici Avatar answered Oct 20 '22 08:10

Dici