【问题标题】:Java custom Sort by 2 parts of same stringJava自定义按相同字符串的2部分排序
【发布时间】:2015-04-24 20:25:18
【问题描述】:

我看到过类似的其他问题,但无法将任何信息调整到我的代码中。要么是因为它不是特定于我的问题,要么是我无法理解答案。所以,我希望用我的具体代码问“如何”。如果需要更多,请告诉我。

我有各种名称格式为“20140214-ddEventBlahBlah02.jpg”和“20150302-ddPsBlagBlag2”的文件(全部为 jpg)。

我有一个自定义比较器正在使用,它以 Windows 操作系统的方式对事物进行排序......即 02、2、003、4、4b、4c、10 等。而不是计算机的排序方式,这被搞砸了。一切都很好,除了我现在想使用字符串中的 2 个标准对这些字符串进行排序。

1) 日期(在开头)。即20150302 2) "-" 之后的文件名的其余部分,即 ddPsBlagBlag2

我目前正在将比较器用于以相反顺序显示这些文件的项目。它们根据最近添加的内容进行显示。即 20150302 在 20140214 之前显示。这很好。但我希望这些文件在按日期倒序排序后,以正常的 Windows 操作系统升序(而不是倒序)按名称显示。

代码:

Collections.sort(file, new Comparator<File>() 
                    {
                    private final Comparator<String> NATURAL_SORT = new WindowsExplorerComparator();

                    @Override
                    public int compare(File o1, File o2) 
                    {
                        return NATURAL_SORT.compare(o1.getName(), o2.getName());
                    }
                });
                Collections.reverse(file);

上面的代码获取文件名的 ArayList 并将其发送到自定义 WindowsExplorerComparator 类。排序后,在 ArrayList 上调用 Collections.reverse()。

代码:

    class WindowsExplorerComparator implements Comparator<String> 
    {
    private static final Pattern splitPattern = Pattern.compile("\\d\\.|\\s");

@Override
public int compare(String str1, String str2) {
    Iterator<String> i1 = splitStringPreserveDelimiter(str1).iterator();
    Iterator<String> i2 = splitStringPreserveDelimiter(str2).iterator();
    while (true) 
    {
        //Til here all is equal.
        if (!i1.hasNext() && !i2.hasNext()) 
        {
            return 0;
        }
        //first has no more parts -> comes first
        if (!i1.hasNext() && i2.hasNext()) 
        {
            return -1;
        }
        //first has more parts than i2 -> comes after
        if (i1.hasNext() && !i2.hasNext()) 
        {
            return 1;
        }

        String data1 = i1.next();
        String data2 = i2.next();
        int result;
        try 
        {
            //If both datas are numbers, then compare numbers
            result = Long.compare(Long.valueOf(data1), Long.valueOf(data2));
            //If numbers are equal than longer comes first
            if (result == 0) 
            {
                result = -Integer.compare(data1.length(), data2.length());
            }
        } 
        catch (NumberFormatException ex) 
        {
            //compare text case insensitive
            result = data1.compareToIgnoreCase(data2);
        }

        if (result != 0) {
            return result;
        }
    }
}

private List<String> splitStringPreserveDelimiter(String str) {
    Matcher matcher = splitPattern.matcher(str);
    List<String> list = new ArrayList<String>();
    int pos = 0;
    while (matcher.find()) {
        list.add(str.substring(pos, matcher.start()));
        list.add(matcher.group());
        pos = matcher.end();
    }
    list.add(str.substring(pos));
    return list;
}

}

上面的代码是用于对 ArrayList 进行排序的自定义 WindowsExplorerComperator 类。

所以,我希望 ArrayList 在排序后(并且日期排序反转)后的样子是:

20150424-ssEventBlagV002.jpg
20150323-ssEventBlagV2.jpg
20150323-ssEventBlagV3.jpg
20150323-ssEventBlagV10.jpg
20141201-ssEventZoolander.jpg
20141102-ssEventApple1.jpg

