如何使用比较器对 ArrayList<String> 进行排序?

How to sort an ArrayList<String> using Comparator?

大家好,我正在尝试为我的游戏制作记分牌,因此我需要对其进行排序。我的输入是 DATE;LEVEL;SCORE,我想按最高分对它进行排序,如果它与最高级别相等并且与日期相等。

我的数组列表:

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

应该排序

[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]; 

但我得到

[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]

我的代码:

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

您正在使用基于字符串的词法比较,它将 "5" 视为大于 "10"(因为字符 '5' 在 Unicode 中位于 '1' 之后table).

相反,您应该使用数值比较。将字符串转换为整数并将它们与 Integer.compare 或类似的进行比较:

而不是这个:

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

你可以这样做:

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

如果您使用的是 Java 8,我想从该字符串创建一个对象,以便您可以轻松地进行比较:

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

有了这个对象:

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
}

或没有对象:

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

注意:您可以按您想要的顺序排列它们,这样您会得到预期的结果

我喜欢@YCF_L 的方法以及@jspcal 如何切中要点。我通常会把它分解成这样的可重用组件。

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