4.6. Fixed-Order Comparison

Problem

You need to sort a collection of objects that have a preestablished order, such as the days of the week or the order of planets in the solar system.

Solution

Use FixedOrderComparator in Jakarta Commons Collections. When using FixedOrderComparator, you supply an array of objects in a sorted order and the Comparator returns comparison results based on the order of the objects in this array. The following example uses a fixed string array to compare different Olympic medals:

import java.util.*;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.FixedOrderComparator;

String[] medalOrder = {"tin", "bronze", "silver", "gold", "platinum"};

Comparator medalComparator = new FixedOrderComparator( medalOrder );
Comparator athleteComparator = new BeanComparator( "medal", medalComparator );

Athlete athlete1 = new Athlete( );
Athlete athlete2 = new Athlete( );

int compare = medalComparator.compare( athlete1.getMedal( ), 
athlete2.getMedal( ) );

In this code, a FixedOrderComparator compares two Athletes by the value of the medal property. The medal property can be “tin,” “bronze,” “silver,” “gold,” or “platinum,” and a FixedOrderComparator uses the order of the medalOrder array to compare these values. The medalOrder array establishes a fixed relationship between the three medal types; “bronze” is less than “silver,” which is less than “gold.”

Discussion

Use FixedOrderComparator when sorting an array or a collection that contains values that are ordered in a pre-determined fashion: days of the week, planets in the solar system, colors in the spectrum, or hands dealt in a poker game. One way to sort an array containing the days of the week would be to assign a numerical value to each day—“Monday” is one, “Tuesday” is two, “Wednesday” is three, etc. Then you could sort the array with a Comparator that takes each day’s name, sorting elements based on the numerical value corresponding to a day’s name. An alternative is the use of FixedOrderComparator, letting the comparator order objects based on the order of an array of day names.

If a bean contains a property to be sorted according to a fixed-order array, you can use the BeanComparator in conjunction with FixedOrderComparator. The following example sorts cards by value and suit using a FixedOrderComparator and a BeanComparator; A PlayingCard object, defined in Example 4-3, is sorted according to the order of two arrays—one for the face value of the PlayingCard and one for the suit of the PlayingCard.

Example 4-3. A bean representing a playing card

package org.discursive.jccook.collections.compare;

public class PlayingCard( ) {

    public static String JOKER_VALUE = null;
    public static String JOKER_SUIT = null;

    private String value;
    private String suit;

    public PlayingCard( ) {}
    public PlayingCard(String value, String suit) {
        this.value = value;
        this.suit = suit;
    }

    public String getValue( ) { return value; }
    public void setValue(String value) { this.value = value; }

    public String getSuit( ) { return suit; }
    public void setSuit(String suit) { this.suit = suit; }

    public String toString( ) {
        String cardString = "JOKER";
        if( value != null && suit != null ) {
            cardString = value + suit;
        }
        return cardString;
    }
}

Example 4-4 creates a ComparatorChain of BeanComparators, which compares the value and suit properties using a FixedOrderComparator. Each card’s suit is compared first, and, if two cards have the same suit, they are compared by face value. Jokers do not have suits or a face value, and this example handles jokers with a null-valued suit and value property by wrapping each FixedOrderComparator with a NullComparator.

Example 4-4. Combining FixedOrderComparator with BeanComparator, NullComparator, and ComparatorChain

package com.discursive.jccook.collections.compare;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.beanutils.BeanComparator;

import org.apache.commons.collections.comparators.NullComparator;
import org.apache.commons.collections.comparators.FixedOrderComparator;
import org.apache.commons.collections.comparators.ComparatorChain;

public class FixedOrderExample {

    // Suit order "Spades", "Clubs", "Diamonds", "Hearts"
    private String[] suitOrder = { "S", "C", "D", "H" };

    private String[] valueOrder = { "2", "3", "4", "5", "6", "7", "8", 
                                    "9", "10", "J", "Q", "K", "A" };

    public static void main(String[] args) {
        FixedOrderExample example = new FixedOrderExample( );
        example.start( );
    }

    public void start( ) {

        List cards = new ArrayList( );
        cards.add( PlayingCard( "J", "C" ) );
        cards.add( PlayingCard( "2", "H" ) );
        cards.add( PlayingCard( PlayingCard.JOKER_VALUE, PlayingCard.
        JOKER_SUIT));
        cards.add( PlayingCard( "2", "S" ) );
        cards.add( PlayingCard( "Q", "S" ) );
        cards.add( PlayingCard( "4", "C" ) );
        cards.add( PlayingCard( "J", "D" ) );

         System.out.println( "Before sorting: " + printCards( cards ) );

        // Create a null-safe suit order comparator that will compare the
        // suit property of two Java beans
        Comparator suitComparator = new FixedOrderComparator( suitOrder );
        suitComparator = new NullComparator( suitComparator );
        suitComparator = new BeanComparator( "suit", suitComparator );

        // Create a null-safe value order comparator that will compare the
        // value property of two Java beans
        Comparator valueComparator = new FixedOrderComparator( valueOrder );
        valueComparator = new NullComparator( valueComparator );
        valueComparator = new BeanComparator( "value", valueComparator );

        // Create a chain of comparators to sort a deck of cards
                          Comparator cardComparator = new ComparatorChain( );
                          cardComparator.addComparator( suitComparator );
                          cardComparator.addComparator( valueComparator );

                          Collections.sort( cards, cardComparator );

            System.out.println( "After sorting: " + printCards( cards ) );
    }

    private String printCards( List cards ) {
        StringBuffer resultBuffer = new StringBuffer( );
        Iterator cardIter = cards.iterator( );
        while( cardIter.hasNext( ) ) {
           PlayingCard card = (PlayingCard) cards.next( );
           resultBuffer.append( " " + card.toString( ) );
        }
        return resultBuffer.toString( );
    }
}

This example sorts the PlayingCard objects and produces the following output:

Before sorting: JC 2H JOKER 2S QS 4C JD
After sorting: 2S QS 4C JC JD 2H JOKER

The list is sorted such that all the cards of a similar suit are grouped together—spades are less than clubs, clubs are less than diamonds, and diamonds are less than hearts. A sorted collection of cards is grouped by suits, and, within each suit, cards are organized according to face value—2 is low and aces is high. The order used in the sorting is captured in two fixed-order arrays, suitOrder and faceOrder. If a shuffled deck were used in the example, it would end up as a perfectly sorted deck of cards.

Example 4-4 ties a number of simple Comparators together to perform a fairly complex sort. A FixedOrderComparator is wrapped in a NullComparator, which is then wrapped with a BeanComparator. These BeanComparator instances are then combined in a ComparatorChain. The use of NullComparator with a BeanComparator is recommended to avoid a NullPointerException from BeanComparator; BeanComparator is not designed to handle null-valued bean properties, and it throws an exception if you ask it to play nice with nulls.

See Also

BeanComparator is discussed in Recipe 3.10. This helpful utility is indispensable if you are working with systems that need to sort JavaBeans.

For more information about the ComparatorChain object, see Recipe 4.4. For more information on the NullComparator, see Recipe 4.5.

Get Jakarta Commons Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.