如您所见,首先按日期排序(并反转),然后按字符串名称的其余部分升序排序。

这可能吗?请告诉我这是一个简单的解决方法。

【问题讨论】:

  • 感谢大家。最后,我使用了 pathfinderelite 的代码,并进行了一些更改。我所有的更改都在 pathfinderelites 答案下的 cmets 中。再次感谢。

标签: java sorting arraylist


【解决方案1】:

你的关闭,每当处理一些不工作的东西时,调试你的程序并确保方法返回你所期望的。当我运行你的程序时,我注意到的第一件事是每次尝试将字符串转换为Long 的比较迭代都会抛出NumberFormatException。这是一个很大的危险信号,所以我输入了一些 println 来检查 data1data2 的值。

这是我的输出:

Compare: 20150323-ssEventBlagV 20150424-ssEventBlagV00
Compare: 20150323-ssEventBlagV 20150323-ssEventBlagV
Compare: 3. 2.
Compare: 20150323-ssEventBlagV 20150424-ssEventBlagV00
Compare: 20150323-ssEventBlagV 20150323-ssEventBlagV
Compare: 3. 2.
Compare: 20150323-ssEventBlagV1 20150323-ssEventBlagV
Compare: 20150323-ssEventBlagV1 20150424-ssEventBlagV00
Compare: 20141201-ssEventZoolander.jpg 20150323-ssEventBlagV1
Compare: 20141201-ssEventZoolander.jpg 20150323-ssEventBlagV
Compare: 20141201-ssEventZoolander.jpg 20150323-ssEventBlagV

这里要注意的一件大事是它试图将 3.2. 转换为 long 值,这当然不会起作用。


使用您的代码最简单的解决方案是简单地更改您的正则表达式。尽管将来您可能会选择更简单的字符串迭代路线而不是正则表达式,但我觉得正则表达式使这个问题变得复杂而不是帮助。

新的正则表达式:\\d+(?=\\.)|\\s

变化:

  • \\d -> \\d+ - 捕获句点之前的所有数字,而不仅仅是第一个数字
  • \\. -> (?=\\.) - 将句点放在非捕获组中,这样您的方法就不会将其附加到我们的数字上

新的调试输出:

Compare: 20150323-ssEventBlagV 20150424-ssEventBlagV
Compare: 20150323-ssEventBlagV 20150323-ssEventBlagV
Compare: 3 2
Compare: 20150323-ssEventBlagV 20150323-ssEventBlagV
Compare: 10 3
Compare: 20141201-ssEventZoolander.jpg 20150323-ssEventBlagV

如您所见,最后的数字实际上得到了正确解析。


还有一件小事:

你的数字比较结果是倒数

result = Long.compare(Long.valueOf(data1), Long.valueOf(data2));

应该是:

result = -Long.compare(Long.valueOf(data1), Long.valueOf(data2));

result = Long.compare(Long.valueOf(data2), Long.valueOf(data1));

因为它会将它们向后排序。

