1

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]);
        }
    }
});

3 Answers 3

4

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])
);
Sign up to request clarification or add additional context in comments.

Comments

4

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

Comments

1

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);
    }
}

1 Comment

Updated to use LocalDate which is cleaner that SimpleDateFormat. Thanks @YCF_L.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.