Home > Software engineering >  Filtering/Removing from a Nested List of Objects using Streams in Java
Filtering/Removing from a Nested List of Objects using Streams in Java

Time:04-26

Say that we have a 3-dimensional List of Objects.

  class OneDObject {
    int id;
    List<Integer> list;
    OneDObject(int id, List<Integer>list) { // Constructor }
    // Getters and Setters
  }

  class TwoDObject {
    int id;
    List<OneDObject> list;
    TwoDObject(int id, List<OneDObject> list) { // Constructor }
    // Getters and Setters
  }

  var l1 = List.of(1,2,4);
  var l2 = List.of(2,4,6);
  var obj1d1 = new OneDObject(1, l1);
  var obj1d2 = new OneDObject(2, l2);
  var l3 = List.of(obj1d1, obj1d2);
  var l4 = List.of(obj1d1);
  var obj2d1 = new TwoDObject(3, l3);
  var obj2d2 = new TwoDObject(4, l4);
  var l5 = List.of(obj2d1, obj2d2);   // 3-d list

Say that I want to filter "l5" such that if any element in the inner most list is an odd number then the entire list should be deleted, and if that makes the 2nd level list as empty, then that should be deleted in return.

So, for the given example, before filtering if it is:

[[[1,2,4],[2,4,6]], [[1,2,4]]]

After filtering, it should be:

[[[2,4,6]]]

How can I do this using Streams in Java?

CodePudding user response:

Since you need your lists to be updated, in the below solution I am using removeIf method of the List to remove any elements which does not meet the necessary criteria. So for removeIf to work, the list should not be immutable. So replace the var list = List.of(...) code with var list = new ArrayList<>(List.of(...)); (Note: Null checks have been ignored as well.)

Now, this problem could be split into components:

  1. Predicate to identify if a list has any odd elements.
Predicate<OneDObject> hasOdd = obj-> obj.getList().stream().anyMatch(i -> i % 2 != 0);
  1. Predicate to remove objects from 2d list, which has odd elements in its 1d list.
Predicate<TwoDObject> validate2d = obj -> {
    // remove any 1d list that has atleast one odd number.
    obj.getList().removeIf(hasOdd);
    // check if there are any valid 1d lists
    return obj.getList().isEmpty();
};
  1. Now apply the predicate to the final list:
l5.removeIf(validate2d); // l5 will now contain only the 2d object having [2,4,6] list

CodePudding user response:

Here's the final code (in Java, but I think it should almost be interchangeable with Kotlin)

List<TwoDObject> l6 = l5.stream()
                .peek(twoDObject -> {
                    List<OneDObject> filteredOneDObjectList = twoDObject.getList()
                            .stream()
                            .filter(oneDObject -> oneDObject.getList()
                                    .stream()
                                    .noneMatch(i -> i % 2 == 1))
                            .toList();

                    twoDObject.setList(filteredOneDObjectList);
                })
                .filter(twoDObject -> twoDObject.getList().size() > 0)
                .toList();

First we go through every twoDObject by calling Stream#peek, then stream its list and filter out every oneDObject, which contains an odd number. Then the list is saved back into the current twoDObject.

In the end we filter out all empty twoDObjects.


Note that Stream#peek should normally only be used for the purpose of debugging and not mutating the stream elements.
In this case it could also be replaced with

List<TwoDObject> l6 = l5.stream()
                .map(twoDObject -> {
                    ...

                    return twoDObject;
                })
                ...

CodePudding user response:

To achieve what you're looking for, we need a mix of stream operations and collection methods working with functional interfaces.

In the following code, first we use the peek aggregate operation to examine every TwoDbObj instance.

Then, for each one of them we check their OneDbObj list and get rid of the instances that contain at least an odd number.

Once the OneDbObj lists have been filtered by the innermost Integer list, we keep only the TwoDbObj instances whose list contains at least a value (it could also have been written as !isEmpty).

public class Main {
    public static void main(String[] args) {
        List<TwoDObject> l5 = new ArrayList<>(List.of(
                new TwoDObject(3, new ArrayList<>(List.of(
                        new OneDObject(1, new ArrayList<>(List.of(1, 2, 4))),
                        new OneDObject(2, new ArrayList<>(List.of(2, 4, 6)))))),
                new TwoDObject(4, new ArrayList<>(List.of(new OneDObject(1, new ArrayList<>(List.of(1, 2, 4))))))));

        System.out.println(l5);

        List<TwoDObject> listFiltered = l5.stream()
                .peek(twoObj -> twoObj.getList().removeIf(oneObj -> oneObj.getList().stream().anyMatch(i -> i % 2 != 0))) //For each TwoDbObj we get rid of the OneDbObj list whose elements contain at least an odd number
                .filter(twoObj -> twoObj.getList().size() > 0) //Keeping only the TwoDbObj instances whose list contains any element
                .collect(Collectors.toList());

        System.out.println(listFiltered);
    }
}

Here I'll just paste a partial implementation of your sample classes in order to emulate the tests I've performed.

class OneDObject {
    int id;
    List<Integer> list;

    OneDObject(int id, List<Integer> list) {
        this.id = id;
        this.list = list;
    }

    public List<Integer> getList() {
        return list;
    }

    public String toString() {
        return String.format("%s", list);
    }
}

class TwoDObject {
    int id;
    List<OneDObject> list;

    TwoDObject(int id, List<OneDObject> list) {
        this.id = id;
        this.list = list;
    }

    public List<OneDObject> getList() {
        return list;
    }

    public String toString() {
        return String.format("%s", list);
    }
}
  • Related