20

I have a set of files in a folder, and all of them starting with a similar name, except one. Here is an example:

Coordinate.txt
Spectrum_1.txt
Spectrum_2.txt
Spectrum_3.txt
.
.
.
Spectrum_11235

I am able to list all the files from the specified folder, but the list is not in an ascending order of the spectrum number. Example: I get the following result when the program is executed:

Spectrum_999.txt
Spectrum_9990.txt
Spectrum_9991.txt
Spectrum_9992.txt
Spectrum_9993.txt
Spectrum_9994.txt
Spectrum_9995.txt
Spectrum_9996.txt
Spectrum_9997.txt
Spectrum_9998.txt
Spectrum_9999.txt

But this order is not correct. There should be Spectrum_1000.txt file after Spectrum_999.txt. Can anyone help? Here is the code:

import java.io.*;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

    public class FileInput {

        public void userInput()
        {
            Scanner scanner = new Scanner( System.in );
            System.out.println("Enter the file path: ");
            String dirPath = scanner.nextLine(); // Takes the directory path as the user input

            File folder = new File(dirPath);
            if(folder.isDirectory())
            {
                File[] fileList = folder.listFiles();

                Arrays.sort(fileList);

                System.out.println("\nTotal number of items present in the directory: " + fileList.length );


                // Lists only files since we have applied file filter
                for(File file:fileList)
                {
                    System.out.println(file.getName());
                }

                // Creating a filter to return only files.
                FileFilter fileFilter = new FileFilter()
                {
                    @Override
                    public boolean accept(File file) {
                        return !file.isDirectory();
                    }
                };

                fileList = folder.listFiles(fileFilter);

                // Sort files by name
                Arrays.sort(fileList, new Comparator()
                {
                    @Override
                    public int compare(Object f1, Object f2) {
                        return ((File) f1).getName().compareTo(((File) f2).getName());
                    }
                });

                //Prints the files in file name ascending order
                for(File file:fileList)
                {
                    System.out.println(file.getName());
                }

            }   
        }
    }
Tuna
  • 2,937
  • 4
  • 37
  • 61
novicegeek
  • 773
  • 2
  • 9
  • 29

11 Answers11

34

What you are asking for is numerical sort. You need implement a Comparator and pass it to the Arrays#sort method. In the compare method you need to extract the number from each filename an then compare the numbers.

The reason why you get the output you are getting now is that sorting happens alphanumerically

Here a is a very basic way of doing it. This code uses simple String-operation to extract the numbers. This works if you know the format of the filename, in your case Spectrum_<number>.txt. A better way of doing the extraction is to use regular expression.

public class FileNameNumericSort {

    private final static File[] files = {
        new File("Spectrum_1.txt"),
        new File("Spectrum_14.txt"),
        new File("Spectrum_2.txt"),
        new File("Spectrum_7.txt"),     
        new File("Spectrum_1000.txt"), 
        new File("Spectrum_999.txt"), 
        new File("Spectrum_9990.txt"), 
        new File("Spectrum_9991.txt"), 
    };

    @Test
    public void sortByNumber() {
        Arrays.sort(files, new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                int n1 = extractNumber(o1.getName());
                int n2 = extractNumber(o2.getName());
                return n1 - n2;
            }

            private int extractNumber(String name) {
                int i = 0;
                try {
                    int s = name.indexOf('_')+1;
                    int e = name.lastIndexOf('.');
                    String number = name.substring(s, e);
                    i = Integer.parseInt(number);
                } catch(Exception e) {
                    i = 0; // if filename does not match the format
                           // then default to 0
                }
                return i;
            }
        });

        for(File f : files) {
            System.out.println(f.getName());
        }
    }
}

Output

Spectrum_1.txt
Spectrum_2.txt
Spectrum_7.txt
Spectrum_14.txt
Spectrum_999.txt
Spectrum_1000.txt
Spectrum_9990.txt
Spectrum_9991.txt
Community
  • 1
  • 1
A4L
  • 17,353
  • 6
  • 49
  • 70
  • 1
    @LukasEder `Here a is a very basic way of doing it. This code uses simple String-operation to extract the numbers. This works if you know the format of the filename, in your case Spectrum_.txt. A better way of doing the extraction is to use regular expression.` Answers here are not meant to provide a fully working production quality code, rather than that provide hints a tips on how to solve the problem and eventually a very basic code sample. I hope I could answer your question. – A4L Feb 23 '18 at 11:35
8

The currently accepted answer does this only for numeric suffixes of files that are always called the same name (i.e. ignoring the prefix).

A much more generic solution, which I blogged about here, works with any file name, splitting names in segments and ordering the segments numerically (if both segments are numbers) or lexicographically, otherwise. Idea inspired from this answer:

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

This can also handle version with subversions, e.g. things like version-1.2.3.txt

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
7

The NameFileComparator class available in Commons IO library that have feature for sorting file array by name,last modified date, size and many more.Files can be sorted in ascending and descending order, with case sensitivity or case insensitivity.

Import :

org.apache.commons.io.comparator.NameFileComparator

