Showing posts with label sort. Show all posts
Showing posts with label sort. Show all posts

Thursday, May 13, 2010

google's guava library tutorial part 2: joys of Ordering

In the first part of my tutorial , I explained anything related to Strings in Guava. In this part of my tutorial, I will explain the Ordering class which combines the power of Comparators with Collections' functionality and which adds other interesting features (such as compound comparators). This class is really useful if you need to order your Iterable, find the maximum/minimum element in your Iterable, find the index of an arbitrary element. It implements Comparator interface for backward compatibility.

I will use a basic Employee class during my examples. Employee is a mutable class with three attributes: id, name and years of service..



class Employee implements Comparable < Employee > {
private int id;
private String name;
private int yearsOfService;

public Employee(int id, String name, int yearsOfService){
this.id = id;
this.name = name;
this.yearsOfService = yearsOfService;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getYearsOfService() {
return yearsOfService;
}
public void setYearsOfService(int yearsOfService) {
this.yearsOfService = yearsOfService;
}
@Override
public int compareTo(Employee employee) {
return this.getName().compareTo(employee.getName());
}

@Override
public String toString() {
String toString = "id="+id
+"-name="+name+"-years of service="+yearsOfService;

return toString;
}

}


As I implemented Comparable in the Employee class, I have to add compareTo(). In this method I only compare the name of the employees. I'll create three employees and add them to an ArrayList.


Employee sezinKarli = new Employee(4, "Sezin Karli", 4);
Employee darthVader = new Employee(3, "Darth Vader", 5);
Employee hanSolo = new Employee(2, "Han Solo", 10);
List < Employee > employeeList =
Lists.newArrayList(sezinKarli, hanSolo, darthVader);
System.out.println("employee list: "+employeeList);
//output:
//employee list: [id=4-name=Sezin Karli-years of service=4,
// id=2-name=Han Solo-years of service=10,
// id=3-name=Darth Vader-years of service=5]


There are many ways to create an Ordering. You can create one using the good old Comparator, using a predefined list of objects that explicitly imposes the ordering strategy, using the toString() method of the objects in hand and using the compareTo() method of the objects. You'll need to implement Comparable in your object if you want to take the compareTo() road. Lets create two comparators. One for comparing the years of service and another for employee id.


Comparator < Employee > yearsComparator =
new Comparator < Employee >(){
@Override
public int compare(Employee employee1, Employee employee2) {
return (
employee1.getYearsOfService() - employee2.getYearsOfService());
}
};

Comparator < Employee > idComparator =
new Comparator < Employee >(){
@Override
public int compare(Employee employee1, Employee employee2) {
return (employee1.getId() - employee2.getId());
}
};


//Create an Ordering from a Comparator
Ordering < Employee > orderUsingYearsComparator =
Ordering.from(yearsComparator);
//Sort the employee list using years comparator
List < Employee > sortedCopy =

orderUsingYearsComparator.sortedCopy(employeeList);
System.out.println(
"sorted copy based on years of service comparator: "+sortedCopy);
/*
output:
sorted copy based on years of service comparator:
[id=4-name=Sezin Karli-years of service=4,
id=3-name=Darth Vader-years of service=5,
id=2-name=Han Solo-years of service=10]

*/


The example above is not different from calling Collections.sort() with the comparator in hand but notice that we don't sort Collections but Iterables.

With explicit(), we create an Ordering where we impose an order explicitly.
I want to order en enum type (colors) and I want red, blue, green as order. I created the enum type as instance variable.

static enum Colors{RED, GREEN, BLUE, YELLOW};


Not lets see the Ordering part.

Ordering < Colors > explicitOrdering =
Ordering.explicit(Colors.RED, Colors.BLUE, Colors.GREEN);

List < Colors > colorList =
Lists.newArrayList(Colors.BLUE, Colors.RED,
Colors.BLUE, Colors.GREEN, Colors.RED);

List < Colors > sortedCopy8 =
explicitOrdering.sortedCopy(colorList);
System.out.println("ordered color list: "+sortedCopy8);
//ordered color list: [RED, RED, BLUE, BLUE, GREEN]
//we imposed the RED, BLUE, GREEN order and
//the result conforms to that


Notice that explicit() works only with the given objects. You can't sort you iterable if you didnt specify the order in explicit(). If you try to to that you'll get IncomparableValueException.

Let me show you how to create an Ordering using toString() method of the object in hand.


Ordering < Object > usingToString = Ordering.usingToString();
// returns an ordering which uses the natural
// order comparator on toString() results of objects
List < Employee > sortedCopy3 =
usingToString.sortedCopy(employeeList);
System.out.println("sorted usingToString: "+sortedCopy3);

/*
sorted usingToString:
[id=2-name=Han Solo-years of service=10,
id=3-name=Darth Vader-years of service=5,
id=4-name=Sezin Karli-years of service=4]
*/


Lets create an Ordering with natural ordering (using compareTo() of the objects)


Ordering < Employee > natural = Ordering.natural();
List < Employee > sortedCopy4 = natural.sortedCopy(employeeList);
System.out.println("sorted with natural: "+sortedCopy4);
/*
sorted with natural:
[id=3-name=Darth Vader-years of service=5,
id=2-name=Han Solo-years of service=10,
id=4-name=Sezin Karli-years of service=4]

*/


We can do binary search on the sorted list for an element. Notice that the Ordering that calls the binary search method and Ordering that sorted the Iterable in hand must be the same. binarySearch() was available in Collections class just like min() and max() we'll see in next examples.


int binarySearch = natural.binarySearch(sortedCopy4, sezinKarli);
System.out.println("My index in the list: "+binarySearch); // 2


I will add elements of employee list, 2 null elements to a new list so we can explore new methods related to the handling of null elements.


List < Employee > employeeListWithNulls =
new ArrayList < Employee > (employeeList);
employeeListWithNulls.add(null);
employeeListWithNulls.add(null);



2 methods can be used for changing the handling of null elements. nullsFirst(), (nullsLast()) returns an Ordering which puts null elements to the beginning (end) of every non-null value.


// sort the employee list with natural ordering
// and put the null elements to the beginning
List < Employee > sortedCopy5 =
natural.nullsFirst().sortedCopy(employeeListWithNulls);
System.out.println("nulls first: "+sortedCopy5);
/*
nulls first:
[null, null, id=3-name=Darth Vader-years of service=5,
id=2-name=Han Solo-years of service=10,
id=4-name=Sezin Karli-years of service=4]

*/

// sort the employee list with natural ordering
//and put the null elements to the ending
List < Employee > sortedCopy6 =
natural.nullsLast().sortedCopy(employeeListWithNulls);
System.out.println("nulls last: "+sortedCopy6);

/*
nulls last:
[id=3-name=Darth Vader-years of service=5,
id=2-name=Han Solo-years of service=10,
id=4-name=Sezin Karli-years of service=4, null, null]

*/


I'd like to find the employee with the highest (lowest) time of service. I can do a sort based on years of service but that'll be slower then doing a single sweep for finding the element with max (min) year of service.


Employee employeeWithMaxYearsOfService =
orderUsingYearsComparator.max(employeeList);
System.out.println(employeeWithMaxYearsOfService);
// Han solo has the biggest year of service for the company

//lets find the minimum
Employee employeeWithMinYearsOfService =
orderUsingYearsComparator.min(employeeList);
System.out.println(employeeWithMinYearsOfService);
// Sezin has the smallest year of service for the company


/*
* min() and max() can be used for any iterable.
* Notice that these methods are overloaded and
* it's possible to use them on two elements or
* an arbitrary number of elements (var-arg)
* */

//Sezin or Darth has the longest year of service?
Employee max =
orderUsingYearsComparator.max(sezinKarli, darthVader);
System.out.println(max); //Vader

//Lets see the var-arg version,
// The result below must be the same
//with employeeWithMaxYearsOfService
//because we used each element in the
// employee list and the same Ordering
Employee max2 =
orderUsingYearsComparator.max(sezinKarli, darthVader, hanSolo);
System.out.println(
"Are the max results the same?: "
+max2.equals(employeeWithMaxYearsOfService)); //true



In each ordering we defined an increasing order. We can reverse the Ordering calling reverse() and obtaining a new Ordering. Lets build a new ordering that'll use the idComparator.


Ordering < Employee > reverseIdOrdering =
Ordering.from(idComparator).reverse();

// We will have an ordering of decreasing employee ids if we use
// this Ordering
List < Employee > employeeWithReverseIdOrder =

reverseIdOrdering.sortedCopy(employeeList);
System.out.println(
"employeeWithReverseIdOrder:"+ employeeWithReverseIdOrder);
/*
employeeWithReverseIdOrder:[
id=4-name=Sezin Karli-years of service=4,
id=3-name=Darth Vader-years of service=5,
id=2-name=Han Solo-years of service=10]

*/
// As you can see the ids are 4, 3 and
//2 (an array with decreasing order



A useful ability of Orderings is the fact that you can combine multiple Comparators with it. Call compound() for this. If the result of comparator1 is 0 then the second one will be called and this will continue until a non-zero value (a larger/smaller relationship) is obtained from the comparator. If all comparators return 0 then the elements are treated as equal (we get 0 from the combined comparator).


List < Employee > newEmployeeList =
Lists.newArrayList(new Employee(1, "Mr Pink", 8),
new Employee(2, "Mr Brown", 8), new Employee(3, "Mr Green", 3),
new Employee(4, "Mr Yellow", 5));

//We previously created an Ordering
// by id (orderUsingYearsComparator)
//Now lets add id comparator to this Ordering

Ordering < Employee > combinedComparatorOrdering =
orderUsingYearsComparator.compound(idComparator);
List < Employee > sortedCopy7 =
combinedComparatorOrdering.sortedCopy(newEmployeeList);
//First by years of service then by id
System.out.println("Combined comparators: "+sortedCopy7);
/*Combined comparators:
[id=3-name=Mr Green-years of service=3,
id=4-name=Mr Yellow-years of service=5,
id=1-name=Mr Pink-years of service=8,
id=2-name=Mr Brown-years of service=8]
*/



As you can see the years of service increase (3 then 5 then two 8). When years of service comparator see that the result is a draw the second comparator is called. As you can see Mr Pink and Mr Brown have the same years of service so their id are inspected and the order between them is calculated. As the id of Mr Pink is less than Mr Brown's he's before Mr Brown.

This is all for compound comparators.
There are two slightly different methods for checking if the iterable in hand is ordered. isOrdered() checks if each element is less than/equal to the subsequent element. isStrictlyOrdered() checks if each element is strictly less than the subsequent element.


List < Integer > integers = Lists.newArrayList(1, 2, 3, 4);
// This should return true from isOrdered() and isStrictlyOrdered()
// because the numbers are increasing( isOrdered() = true)
// and there's no consecutive and equal
// elements (isStrictlyOrdered = true)

boolean ordered = Ordering.natural().isOrdered(integers);
System.out.println("isOrdered: "+ordered);
boolean strictlyOrdered =
Ordering.natural().isStrictlyOrdered(integers);
System.out.println("isStrictlyOrdered: "+strictlyOrdered);

//Lets use another list with equal elements inside

List < Integer > newIntegers =
Lists.newArrayList(1, 2, 3, 3, 4);
// The numbers are increasing( isOrdered() = true)
// and there's consecutive and equal
// elements (isStrictlyOrdered = false)

ordered = Ordering.natural().isOrdered(newIntegers);
System.out.println("isOrdered: "+ordered);
strictlyOrdered =
Ordering.natural().isStrictlyOrdered(newIntegers);
System.out.println("isStrictlyOrdered: "+strictlyOrdered);



Using functions as Ordering is quite interesting, but I will explain functional programming capabilities later so lets skip it for the moment.

Saturday, August 8, 2009

how to sort using comparators

Although Collections class' sort method is quite handy, there will be times you'll need to do tricky stuff while sorting. But you can still use this static method with your Comparator that'll ensure the desired sorting behavior.

Lets quote the definition of Comparator class:
A comparison function, which imposes a total ordering on some collection of objects. Comparators can be passed to a sort method (such as Collections.sort or Arrays.sort) to allow precise control over the sort order


Assume that we want to sort a List of floats by considering only the part after the dot. So we will consider 63 of 3.63 and 72 of 2.72 and their increasing order will be {3.63, 2.72} because 63 < 72. The code of the Comparator is below.


// we use generics (Float here) for avoiding a downcast
class FloatComparator implements Comparator < Float >{

public int compare(Float f1, Float f2) {
// for instance, 323.432 - 323 ~= 0.432
Float f1_ = f1 - f1.intValue(); /* intValue() returns the floor integer (not the closest integer) */
Float f2_ = f2 - f2.intValue();
/* after stripping the float from the integer part we use float's compareTo function compare the remaining part of the number */
return f1_.compareTo(f2_);
}


}




We implemented the Comparator interface in the class. In public int compare method we first remove the integer part of the float and then compare the rest. If you must do more trickier stuff you have to consider few things. I'm quoting from Sun's Comparator documentation:

The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y. (This implies that compare(x, y) must throw an exception if and only if compare(y, x) throws an exception.)

The implementor must also ensure that the relation is transitive: ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0.

Finally, the implementor must ensure that compare(x, y)==0 implies that
sgn(compare(x, z))==sgn(compare(y, z)) for all z.



As we use the compareTo method of Float, we don't have to think about the above cases. They are already handled.

Lets see our FloatComparator in action.


public class SortingWithComparator {

public static void main(String[] args) {
List < Float > numberList = new ArrayList < Float > ();
numberList.add(323.432F);
numberList.add(34.234F);
numberList.add(-81.01F);
numberList.add(49.736F);

for(Float number : numberList)
System.out.println(number);

Collections.sort(numberList, new FloatComparator()); /* sort numberList using the FloatComparator */
System.out.println("------- after sorting takes place -----");

for(Float number : numberList)
System.out.println(number);

}
}


The output is:


323.432
34.234
-81.01
49.736
------- after sorting takes place -----
-81.01
34.234
323.432
49.736