6

I have a folder with files which are named after timestamps.

When I try to go through each file it sorts them alphabetically and gives me this order:

/home/user/buffereddata/1
/home/user/buffereddata/100
/home/user/buffereddata/1000
/home/user/buffereddata/200
/home/user/buffereddata/2000
/home/user/buffereddata/300

But I want them sorted like this:

/home/user/buffereddata/1
/home/user/buffereddata/100
/home/user/buffereddata/200
/home/user/buffereddata/300
/home/user/buffereddata/1000
/home/user/buffereddata/2000

This is my code:

File file = new File(System.getProperty("user.home") + "/buffereddata");

if(file.exists()) {
  File[] fileArray = file.listFiles();
  Arrays.sort(fileArray);
  for(File f : fileArray) {
    System.out.println(f);
  }
}

Is there some (preferably simple) way to loop through the files in the way that I want to loop through them?

JREN
  • 3,572
  • 3
  • 27
  • 45
  • You can parse the string for integer, then put both strings and parsed ints in two equally indiced arrays then sort the int array as doing same swaps for the string array. Maybe you need to write sorting method on your own. You can make a hash table too, an int for a key and a string for it. – huseyin tugrul buyukisik Jun 27 '13 at 09:48
  • 2
    You should write a `Comparator` which is based on the filename and sorts them numerically. – Dominik Sandjaja Jun 27 '13 at 09:49
  • you can create a comparator see (http://www.mkyong.com/java/java-object-sorting-example-comparable-and-comparator/) and do either a numeric compare (or pad the names with zero's to ensure they are of the same length and do a string compare) – Bruce Martin Jun 27 '13 at 09:53
  • possible duplicate of [Sort on a string that may contain a number](http://stackoverflow.com/questions/104599/sort-on-a-string-that-may-contain-a-number) (Not exactly, but close enough that the solutions are analogous.) – Stephen C Jun 27 '13 at 10:01

4 Answers4

14
Arrays.sort(fileArray, new Comparator<File>() {
    public int compare(File f1, File f2) {
        try {
            int i1 = Integer.parseInt(f1.getName());
            int i2 = Integer.parseInt(f2.getName());
            return i1 - i2;
        } catch(NumberFormatException e) {
            throw new AssertionError(e);
        }
    }
});
rlegendi
  • 10,466
  • 3
  • 38
  • 50
  • I'll try it out. Should I catch my NumberFormatExceptions in the comparator or outside? – JREN Jun 27 '13 at 09:52
  • What about NumberFormatExceptions? – Ingo Jun 27 '13 at 09:52
  • You should check NFE if you *expect* an NFE. If it should not happen, you would just throw an `AssertionError` either way (and embedding NFE). That's why NFE is a `RuntimeExcpetion` btw. – rlegendi Jun 27 '13 at 09:55
  • `try { return ...; } catch (NFE e) { return f1.getName().compareTo(...); }` (Fallback on string compare.) – Joop Eggen Jun 27 '13 at 09:56
  • What if filename has a separate two integers maybe for a folder name? – huseyin tugrul buyukisik Jun 27 '13 at 09:56
  • I editted your answer to something more complete because your initial code worked after I fixed the syntax and added the exception handling. Now I can select this as accepted answer. Thanks – JREN Jun 27 '13 at 09:58
  • Actually, the NFE **will not** be raised for `Arrays.sort()`. You can only handle it within compare. – rlegendi Jun 27 '13 at 10:01
  • I'd recommend NOT to throw any Error, as it usually will bypass your Exception handling. Consider throwing some RuntimeException! – LastFreeNickname Jun 27 '13 at 10:18
  • @LastFreeNickname Yeah, it's rather religious question :-) In this particular example it is just a **demo** how may one handle NFE (makes more sense than rethrowing e). – rlegendi Jun 27 '13 at 10:22
  • @rlegendi: Don't want to start a discussion, but Errors are commonly reserved to be used by the JVM and should not be thrown by programmers. See e.g. [here](http://stackoverflow.com/questions/912334/differences-betweeen-exception-and-error) and [here](http://stackoverflow.com/questions/5813614/what-is-difference-between-errors-and-exceptions). – LastFreeNickname Jun 27 '13 at 14:20
  • @LastFreeNickname In principle that's true. However, the `assert()` statement itself is throwing an error (specifically: an `AssertionError`). So basically if you know that control flow should not reach a certain point (i.e., place a control flow invariant), you cannot do anything else than throwing an `AE` explicitly. The reason is that you *must* return a value from the code (e.g., the `compare()` method here -- `assert()` statements are usually disabled). See [here](http://docs.oracle.com/javase/1.4.2/docs/guide/lang/assert.html#usage-invariants). And as I stated, this is only a snippet. – rlegendi Jun 28 '13 at 07:56
  • So in a few cases it is reasonable to throw errors explicitly. – rlegendi Jun 28 '13 at 07:57
  • It is a valuable answer for me – Arsal Imam Oct 13 '18 at 06:43
5

While the other answers are correct in your specific case (where all file names in a given directory are numeric only), here's a solution that can compare mixed numeric / non-numeric file names, e.g. version-1.10.3.txt in an intuitive way, similar to the way Windows Explorer does it:

enter image description here

The idea (which I have blogged about here. The idea was inspired by this answer here.) is to split a file name into numeric / non-numeric segments and then compare each individual segment from the two file names either numerically (if both are numeric), or alpha-numerically otherwise:

public final class FilenameComparator implements Comparator<String> {
    private static final Pattern NUMBERS = 
        Pattern.compile("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
    @Override public final int compare(String o1, String o2) {
        // Optional "NULLS LAST" semantics:
        if (o1 == null || o2 == null)
            return o1 == null ? o2 == null ? 0 : -1 : 1;

        // Splitting both input strings by the above patterns
        String[] split1 = NUMBERS.split(o1);
        String[] split2 = NUMBERS.split(o2);
        for (int i = 0; i < Math.min(split1.length, split2.length); i++) {
            char c1 = split1[i].charAt(0);
            char c2 = split2[i].charAt(0);
            int cmp = 0;

            // If both segments start with a digit, sort them numerically using 
            // BigInteger to stay safe
            if (c1 >= '0' && c1 <= '9' && c2 >= 0 && c2 <= '9')
                cmp = new BigInteger(split1[i]).compareTo(new BigInteger(split2[i]));

            // If we haven't sorted numerically before, or if numeric sorting yielded 
            // equality (e.g 007 and 7) then sort lexicographically
            if (cmp == 0)
                cmp = split1[i].compareTo(split2[i]);

            // Abort once some prefix has unequal ordering
            if (cmp != 0)
                return cmp;
        }

        // If we reach this, then both strings have equally ordered prefixes, but 
        // maybe one string is longer than the other (i.e. has more segments)
        return split1.length - split2.length;
    }
}

You can then use the comparator as such:

Arrays.sort(fileArray, Comparators.comparing(File::getName, new FilenameComparator()));
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
3

you need a custom comparator

    Arrays.sort(fileArray, new Comparator<File>() {
        public int compare(File f1, File f2) {
            int n1 = Integer.parseInt(f1.getName());
            int n2 = Integer.parseInt(f1.getName());
            return Integer.compare(n1, n2);
        }});
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
-1

Basing on this answer:

You can define your own comparator with compare function as:

public class FileSizeComparator implements Comparator<File> {
    public int compare( File a, File b ) {
        String aName = a.getName();
        String bName = b.getName();
        // make both strings equal size by padding 0s to the smaller one
        // then compare the strings
        return aName.compareTo(bName); // dictionary order!
    }
}

compareTo is a String class method which works to your benefit here.

"0100".compareTo("1000"); // < 0
"0100".compareTo("0200"); // < 0
"0200".compareTo("1000"); // < 0

So if you have 100, 200, 1000 you get 100, 200, 1000 and not 100, 1000, 200!

Should work, not tested!

Community
  • 1
  • 1
Prasanth
  • 5,230
  • 2
  • 29
  • 61
  • 2
    No, this is wrong. `"200".compareTo("1000")` returns 1, not -1 as you suggest. String.compareTo() does alphabetical, not numerical comparison. – Jonik Jun 27 '13 at 10:09
  • Also, FileSizeComparator is a misleading class name for your comparator. – Jonik Jun 27 '13 at 10:10
  • Oh man! you are right! I was wrong. I had completely different notion about the string compareTo function in mind :( it's been long since I used java. – Prasanth Jun 27 '13 at 10:15
  • Yes. You should edit your answer, or delete it (there are two correct answers already); otherwise I'll have to downvote. :) – Jonik Jun 27 '13 at 10:20