【讨论】:

    【解决方案2】:

    你应该做几件事:

    首先,您需要修复 @ug_ 所述的拆分表达式。但是,我认为按数字划分更合适。

    private static final Pattern splitPattern = Pattern.compile("\\d+");
    

    对于20150323-ssEventBlagV2.jpg 将导致

    [, 20150323, -ssEventBlagV, 2, .jpg]
    

    其次,执行与 Long 比较分开的日期比较。使用 SimpleDateFormat 将确保您只比较格式为日期的数字。

    try {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        result = sdf.parse(data2).compareTo(sdf.parse(data1));
        if (result != 0) {
            return result;
        }
    } catch (final ParseException e) {
        /* continue */
    }
    

    最后,交换长比较的顺序

    Long.compare(Long.valueOf(data2), Long.valueOf(data1));
    

    你应该很高兴。完整代码如下。

    private static final Pattern splitPattern = Pattern.compile("\\d+");
    
        @Override
        public int compare(String str1, String str2) {
            Iterator<String> i1 = splitStringPreserveDelimiter(str1).iterator();
            Iterator<String> i2 = splitStringPreserveDelimiter(str2).iterator();
            while (true) {
                // Til here all is equal.
                if (!i1.hasNext() && !i2.hasNext()) {
                    return 0;
                }
                // first has no more parts -> comes first
                if (!i1.hasNext() && i2.hasNext()) {
                    return -1;
                }
                // first has more parts than i2 -> comes after
                if (i1.hasNext() && !i2.hasNext()) {
                    return 1;
                }
    
                String data1 = i1.next();
                String data2 = i2.next();
                int result;
    
                try {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
                    result = sdf.parse(data1).compareTo(sdf.parse(data2));
                    if (result != 0) {
                        return result;
                    }
                } catch (final ParseException e) {
                    /* continue */
                }
    
                try {
                    // If both datas are numbers, then compare numbers
                    result = Long.compare(Long.valueOf(data2),
                            Long.valueOf(data1));
                    // If numbers are equal than longer comes first
                    if (result == 0) {
                        result = -Integer.compare(data1.length(),
                                data2.length());
                    }
                } catch (NumberFormatException ex) {
                    // compare text case insensitive
                    result = data1.compareToIgnoreCase(data2);
                }
    
                if (result != 0) {
                    return result;
                }
    
            }
        }
    

    【讨论】:

    • 这看起来不错。非常感谢你和 ug。当前代码存在的唯一问题是字符串名称末尾的排序。具体来说,数字后面的字母和数字前面的 0。即 20140526-blEventBlah12 在 20140526-blEventBlah012b 之前出现,它在 20140526-blEventBlah012 之前出现...... Windows 排序方式,应该有它...... 012 -> 012b -> 12
    • 名称也是倒序的。只希望日期以相反的顺序排列。目前按此顺序显示 20140404-agEventBecca -> 20140404-agEventAgent。字符串名称的升序顺序应该是其他方式。
    • 另外.. 澄清一下.. EventBecca.jpg 应该在 EventBecca0.jpg 之前,EventBecca0.jpg 应该在 EventBecca01.jpg 之前,而 EventBecca01.jpg 应该在 EventBecca10.jpg 之前
    • if (result == 0) { result = -Integer.compare(data2.length(), data1.length());我在这里切换了 data1 和 data2 并设法按顺序获得了数字。 Blah -> Blah012 -> Blah12 ... 仍在试图弄清楚如何获得 Blah12 -> Blah12b -> Blah12c .. 它目前正朝着相反的方向发展。并且仍然试图让名称部分不反向......即 Blah.jpg -> Aggro.jpg
    • 我几乎明白了。我在这段代码中切换了 data1 和 data2 'catch (NumberFormatException ex) { // 比较文本不区分大小写 result = data2.compareToIgnoreCase(data1); }' 现在一切正常......几乎......唯一不工作的是它最后没有数字。即它的'Blag -> Blah00b -> Blah00c -> Blah01 -> Blah' 没有数字的 Blah 应该是第一个,而不是最后一个......
    【解决方案3】:

    您将需要编辑您的 WindowsExporerComparator 类,以便它执行此排序。给定两个文件名作为字符串,您需要使用以下高级算法确定它们的顺序。

    1. 它们是一样的吗?如果是,则返回 0
    2. 将文件名分成两个字符串,日期部分和名称部分。
    3. 使用日期部分将字符串转换为使用 Java DateTime 的日期,然后比较日期。
    4. 如果日期相同,则使用当前的比较代码比较两个名称部分,然后返回结果。

    这有点复杂,有点令人困惑,但你必须在一个比较器中完成它并放入所有自定义逻辑

    【讨论】:

      猜你喜欢
      • 2015-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-19
      • 1970-01-01
      • 2012-09-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多