Pages

Filtering apples with Predicate and lambdas

We have to write a apple filtering Java method that, given in input a string describing a list of apples, preceded by a flag that tell us how to filter them, gives as output the filtered apples.

Even if just two ways of filtering have to be implemented, we should design a solution that could be easily extended to support many unforeseen other filters.

Here is a few test cases that I have written to better explain the problem.
@Test
public void testSolutionByColor() {
    assertThat(AppleFilter.solution("color:80 green|155 green|120 red"), is("80 green|155 green"));
}

@Test
public void testSolutionByMissingColor() {
    assertThat(AppleFilter.solution("color:80 blue|155 yellow|120 red").length(), is(0));
}

@Test
public void testSolutionByWeight() {
    assertThat(AppleFilter.solution("weight:80 green|155 green|120 red"), is("155 green"));
}
Each apple is represented by its color and weight, blank separated. A pipe is used as separator between apples. There are just two filtering way currently, color, that let's us select just the green apples, and weight, that select only the apples heavier than 150.

The point of this exercise is in using a few Java 8 features, namely the Predicate functional interface and lambda functions.

The core of the algorithm is this filter method:
private static List<Apple> filter(List<Apple> apples, Predicate<Apple> p) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (p.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}
Given a list of apples and a predicate on apples, each apple in the list is tested against the predicate. If the test is passed, the apple is added to a new list that is returned by the method.

Here is how I decide which predicate to use, given that data[0] is a String containing the selector as extracted from the input parameter.
Predicate<Apple> p = data[0].equals("color") ?
    (Apple a) -> a.color.equals("green") :
    (Apple a) -> a.weight > 150;
I store in the predicate p a lambda function that has as input an apple and return a boolean, accordingly to the kind of filtering I want to perform. Here I assume only two kind filtering could be performed, however is easy to extend, and make more robust, this piece of code.

The rest of the code I have written is all about converting raw data to objects and back from objects to a string formatted as required by the user. There is some interesting stuff there, too.

I used the String split() method to split the input string. Nothing much to say on it when the separator is a colon or a blank, however we should remember that it is a regular expression, so we have to pay attention to correctly escape it when a special character, like a pipe, is passed it. So, to split the list of apples I write:
data[1].split("\\|")
For the other way round, I used the static String join() method, that joins an iterable on a give character sequence. There is a caveat here, too. The iterable has to be a generic based on CharSequence, quite a nuisance. Libraries as Guava and Apache Commons provide smarter functions, however, if we want to keep our code free from third-party code, we need to use some workaround, like:
List<Apple> selected = filter(apples, p);
List<String> result = new ArrayList<>(selected.size());
for (Apple a : selected) {
    result.add(a.toString());
}
return String.join("|", result);

I have conceived this problem as a way to work with the information provided by the authors when reading Java 8 in action, chapter one. If you have this book at hand, you are going to find in section 1.2 Functions in Java code very similar to the one I used above.

I have create a repository on GitHub where I plan to push all the code I write related to this book. You can see the code above in the Java source AppleFilter class and in the JUnit AppleFilterTest.

No comments:

Post a Comment