quoci
quoci

Reputation: 3557

How to sort an ArrayList<String> using Comparator?

Hey guys I'm trying to make a scoreboard for my game and therefor I need to sort it. My input is DATE;LEVEL;SCORE and I want to sort it by the highest score, if it's equal by the highest level and if it's equal by the date.

My ArrayList:

ArrayList<String> test = new ArrayList<>();
test.add("16.06.2018;1;10");
test.add("16.06.2018;1;2");
test.add("16.06.2018;1;5");
test.add("16.06.2018;1;1");
test.add("16.06.2018;1;3");
test.add("16.06.2018;2;3");
test.add("15.06.2018;1;3");
test.add("17.06.2018;1;3");

should be sorted

[16.06.2018;1;10, 16.06.2018;1;5, 16.06.2018;2;3, 15.06.2018;1;3, 16.06.2018;1;3, 17.06.2018;1;3, 16.06.2018;1;2, 16.06.2018;1;1]; 

but I'm getting

[16.06.2018;1;5, 16.06.2018;2;3, 15.06.2018;1;3, 16.06.2018;1;3, 17.06.2018;1;3, 16.06.2018;1;2, 16.06.2018;1;10, 16.06.2018;1;1]

My code:

Collections.sort(test, new Comparator<String>() {
    @Override
    public int compare(String A, String B) {
        String[] tmp = A.split(";");
        String[] tmp2 = B.split(";");
        if (tmp[2].equals(tmp2[2])) {
            if (tmp[1].equals(tmp2[1])) {
                return compareDate(tmp[0], tmp2[0]);
            } else {
                return tmp2[1].compareTo(tmp[1]);
            }
        } else {
            return tmp2[2].compareTo(tmp[2]);
        }
    }

    //compares 2 dates
    private int compareDate(String A, String B) {
        String[] tmp = A.split("\\.");
        String[] tmp2 = B.split("\\.");
        if (tmp[2].equals(tmp2[2])) {
            if (tmp[1].equals(tmp2[1])) {
                return tmp[0].compareTo(tmp2[0]);
            } else {
                return tmp[1].compareTo(tmp2[1]);
            }
        } else {
            return tmp[2].compareTo(tmp2[2]);
        }
    }
});

Upvotes: 1

Views: 144

Answers (3)

pamcevoy
pamcevoy

Reputation: 1246

I like the approach from @YCF_L and how @jspcal gets right to the point. I would usually break it up into reusable components like this.

public static void sort(List<String> data) {
    Collections.sort(data, new DataComparator());
}

private static class DataComparator implements Comparator<String>
{
    @Override
    public int compare(String str1, String str2) {
        DataObject data1 = DataObject.valueOf(str1);
        DataObject data2 = DataObject.valueOf(str2);
        return data1.compareTo(data2);
    }
}

private static class DataObject implements Comparable<DataObject>
{
    private static final Map<String,DataObject> valuesCache = new HashMap<String,DataObject>();

    private LocalDate date;
    private int value1;
    private int value2;

    /**
     * Parse the "date;value1;value2" String into an Object.
     * @param value the string
     * @throws ParseException if the date is invalid
     */
    public DataObject(String value) {
        String[] values = value.split(";");
        this.date = LocalDate.parse(values[0], DateTimeFormatter.ofPattern("dd.MM.yyyy"));
        this.value1 = Integer.valueOf(values[1]);
        this.value2 = Integer.valueOf(values[2]);
    }

    /**
     * Parse the String into an object.
     * @param str the string
     * @return the data object
     */
    public static DataObject valueOf(String str) {
        DataObject data = valuesCache.get(str);
        if (data == null) {
            data = new DataObject(str);
            valuesCache.put(str, data);
        }
        return data;
    }

    /**
     * Compare this DataObject to the other DataObject.
     */
    @Override
    public int compareTo(DataObject other) {
        int cmp = 0;
        if (this != other) {
            // first compare the value2 integers
            // sort descending (higher first) by multiplying by -1
            cmp = -1 * Integer.compare(this.value2, other.value2);

            // if those values matched, then compare value1 integers
            // also sort descending
            if (cmp == 0) {
                cmp = -1 * Integer.compare(this.value1, other.value1);
            }

            // if those values matched, then compare dates ascending
            if (cmp == 0) {
                cmp = this.date.compareTo(other.date);
            }
        }
        return cmp;
    }

    @Override
    public String toString() {
        return String.format("%s;%d;%d", date, value1, value2);
    }
}

Upvotes: 1

jspcal
jspcal

Reputation: 51894

You're using a string-based lexical comparison which treats "5" as being greater than "10" (because the character '5' comes after '1' in the Unicode table).

Instead you should use a numerical comparison. Convert the strings to integers and compare them with Integer.compare or similar:

Instead of this:

return tmp2[2].compareTo(tmp[2]);

You can do this:

return Integer.compare(
    Integer.parseInt(tmp2[2]),
    Integer.parseInt(tmp[2])
);

Upvotes: 4

Youcef LAIDANI
Youcef LAIDANI

Reputation: 59960

If you are using Java 8, I would like to create an Object from that String so you can compare it easily :

test.stream()
        .map(c -> {
            String[] tmp = c.split(";");
            MyObject obj = new MyObject(
                    LocalDate.parse(tmp[0], DateTimeFormatter.ofPattern("dd.MM.yyyy")),
                    Integer.valueOf(tmp[1]), Integer.valueOf(tmp[2])
            );
            return obj;

        }).sorted(
        Comparator.comparing(MyObject::getDate)
                .thenComparing(MyObject::getLevel)
                .thenComparing(MyObject::getScore));

With this Object :

class MyObject {
    private LocalDate date;
    private Integer level;
    private Integer score;

    public MyObject(LocalDate date, Integer level1, Integer score) {
        this.date = date;
        this.level = level;
        this.score= score;
    }

    public MyObject() {
    }
    //getter setter
}

Or without an Object :

test.stream().map(c -> c.split(";")).sorted(
        Comparator.comparing(a -> LocalDate.parse(((String[]) a)[0], DateTimeFormatter.ofPattern("dd.MM.yyyy")))
                .thenComparing(a -> Integer.valueOf(((String[]) a)[1]))
                .thenComparing(a -> Integer.valueOf(((String[]) a)[2])));

Note : You can put them in the order you want so you will get the expected result

Upvotes: 4

Related Questions