简体   繁体   中英

Proper way to sort a File array in ascending order by last number

I have various files in the internal memory, each one's finish his name with a number and his format, example:

/storage/emulated/0/packets
  • File 1.x
  • File 2.x
  • ...
  • File 11.x

I made a code to retrieve the files and sort in ascending order, but when the files are more than 10, the sort fail.

My code:

ArrayList<String> list;

public static boolean SetFileList(String directory){
    list = new ArrayList<>();

    if(DirectoryExist(directory)) { //DirectoryExist() verify if the parameter directory it's a true directory
        File[] files = new File(directory).listFiles();

        if (files == null) {
            return false;
        }

        else if (files.length == 0) {
            return false;
        }

        else {
            Arrays.sort(files); //<------this method is supposed to sort

            for (File file : files) {
                list.add(file.getName());
            }

            return true;
        }
    }

    else{
        return false;
    }
}

When I use the following code to show the files:

for(int i = 0; i < list.size(); i++){
    Log.i("File", list.get(i));
}

Throws something like:

File: File 1.x
File: File 10.x
File: File 11.x
File: File 12.x
File: File 2.x
File: File 3.x
File: ...

But I want to show:

File: File 1.x
File: File 2.x
File: File 3.x
File: ...
File: File 10.x
File: File 11.x
File: File 12.x

Why is this happening?

How about this way? Extract numbers from each filenames and compare them.

File[] files = new File(directory).listFiles();

if (files == null || files.length == 0) {
    return false;
}

List<File> fileList = Arrays.asList(files);

Collections.sort(fileList, (o1, o2) -> {
    // find the dots.
    int pointIndex1 = o1.getName().lastIndexOf(".");
    int pointIndex2 = o2.getName().lastIndexOf(".");

    // filename -> integer value.
    int val1 = Integer.valueOf(o1.getName().substring(0, pointIndex1));
    int val2 = Integer.valueOf(o2.getName().substring(0, pointIndex2));

    return Integer.compare(val1, val2);
});

for (File file : fileList) {
    list.add(file.getName());
}

return true;

-- EDIT

And you can do this with arrays!

File[] files = new File(directory).listFiles();

Arrays.sort(files, (o1, o2) -> {
    // find the dots.
    int pointIndex1 = o1.getName().lastIndexOf(".");
    int pointIndex2 = o2.getName().lastIndexOf(".");

    // filename -> integer value.
    int val1 = Integer.valueOf(o1.getName().substring(0, pointIndex1));
    int val2 = Integer.valueOf(o2.getName().substring(0, pointIndex2));

    return Integer.compare(val1, val2);
});

for (File file : fileList) {
    list.add(file.getName());
}

return true;

I have add one method and also used compare method into sorting of file name.

 public  boolean SetFileList(String directory){
    list = new ArrayList<>();

    if(DirectoryExist(directory)) { //DirectoryExist() verify if the parameter directory it's a true directory

        File[] files = new File(Environment.getExternalStorageDirectory() +"/"+directory).listFiles();

        if (files == null) {
            return false;
        }

        else if (files.length == 0) {
            return false;
        }

        else {
            Arrays.sort(files, new Comparator<File>() {
                @Override
                public int compare(File file, File t1) {
                    int n1 = extractNumber(file.getName());
                    int n2 = extractNumber(t1.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;
                }
            }); //<------this method is supposed to sort

            for (File file : files) {
                list.add(file.getName());
                System.out.println(file.getName());
            }

            return true;
        }
    }

    else{
        return false;
    }
}

The following method returns a list of file names sorted by their numbers.

It extracts one or more digits right before the extension and parses an integer value. Thus, it will work even if the file name contains digits in different places.

Files that don't have digits before their extensions are not compared and go to the end of the list.

The method returns an empty list if the file with the given name doesn't exist or it is not a directory.

public static List<String> sortFiles(String directory) {
    File dir = new File(directory);
    if (dir.exists() && dir.isDirectory()) {
        File[] files = dir.listFiles();
        if (files != null) {
            // this will match the number before the extension
            Pattern p = Pattern.compile("[\\d]+(?=\\.[a-zA-Z]+$)");
            return Arrays.stream(files)
                    .map(File::getName)
                    .sorted(Comparator.comparing(n -> {
                        Matcher m = p.matcher(n);
                        if (m.find()) return Integer.parseInt(m.group());
                        else return Integer.MAX_VALUE;
                    }))
                    .collect(Collectors.toList());
        }
    }
    return Collections.emptyList();
}

Using Arrays.sort you are sorting in natural order (alphanumeric), therefore 10 is before 3 because 1 is before 3.

That's why a new comparator is needed. Maybe what you want is some kind of Windows Explorer Sorting, where number - parts are treated as numbers.

Here is an implementation for this: Java - Sort Strings like Windows Explorer . It is more complex than the other answers, because its doing more than sorting only the last number before the extension. Look at the example there. It compares corresponding letter and number parts of two strings and is able to deal with leading zeros.

it outputs the following

file1.txt
file3.txt
file05.txt
file7.txt
file10.txt

and here is the implementation itself:

public class WindowsSortOrderTest {
    public static void main(String args[]) throws UnsupportedEncodingException {
        List<String> files = Arrays.asList("file1.txt", "file10.txt", "file3.txt", "file7.txt");

        files.sort(new WindowsExplorerComparator());

        for (String file : files) {
            System.out.println(file);
        }
    }


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

How about this way?

--For sorting an array of string below is the solution work for me:

val otherStrings = arrayOf("LM_1", "LM_16", "LM_10", "LM_2", "LM_3","LM_4", "LM_5", "LM_6")
        Arrays.sort(otherStrings, Comparator<String?> { o1, o2 ->
            if (o1 == null) return@Comparator -1 else if (o2 == null) return@Comparator 1
            val cmp = o1.length.compareTo(o2.length)
            if (cmp != 0) cmp else o1.compareTo(o2)
        })
        for (i in otherStrings.indices)
            Log.e("otherStrings", otherStrings[i])

Code for using ArrayList As:

Collections.sort(otherStrings, Comparator<String?> { o1, o2 ->

Output Below:

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM