Home > Software engineering >  Filter based on the value of previous elements in the list
Filter based on the value of previous elements in the list

Time:11-19

I have a list of objects that looks like this:

class Test {
    String a;
    int sortKey;
}

List<Test> testList; 

This testList is sorted based on sortKey.

I want to find the first Test object in the testList where the value of a is "X" and the value of all previous Test objects in the list till this element is found is "Y"

This is what I am trying

testList
    .stream()
    .sorted(sortBasedOnSortKey())
    .filter(test -> test.a().equals("X"))
    .findFirst();

How to make sure the previous elements have a value of a == "Y"?

CodePudding user response:

  1. Sort the list before the stream.
  2. Using IntStream, find the index of the first element matching the filter "X".equals(a)
  3. If such index is found, check that all elements before the index match another filter "Y".equals(a)
static Test findFirstAfterAll(String first, String all, List<Test> testList) {
    testList.sort(Comparator.comparingInt(Test::getSortKey));
    int index = IntStream
            .range(0, testList.size())
            .filter(i -> first.equals(testList.get(i).getA()))
            .findFirst()
            .orElse(-1);

    return IntStream.range(0, index)
            .allMatch(i -> all.equals(testList.get(i).getA())) 
                ? testList.get(index) : null;
}

Test:

List<Test> testList = Arrays.asList(
        new Test("X", 20),
        new Test("Y", 15),
        new Test("Y", 12)
);

System.out.println(findFirstAfterAll("X", "Y", testList));

Output:

Test(a=X, sortKey=20)

Using Java 9 Stream::takeWhile could work too assuming that no element with a.equals("Y") may appear before "X":

static Optional<Test> findFirstAfterAll(String first, String all, List<Test> testList) {
    return testList.stream()
            .sorted(Comparator.comparingInt(Test::getSortKey))
            .takeWhile(t -> all.equals(t.getA()) || first.equals(t.getA()))
            .filter(t -> first.equals(t.getA()))
            .findFirst();
}

Tests:

System.out.println(findFirstAfterAll("X", "Y", Arrays.asList(new Test("Y", 20), new Test("X", 15), new Test("X", 12))));

System.out.println(findFirstAfterAll("X", "Y", Arrays.asList(new Test("X", 20), new Test("Y", 15), new Test("Y", 12))));

System.out.println(findFirstAfterAll("X", "Y", Arrays.asList(new Test("Y", 20), new Test("Y", 15), new Test("Y", 12))));

System.out.println(findFirstAfterAll("X", "Y", Arrays.asList(new Test("Y", 20), new Test("X", 15), new Test("A", 12))));

Output:

Optional[Test{a='X', sortKey=12}]
Optional[Test{a='X', sortKey=20}]
Optional.empty
Optional.empty

The logic is that only "X" or "Y" may appear in the beginning of the sorted list and as soon as "X" is found it's ok.


If at least one "Y" MUST appear before X to make a valid match, similar solution using takeWhile can be implemented:

static Optional<Test> findFirstAfterAtLeastOne(String first, String all, List<Test> testList) {
    testList.sort(Comparator.comparingInt(Test::getSortKey));
    
    int index = (int) IntStream.range(0, testList.size())
        .takeWhile(i -> all.equals(testList.get(i).getA()))
        .count();

    return index == 0 
        ? Optional.empty() 
        : IntStream.range(index, testList.size())
            .takeWhile(i -> first.equals(testList.get(i).getA()))
            .mapToObj(testList::get)
            .findFirst();
}

Here the output for the same test data is:

Optional.empty   // no Y before X
Optional[Test{a='X', sortKey=20}]
Optional.empty
Optional.empty

CodePudding user response:

Well, you can combine takeWhile and dropWhile:

Test[] tests = {
    new Test("Y", 1),
    new Test("Y", 2),
    new Test("Y", 3),
    new Test("X", 4), // Expecting to yield this value
    new Test("O", 5),
    new Test("X", 6),
    new Test("X", 7)
};
Optional<Test> test = Stream.of(tests)
    .takeWhile(t -> t.a().equals("X") || t.a().equals("Y"))
    .dropWhile(t -> t.a().equals("Y"))
    .findFirst();
  • Related