Code :

File directory = new File(".");
File[] files = directory.listFiles();
Arrays.sort(files, NameFileComparator.NAME_COMPARATOR)
Divyesh Kanzariya
  • 3,629
  • 3
  • 43
  • 44
3

You can find solution to your problem in comment above, but taking into consideration that only link had been published I'm giving code from that site. Worked great.

  1. You need to create your own AlphanumericalComparator.

     import java.io.File;
     import java.util.Comparator;
    
    public class AlphanumFileComparator implements Comparator
    {
    
       private final boolean isDigit(char ch)
       {
        return ch >= 48 && ch <= 57;
       }
    
    
    private final String getChunk(String s, int slength, int marker)
    {
        StringBuilder chunk = new StringBuilder();
        char c = s.charAt(marker);
        chunk.append(c);
        marker++;
        if (isDigit(c))
        {
            while (marker < slength)
            {
                c = s.charAt(marker);
                if (!isDigit(c))
                    break;
                chunk.append(c);
                marker++;
            }
        } else
        {
            while (marker < slength)
            {
                c = s.charAt(marker);
                if (isDigit(c))
                    break;
                chunk.append(c);
                marker++;
            }
        }
        return chunk.toString();
    }
    
    public int compare(Object o1, Object o2)
    {
        if (!(o1 instanceof File) || !(o2 instanceof File))
        {
            return 0;
        }
        File f1 = (File)o1;
        File f2 = (File)o2;
        String s1 = f1.getName();
        String s2 = f2.getName();
    
        int thisMarker = 0;
        int thatMarker = 0;
        int s1Length = s1.length();
        int s2Length = s2.length();
    
        while (thisMarker < s1Length && thatMarker < s2Length)
        {
            String thisChunk = getChunk(s1, s1Length, thisMarker);
            thisMarker += thisChunk.length();
    
            String thatChunk = getChunk(s2, s2Length, thatMarker);
            thatMarker += thatChunk.length();
    
            /** If both chunks contain numeric characters, sort them numerically **/
    
            int result = 0;
            if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0)))
            {
                // Simple chunk comparison by length.
                int thisChunkLength = thisChunk.length();
                result = thisChunkLength - thatChunk.length();
                // If equal, the first different number counts
                if (result == 0)
                {
                    for (int i = 0; i < thisChunkLength; i++)
                    {
                        result = thisChunk.charAt(i) - thatChunk.charAt(i);
                        if (result != 0)
                        {
                            return result;
                        }
                    }
                }
            } else
            {
                result = thisChunk.compareTo(thatChunk);
            }
    
            if (result != 0)
                return result;
        }
    
        return s1Length - s2Length;
    }
    }
    

2. Sort your files, depending on this class.

     File[] listOfFiles = rootFolder.listFiles();
     Arrays.sort(listOfFiles, new AlphanumFileComparator() );
     ...to sth with your files.

Hope it helps. It worked for me, like a charm.

Solution from: http://www.davekoelle.com/files/AlphanumComparator.java here

deyvw
  • 820
  • 7
  • 9
2
Arrays.sort(fileList, new Comparator()
{
    @Override
    public int compare(Object f1, Object f2) {
        String fileName1 = ((File) f1).getName();
        String fileName2 = ((File) f1).getName();

        int fileId1 = Integer.parseInt(fileName1.split("_")[1]);
        int fileId2 = Integer.parseInt(fileName2.split("_")[1]);

        return fileId1 - fileId2;
    }
});

make sure to handle files that does not has _ in the name

kdureidy
  • 960
  • 9
  • 26
2

Just use:

  1. For Ascending: Collections.sort(List)

  2. For Descending : Collections.sort(List,Collections.reverseOrder())

Somil Aseeja
  • 150
  • 8
1

Best implementation of a smart AlphaDecimal comparator by Ivan Gerasimiv was found here. It was proposed as an extension of a standard JDK Comparators here and discussed in a thread here.
Unfortunately this change still didn't make its way into JDK (as far as I know). But you can use the code from the first link as a solution. For me it worked like a charm.

stormdb
  • 371
  • 1
  • 4
  • 13
0

My algoritm of kotlin version. This algorithm is used to sort files and directories by their name.

import kotlin.Comparator

const val DIFF_OF_CASES = 'a' - 'A'

/** File name comparator. */
class FileNameComparator(private val caseSensitive: Boolean = false) : Comparator<String> {

    override fun compare(left: String, right: String): Int {
        val csLeft = left.toCharArray()
        val csRight = right.toCharArray()
        var isNumberRegion = false
        var diff = 0; var i = 0; var j = 0
        val lenLeft = csLeft.size; val lenRight = csRight.size
        while (i < lenLeft && j < lenRight) {
            val cLeft = getCharByCaseSensitive(csLeft[i])
            val cRight = getCharByCaseSensitive(csRight[j])
            val isNumericLeft = cLeft in '0'..'9'
            val isNumericRight = cRight in '0'..'9'
            if (isNumericLeft && isNumericRight) {
                // Number start!
                if (!isNumberRegion) {
                    isNumberRegion = true
                    // Remove prefix '0'
                    while (i < lenLeft && cLeft == '0') i++
                    while (j < lenRight && cRight == '0') j++
                    if (i == lenLeft || j == lenRight) break
                }
                // Diff start: calculate the diff value.
                if (cLeft != cRight && diff == 0) diff = cLeft - cRight
            } else {
                if (isNumericLeft != isNumericRight) {
                    // One numeric and one char: the longer one is backwards.
                    return if (isNumberRegion) { if (isNumericLeft) 1 else -1 } else cLeft - cRight
                } else {
                    // Two chars: if (number) diff don't equal 0, return it (two number have same length).
                    if (diff != 0) return diff
                    // Calculate chars diff.
                    diff = cLeft - cRight
                    if (diff != 0) return diff
                    // Reset!
                    isNumberRegion = false
                    diff = 0
                }
            }
            i++; j++
        }
        if (i == lenLeft) i--
        if (j == lenRight) j--
        return csLeft[i] - csRight[j]
    }

    private fun getCharByCaseSensitive(c: Char): Char
            = if (caseSensitive) c else if (c in 'A'..'Z') (c + DIFF_OF_CASES) else c
}

For example, for input,

          "A__01__02",
            "A__2__02",
            "A__1__23",
            "A__11__23",
            "A__3++++",
            "B__1__02",
            "B__22_13",
            "1_22_2222",
            "12_222_222",
            "2222222222",
            "1.sadasdsadsa",
            "11.asdasdasdasdasd",
            "2.sadsadasdsad",
            "22.sadasdasdsadsa",
            "3.asdasdsadsadsa",
            "adsadsadsasd1",
            "adsadsadsasd10",
            "adsadsadsasd3",
            "adsadsadsasd02"

It will sort them as,

1.sadasdsadsa 1_22_2222 2.sadsadasdsad 3.asdasdsadsadsa 11.asdasdasdasdasd 12_222_222 22.sadasdasdsadsa 2222222222 A__01__02 A__1__23 A__2__02 A__3++++ A__11__23 adsadsadsasd02 adsadsadsasd1 adsadsadsasd3 adsadsadsasd10 B__1__02 B__22_13 
Shawn Wong
  • 554
  • 6
  • 15
0

Simplest way to achieve this is to use NameFileComparator.NAME_COMPARATOR. Following is the code format :

File[] fileNamesArr = filePath.listFiles();
if(fileNamesArr != null && fileNamesArr.length > 0) {
    Arrays.sort(fileNamesArr, NameFileComparator.NAME_COMPARATOR);
}

In order to implement NameFileComparator you need to add following maven library:

implementation 'commons-io:commons-io:2.6'

Also import :

import org.apache.commons.io.comparator.NameFileComparator;

That's all...

Mudit Goel
  • 196
  • 1
  • 5
-1

Just another way to do it, but using the power of java8

List<Path> x = Files.list(Paths.get("C:\\myPath\\Tools"))
            .filter(p -> Files.exists(p))
            .map(s -> s.getFileName())
            .sorted()
            .collect(Collectors.toList());

x.forEach(System.out::println);
ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97
  • if you use a mapping, sorted and everything after will be fed with the filename (= String object) rather than the actual path or file object. On collecting the result, `toList` will actually generate a `List` rather than a `List` object. I've adapted your code though to match my needs: `File[] sortedFiles = Arrays.stream(files).filter(f -> Files.exists(f.toPath())).sorted(Comparator.comparing(File::getName)).toArray(File[]::new);` – Roman Vottner Jan 08 '18 at 14:20
  • sorted() uses natural ordering, which will put "a_10" before "a_9". This is not what is required in the question. – stormdb Nov 24 '20 at 18:52
-2

you can use Collections.sort(fileList); to sort arraylist.

Then use

 for(File file:fileList)                
         System.out.println(file.getName());

Collections.sort()

PSR
  • 39,804
  • 41
  • 111
  • 151
  • I tried this, but for `Collections.sort(fileList);` the following error is shown: `The method sort(List) in the type Collections is not applicable for the arguments (File[])` – novicegeek Jun 03 '13 at 13:45
  • what is the problem with this – PSR Jun 03 '13 at 13:46
  • I removed the code `Arrays.sort(fileList, new Comparator() { @Override public int compare(Object f1, Object f2) { return ((File) f1).getName().compareTo(((File) f2).getName()); } });` and tried `Collections.sort(fileList);` but the following error is shown: `The method sort(List) in the type Collections is not applicable for the arguments (File[])` How to proceed? – novicegeek Jun 03 '13 at 13:53
  • Yes. But the problem is still not solved. I want that the numbers in the file names should be arranged in an ascending order,rather than the listing displayed above. – novicegeek Jun 03 '13 at 13:59
  • then use substring and put them into a list – PSR Jun 03 '13 at 14:01
  • In any case, Collections.sort uses natural ordering, which puts "a_10" before "a_9". This is not what required in the question – stormdb Nov 24 '20 at 18:53