Thursday, August 12, 2010

google's guava library tutorial part 4: taste the functional programming flavor in java with predicates

In this blog entry, I'll try to explain another functional programming feature introduced by Google Guava library: Predicates. They work like the Function class with the difference that they just return a boolean value. It's possible to use Predicates for filtering elements in an Iterable that satisfy the given Predicate or to check if all/any element in an Iterable satisfies the given Predicate.

Lets write a Predicate that accepts Person objects with names that began with a vowel letter. But first, let me show you what's Person object.



class Person implements Comparable < Person > {
public Person(String name, String lastName){
this.name = name;
this.lastName = lastName;
}

public Person(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
private String name;
private String lastName;
@Override
public int compareTo(Person arg0) {
return this.getName().compareTo(arg0.getName());
}

@Override
public String toString() {
return this.name+" "+this.lastName;
}

}



Now lets see what a Predicate looks like;



Predicate < Person > allowNamesWithVowels = new Predicate < Person > (){
String[] vowels = { "a", "e", "i", "o", "u", "y" };
@Override
public boolean apply(Person person) {
String firstName = person.getName().toLowerCase();
// convert to lowercase just in case

// for each vowel check if
// first name begins with that
// vowel
for(String vowel : vowels){
if(firstName.startsWith(vowel))
return true;
}

return false;
}
};



Now lets build a Person set and begin working using it.



Person frodo = new Person("Frodo", "Baggins");
Person sezin = new Person("Sezin", "Karli");
Person luke = new Person("Luke", "Skywalker");
Person anakin = new Person("Anakin", "Skywalker");
Person eric = new Person("Eric", "Draven");

HashSet setOfPerson =
Sets.newHashSet( eric, frodo, sezin, luke, anakin );
System.out.println("set of person: "+setOfPerson);
// [Sezin Karli, Luke Skywalker, Anakin Skywalker, Frodo Baggins]



filter() will use our Predicate and create a view from elements that satisfy the
Predicate



Set < Person > view = Sets.filter(setOfPerson, allowNamesWithVowels);
System.out.println("view: "+view);
// filter: [Eric Draven, Anakin Skywalker]
// we remove eric from the view
view.remove(eric);
System.out.println("new view: "+view);
//new view: [Anakin Skywalker]
System.out.println("set of person: "+setOfPerson);
// [set of person: [Sezin Karli, Luke Skywalker, Anakin Skywalker, Frodo Baggins]



As you can see the removal of an item from the view causes the same removal from the origin set. This removal action is bidirectional. e.g. removal from the origin set will remove the corresponding element from the view.

Lets see what happens when I add to the original list.


Person bilbo = new Person("Bilbo", "Baggins");
Person alex = new Person("Alex", "Delarge");
setOfPerson.add(bilbo);
setOfPerson.add(alex);
System.out.println("new view: "+view);
// new view: [Anakin Skywalker, Alex Delarge]


The view is updated as well and the elements that are accepted by the predicate are added to the view. In the case above only alex is accepted. you can try add a new element to the view which does not met the predicate's requirement.



Person jarjar = new Person("Jarjar", "Binks");
//view.add(jarjar);



The commented line above will throw java.lang.IllegalArgumentException because the view does not accept elements that does not met the predicate's requirement.

The filter() method can be called from Iterables for all Iterable objects. All live-view tricks we saw earlier are still valid. But what I'll do for working on Maps? It's possible to filter Map objects using keys or values or both (entries) as input. First, I will build a game characters to appearance map.



Map < String, Integer > characterAppearancesMap = Maps.newHashMap();
characterAppearancesMap.put("Mario", 15);
characterAppearancesMap.put("Snake", 8);
characterAppearancesMap.put("Kratos", 4);



Now lets create the necessary Predicates. I'd like to have game characters with 'o' in their name and with lots of game appearances (more than 10 to be exact).




Predicate < String > allowNamesWithO = new Predicate < String > (){
@Override
public boolean apply(String name) {
String lowerCaseName = name.toLowerCase();

return lowerCaseName.contains("o");
}
};

Predicate < Integer > allowLotsOfAppearances = new Predicate < Integer > (){
@Override
public boolean apply(Integer appearance) {
int numberOfAppearance = appearance.intValue();

return (numberOfAppearance > 10);
}
};



First we will filter using the names (keys) and then we will filter using the number of appearances (values).



Map < String, Integer > filterKeys =
new HashMap < String, Integer > ( Maps.filterKeys(characterAppearancesMap, allowNamesWithO) );
// I copy the live view
System.out.println("Game Characters with 'o' in their name: "+filterKeys);
// {Mario=15, Kratos=4}

Map < String, Integer > filterValues = Maps.filterValues(filterKeys, allowLotsOfAppearances);
System.out.println(
"Game Characters with 'o' in their name and with more than 10 appearances: "+filterValues);
//{Mario=15}



You can obtain the desired map by writing a single Predicate that will contain both predicates and use this Predicate on entries of a Map. It's not possible for Predicates.and() because we have to apply the Predicate on both keys and values but new Predicate built using and() can only be applied on keys or values. Lets create a new Predicate for our case.


Predicate < Map.Entry < String, Integer > > characterPredicate =
new Predicate < Map.Entry < String, Integer > > (){

@Override
public boolean apply(Entry entry) {
String key = entry.getKey();
int value = entry.getValue().intValue();

return (key.contains("o") && value > 10);
}

};

Map < String, Integer > filterEntries =
Maps.filterEntries(characterAppearancesMap, characterPredicate);
System.out.println("Are the game character results same? "+filterEntries.equals(filterValues));
// As you can see the result is the same



Functions class has a static method that'll convert your Predicate to a Function. The said Function will use your Predicate and return the result as a Boolean.



Function < Person, Boolean > vowelVerifier = Functions.forPredicate(allowNamesWithVowels);
ArrayList < Person > people = Lists.newArrayList(anakin, bilbo, alex);
List transform2 = Lists.transform(people, vowelVerifier);
// Vowel verifier will return TRUE (FALSE) for each
// person with TRUE (FALSE) Predicate result
System.out.println("Result: "+transform2);
//[true, false, true]
// anakin and alex are true because the Predicate
// was checking to see if the first name of the Person
// begins with a vowel



CharMatcher is basically a Predicate who accepts Character (instead of objects). Due to this fact you can easily convert your Predicate to a CharMatcher. Lets assume that we have a Predicate that allows vowels only.



Predicate < Character > allowVowel = new Predicate < Character > (){
char[] vowels = { 'a', 'e', 'i', 'o', 'u', 'y' };
@Override
public boolean apply(Character character) {
char lowerCase = Character.toLowerCase(character.charValue());

for(char chr : vowels){
if(chr == lowerCase)
return true;
}

return false;
}
};



With forPredicate() we can build a CharMatcher from a Predicate. Lets use our new CharMatcher for counting vowels in a String.




CharMatcher vowelMatcher = CharMatcher.forPredicate(allowVowel);
int vowelCount = vowelMatcher.countIn("starkiller");
System.out.println("Vowel Count in 'starkiller': "+vowelCount);



Predicates class contains several useful methods for creating Predicates. If you plan using Predicates you should take a look. Lets see its methods.

Assume that we don't want people with long last names. Our character limit can be 8 for instance. Lets write a Predicate for the purpose.


Predicate < Person > allowShortLastNames =
new Predicate < Person > (){
@Override
public boolean apply(Person person) {
String lastName = person.getLastName();

return (lastName.length() < 8);
}
};

System.out.println(setOfPerson);
// [Alex Delarge, Frodo Baggins, Anakin Skywalker,
// Luke Skywalker, Bilbo Baggins, Sezin Karli]
Set onlyShortLastNames =
Sets.filter(setOfPerson, allowShortLastNames);
System.out.println(
"People with short last names: "+onlyShortLastNames);
//[Sezin Karli, Alex Delarge, Bilbo Baggins, Frodo Baggins]



Because Skywalker is more than 7 characters it's filtered out. All other last names are in line with the predicate but what if I want a more complicated Predicate? For instance one which will accept people with first name that begins with a vowel and with short last name. I can combine 2 Predicates in this case.


Predicate < Person > combinedPredicates =
Predicates.and(allowShortLastNames, allowNamesWithVowels );
Set < Person > filter = Sets.filter(setOfPerson, combinedPredicates);
System.out.println("Combined Predicates: "+filter);
// Alex Delarge



We know that longer last names will return false and we will be left with shorter last names. e.g. [Bilbo Baggins, Alex Delarge, Sezin Karli, Frodo Baggins].Then we will apply the vowel Predicate which will give [Alex Delarge] because Alex is the only first name that begins with a vowel. All Predicates that are combined will be applied for each element in the Iterable (Set, here). If one of the Predicates are false then the overall result is false else it will return true.

There are three and() methods. We show the one that'll work with 2 Predicates. There's a version that'll work with more than 2 Predicates (var-arg as argument). The last one takes Predicates from an Iterable that contains them (Iterable ). We previously used the boolean and() operator. It's also possible to combine Predicates with or() operator. or() will return true even one of the results is true if none is true then it'll return false. Assume that I want either short first names or short last names. I have a Predicate for last names. Lets add a new one for first names.



Predicate < Person > allowShortFirstNames = new Predicate < Person > (){
@Override
public boolean apply(Person person) {
String firstName = person.getName();

return (firstName.length() < 8);
}
};

Predicate < Person > predicatesCombinedWithOr =
Predicates.or(allowShortLastNames, allowShortFirstNames);
filter = Sets.filter(setOfPerson, predicatesCombinedWithOr);
System.out.println(setOfPerson);
System.out.println("Combined with or:"+filter);

// As you can see nothing is filtered because
// setOfPerson elements have either short
// first names or short last names
// Lets add a person with a long first name
// and last name

setOfPerson.add(new Person("Abdullah", "Jabbarian"));
filter = Sets.filter(setOfPerson, predicatesCombinedWithOr);
System.out.println("Combined with or 2: "+filter);
// [Alex Delarge, Frodo Baggins, Anakin Skywalker,
// Luke Skywalker, Bilbo Baggins, Sezin Karli]

// As you can see Abdullah who has a long first and last name
// is not in the new set.


It's possible to negate the Predicate in hand using not() method. Lets negate the combined predicate we created using and(). Normally we are accepting people with first name that begins with a vowel and with short last name. If we negate this the Predicate will accept people with first name that begin with a consonant and with a long last name.


Predicate negatedPredicates =
Predicates.not(combinedPredicates);

//Alex delarge was the only person with the predicate in hand.
//When we negate the predicate
//we will get a predicate that will return
//every element but alex delarge

filter = Sets.filter(setOfPerson, negatedPredicates);
System.out.println("Negated and combined Predicates: "+filter);
// [Frodo Baggins, Anakin Skywalker, Abdullah Jabbarian,
// Luke Skywalker, Bilbo Baggins, Sezin Karli]



Anakin is the only one with a vowel in the first name but as his last name is long he's allowed by the Predicate.

You can create two interesting Predicates using isNull() and notNull(). isNull() (notNull()) returns a Predicate that returns true if the object in hand is null (not null).


setOfPerson.add(null);
System.out.println(setOfPerson);
//[null, Alex Delarge, Frodo Baggins, Anakin Skywalker,
// Abdullah Jabbarian, Luke Skywalker, Bilbo Baggins, Sezin Karli]

Predicate < Person > notNullPredicate = Predicates.notNull();
// this predicate will return true if and
// only if the Person in hand is not null
Set < Person > filter2 = Sets.filter(setOfPerson, notNullPredicate);
System.out.println(filter2);

//[Alex Delarge, Frodo Baggins, Anakin Skywalker,
//Abdullah Jabbarian, Luke Skywalker, Bilbo Baggins, Sezin Karli]



As you can see the null value is not present in the new view Set.

It's possible to use Predicates' contains() or containsPattern() methods for building a Predicate that'll accept only CharSequences that contain the given pattern. contains() takes a Pattern object while containsPattern() takes a regular expression as a String.


// the Predicate below only accepts words with a number inside
Predicate < CharSequence > containsPattern =
Predicates.containsPattern("[0-9]");
Set sw = Sets.newHashSet("r2-d2", "luke", "c3po", "darth");
Set filter3 = Sets.filter(sw, containsPattern);
System.out.println("New set of sw characters "+filter3);

// contains and containsPattern are similar
Predicate < CharSequence > containsPattern2 =
Predicates.contains( Pattern.compile("[0-9]") );
Set < String > filter4 = Sets.filter(sw, containsPattern2);
System.out.println("Do they have the same result? "+filter4.equals(filter3));


Another useful method of Predicates is in(). It allows elements that are inside a given Collection and rejects all others.



ArrayList < String > fruitList =
Lists.newArrayList("apple", "melon", "strawberry");
Predicate < String > fruitPredicate = Predicates.in(fruitList);
List < String > fruitBasket =
Lists.newArrayList("apple", "mango", "melon", "banana", "apple", "melon", "strawberry", "melon", "strawberry");
Iterable < String > filter5 = Iterables.filter(fruitBasket, fruitPredicate);
// As Lists does not have a filter method I used from Collections2

System.out.println("Filtered Fruit Basket: "+filter5);
// Filtered Fruit Basket: [apple, melon, apple,
// melon, strawberry, melon, strawberry]
// only 3 types of fruits are left as you can see

// If you dont need a live view you can easily use retainAll()
// for both List and Set which has a similar effect.
fruitBasket.retainAll(fruitList);
System.out.println("Filtered Fruit Basket: "+fruitBasket);



Predicates' instanceOf() checks if the object in hand is an instance of the given Class.


Predicate < Object > listPredicate = Predicates.instanceOf(List.class);
List < Collection < collectionsList =
new ArrayList < Collection > ();
collectionsList.add(new HashSet());
collectionsList.add(new ArrayList());
collectionsList.add(new LinkedList());
collectionsList.add(new TreeSet());
collectionsList.add(new LinkedHashSet());

Collection < Collection > filter6 =
Collections2.filter(collectionsList, listPredicate);
System.out.println("Are only Lists filtered? "
+ (filter6.size() == 2) );
// only two elements are allowed



Only instances of List are allowed so the result contains only the ArrayList and LinkedList.

Another interesting method of Predicates is compose(). compose() method allows you to process all your elements using the given Function and apply a Predicate on the result. You can do that in 2 steps but when you can do that in a single step do you need it? I have a set of objects of type Circle. I'd like to filter Circles with their area less than a value. It's possible do the calculation of the area in the Predicate or we can put the method in a helper class (e.g. Circles.getArea()). Another possibility is to write a Function for this operation.


Function < Circle, Double > calculateArea =
new Function < Circle, Double > () {

@Override
public Double apply(Circle circle) {
double radius = circle.getRadius();
double area = Math.PI * radius * radius;

return area;
}
};

Predicate < Double > allowCirclesWithLargeArea =
new Predicate < Double > () {
double value = 10d;
@Override
public boolean apply(Double area) {
return area > value;
}
};

Predicate < Circle > allowCircles =
Predicates.compose(allowCirclesWithLargeArea, calculateArea);

HashSet < Circle > circleSet =
Sets.newHashSet( new Circle(3.5, 4.5, 0.3),
new Circle(2.5, 1.5, 0.03), new Circle(3.4, 4.6, 4.3),
new Circle(3.5, 6.5, 5.3), new Circle(0.5, 4.5, 12.3));

Set < Circle > filter7 =
Sets.filter(circleSet, allowCircles);
System.out.println(
"Allow circle with area less than 10: "+filter7);
// output: Allow circle with area less than 10:
//[{x=0.5, y=4.5, radius=12.3},
// {x=3.5, y=6.5, radius=5.3},
//{x=3.4, y=4.6, radius=4.3}]



Each circle is first processed by calculateArea Function and their areas are obtained. Then allowCirclesWithLargeArea Predicate can be used for filtering the areas in hand.

Iterables class contains several methods for working using Predicates. all() and any() are quite straightforward as methods. all (any) returns true if all (any) elements in the given Iterable satisfy the given Predicate. I'm going to create Band class for storing two attributes of a Band (its name and country of origin). Then create a country Predicate so we can see what all() and any() do.


class Band{
String name, country;
public Band(String name, String country) {
this.name = name;
this.country = country;
}
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getCountry() {
return country;
}

public void setCountry(String country) {
this.country = country;
}

@Override
public String toString() {
return "{name= "+name+", country= "+country+"}";
}
}


Now lets create the bands.


Band stratovarius = new Band("Stratovarius", "Finland");
Band amorphis = new Band("Amorphis", "Finland");

Set < Band > metalBands =
Sets.newHashSet(stratovarius, amorphis);

Predicate < Band > finlandPredicate =
new Predicate < Band > () {
@Override
public boolean apply(Band band) {
boolean result =
band.getCountry().equals("Finland");
// is band country Finland?

return result;
}
};


// Easy to predict what I'll do right?
//I have two bands from Finland and a Finland Predicate.
//So all() should return true to me because every band
//is from Finland.


boolean all =
Iterables.all(metalBands, finlandPredicate);
System.out.println(
"every band is from Finland? "+all); // TRUE

// Lets add Mezarkabul to the band set
// With this addition not every element
// satisfies the Predicate

Band mezarkabul = new Band("Mezarkabul", "Turkey");
metalBands.add(mezarkabul) ;

all = Iterables.all(metalBands, finlandPredicate);
System.out.println(
"every band is from Finland? "+all); // FALSE


// Still any() should return true because
// at least one element is still satisfying the
// Predicate.


boolean any = Iterables.any(metalBands, finlandPredicate);
System.out.println(
"at least one band is from Finland? "+any);



Lets remove all bands form Finland and see any()s behavior. There are more than one way to do that. The most straightforward way is to use removeIf() method of Iterables class which removes elements that satisfy the given Predicate from the given Iterable.


Iterables.removeIf(metalBands, finlandPredicate);
// you can see that none of the bands are from Finland as of now
System.out.println(metalBands);
// output: [{name= Mezarkabul, country= Turkey}]

//lets see any()'s behavior now

any = Iterables.any(metalBands, finlandPredicate);
System.out.println("at least one band is from Finland? "+any);


Iterables' filter() method can be used for filtering any Iterable using Predicates. It's similar to Collections2's filter method but usable for a larger number of Classes.

Iterables class has two more interesting methods. indexOf() which returns the index of the first element that satisfies the given Predicate. If it cant find a matching element it returns -1. The other method is find() which returns the first element which satisfies the given Predicate. If it cant find a matching element it throws NoSuchElementException. To sum up find() returns the object, indexOf() returns its index in the Iterable. As find() throws an exception in the case it couldnt find a matching element it would be wise to check first with indexOf() and then either use the index to obtain the object or to use find().



metalBands.add(new Band("Lordi", "Finland"));

// returns the index of the
// first element that satisfies
// the predicate which's
// Lordi

if(Iterables.indexOf(metalBands, finlandPredicate) != -1){
// here we are sure that
// such a band exists
Band bandFromFinland =
Iterables.find(metalBands, finlandPredicate);
System.out.println("The first band from Finland: "
+bandFromFinland);
}


This is all for the Predicates. I hope I was able to give you a taste of functional programming in Java using Guava.

No comments:

Post a Comment