Question

I have a Java collection containing different Java primitives:

List<Object> list = new ArrayList<>();
list.add(100);
list.add(10L);
list.add("KONG")
list.add(102.78);

Now I want to sort these different types into a "natural" order, like Excel does:

10, 100, 102.78, KONG

Does anyone know of an easy way to implement Comparator for this type of sort? Or a library that wraps all the laborious instanceof and casting stuff?

A good candidate "natural" order would be numbers first, then strings in alphabetical order, just as in the example above.

Was it helpful?

Solution 2

I like this way

    String[] ss = { "100", "10", "KONG", "102.65" };
    List<String> s = Arrays.asList(ss);
    Collections.sort(s, new Comparator<String>() {

        @Override
        public int compare(String o1, String o2) {
            if (isNumber(o1)) {
                if (isNumber(o2)) {
                    // o1 = number
                    // o2 = number
                    return (Double.valueOf(o1).compareTo(Double.valueOf(o2)));
                } else {
                    // o1 = number
                    // o2 = string
                    return -1;
                }
            } else {
                if (isNumber(o2)) {
                    // o1 = string
                    // o2 = number
                    return 1;
                } else {
                    // o1 = string
                    // o2 = string
                    return o1.compareTo(o2);
                }
            }
        }

        private boolean isNumber(String s) {
            try {
                Double.parseDouble(s);
                return true;
            } catch (NumberFormatException nfe) {
                return false;
            }
        }

    });
    System.out.println(s);
}

OTHER TIPS

Just convert them all to Strings (use toString(), box where needed, make sure you handle null properly) and then sort their String representations. This is the easiest way, I think.

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

public class Test53 {

    public static void main(String[] args) {
        List<Object> list = new ArrayList<Object>();
        list.add(null);
        list.add("");
        list.add(null);
        list.add("5");
        list.add(5);
        list.add("2");
        list.add(20);
        list.add(100);
        list.add(10L);
        list.add("9");
        list.add("KONG");
        list.add("KONGGG");
        list.add(102.78);
        Collections.sort(list, new SpecialComparator());
        System.out.println(Arrays.toString(list.toArray()));
    }

}

class SpecialComparator implements Comparator<Object> {

    public int compare(Object o1, Object o2) {
        if (o1 == null && o2 != null) return -1;
        else if (o1 != null && o2 == null) return 1;
        else if (o1 == null && o2 == null) return 0;

        else {
            Class<?> c1 = o1.getClass();
            Class<?> c2 = o2.getClass();

            if (Number.class.isAssignableFrom(c1) && String.class.isAssignableFrom(c2)){
                return -1;
            }else if (Number.class.isAssignableFrom(c2) && String.class.isAssignableFrom(c1)){
                return 1;
            }
            else if (Number.class.isAssignableFrom(o1.getClass()) && Number.class.isAssignableFrom(o2.getClass())){
                double d = ((Number)o1).doubleValue() - ((Number)o2).doubleValue();
                if (Math.abs(d)<1e-8) return 0;
                else return (d<0) ? -1 : 1;
            }
            else {
                return o1.toString().compareTo(o2.toString());
            }
        }
    }
}

This is very quick and very ugly solution, which first came to my mind - nonetheless, it works. All optimizations are up to you depending on what exactly do you want and how are you going to use it.

import java.util.Arrays;

public class Value implements Comparable<Value> {
    private String strValue = null;
    private Double numValue = null;
    private Type   type     = null;

    private enum Type {
        NUMERIC,
        STRING;
    }

    public Value(String value) {
        this.strValue = value;
        this.type     = Type.STRING;
    }

    public Value(long value) {
        this.numValue = (double) value;
        this.type     = Type.NUMERIC;
    }

    public Value(double value) {
        this.numValue = value;
        this.type     = Type.NUMERIC;
    }

    @Override
    public int compareTo(Value cmpWith) {
        if (this.type == Type.NUMERIC && cmpWith.type == Type.NUMERIC) {
            return (int) (this.numValue - cmpWith.numValue);
        } else if (this.type == Type.NUMERIC && cmpWith.type == Type.STRING) {
            return -1;
        } else if (this.type == Type.STRING && cmpWith.type == Type.NUMERIC) {
            return 1;
        } else if (this.type == Type.STRING && cmpWith.type == Type.STRING) {
            String[] tmp = new String[]{this.strValue, cmpWith.strValue};
            Arrays.sort(tmp);

            return (tmp[0] == this.strValue) ? -1 : 1;
        }

        return 0;
    }

    public static void main(String[] args) {
        Value[] values = new Value[]{
            new Value(10), new Value(100), new Value(102.78),
            new Value("KONG"), new Value(-1), new Value("SuperKong")
        };

        Arrays.sort(values);

        for (Value val : values) {
            System.out.println((val.type == Type.NUMERIC) ? val.numValue : val. strValue);
        }
    }
}

Using the suggestions above; the solution ended up very simple:

/**
 * Compare objects of different types or int, long, float, double and string to 
 * produce an order with numbers first, then strings.
 */
public class DifferentObjectsComparator implements Comparator<Object> {

    @Override
    public int compare(Object o1, Object o2) {
        //Both strings
        if (o1 instanceof String && o2 instanceof String) {
            return o1.toString().compareTo(o2.toString());
        }
        //Both numbers
        if (o1 instanceof Number && o2 instanceof Number) {
            return Double.compare(
               ((Number) o1).doubleValue(), ((Number) o2).doubleValue()
            );
        }
        //Number then string
        if (o1 instanceof Number) {
            return -1;
        }
        //String then number
        return 1;
    }

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top