How Would I Sort a List of Files by Name to Match How Windows Explorer Displays Them

How would I sort a list of files by name to match how Windows Explorer displays them?

Windows Explorer uses an API called:

StrCmpLogicalW

to perform the sort in a "logical" manner.

Someone has also implemented a class in C# which will do this for you.

C# How do I use Directory.GetFiles() to get files that have the same order as in Windows explorer?

I think this is going to be a lot more complex than you expect. Folder settings are stored in the registry in two places:

HKCU\Software\Microsoft\Windows\Shell\BagMRU
HKCU\Software\Microsoft\Windows\Shell\Bags

The first path contains a structure which reflects the structure of the file system, and the second path contains details about those items, including a REG_BINARY value called "Sort" which records the sort order used for that folder.

See Willi Balenthin's website for details on the structure, including sample code (in Python)

How to sort by file name the same way Windows Explorer does?

TL;DR

Get-ChildItem | Sort-Object { [regex]::Replace($_.Name, '\d+', { $args[0].Value.PadLeft(20) }) }

Here is some very short code (just the $ToNatural script block) that does the trick with a regular expression and a match evaluator in order to pad the numbers with spaces. Then we sort the input with padded numbers as usual and actually get natural order as a result.

$ToNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) }

'----- test 1 ASCIIbetical order'
Get-Content list.txt | Sort-Object

'----- test 2 input with padded numbers'
Get-Content list.txt | %{ . $ToNatural }

'----- test 3 Natural order: sorted with padded numbers'
Get-Content list.txt | Sort-Object $ToNatural

Output:

----- test 1 ASCIIbetical order
1.txt
10.txt
3.txt
a10b1.txt
a1b1.txt
a2b1.txt
a2b11.txt
a2b2.txt
b1.txt
b10.txt
b2.txt
----- test 2 input with padded numbers
1.txt
10.txt
3.txt
a 10b 1.txt
a 1b 1.txt
a 2b 1.txt
a 2b 11.txt
a 2b 2.txt
b 1.txt
b 10.txt
b 2.txt
----- test 3 Natural order: sorted with padded numbers
1.txt
3.txt
10.txt
a1b1.txt
a2b1.txt
a2b2.txt
a2b11.txt
a10b1.txt
b1.txt
b2.txt
b10.txt

And finally we use this one-liner to sort files by names in natural order:

Get-ChildItem | Sort-Object { [regex]::Replace($_.Name, '\d+', { $args[0].Value.PadLeft(20) }) }

Output:

    Directory: C:\TEMP\_110325_063356

Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2011-03-25 06:34 8 1.txt
-a--- 2011-03-25 06:34 8 3.txt
-a--- 2011-03-25 06:34 8 10.txt
-a--- 2011-03-25 06:34 8 a1b1.txt
-a--- 2011-03-25 06:34 8 a2b1.txt
-a--- 2011-03-25 06:34 8 a2b2.txt
-a--- 2011-03-25 06:34 8 a2b11.txt
-a--- 2011-03-25 06:34 8 a10b1.txt
-a--- 2011-03-25 06:34 8 b1.txt
-a--- 2011-03-25 06:34 8 b2.txt
-a--- 2011-03-25 06:34 8 b10.txt
-a--- 2011-03-25 04:54 99 list.txt
-a--- 2011-03-25 06:05 346 sort-natural.ps1
-a--- 2011-03-25 06:35 96 test.ps1

Java File list same order like Window explorer

Sorting by name in Windows is tricky and far more complicated than your implementation. It's also configurable and version dependent.

NOTE: I created a demo for what follows in this post. Check it out on GitHub.

Sorting file names using StrCmpLogicalWComparator function

According to some (e.g. here) Windows uses StrCmpLogicalW for sorting files by name.

You could try to implement your comparator by calling this system function using JNA (don't forget to include JNA library in your project):

Comparator:

public class StrCmpLogicalWComparator implements Comparator<String> {

@Override
public int compare(String o1, String o2) {
return Shlwapi.INSTANCE.StrCmpLogicalW(
new WString(o1), new WString(o2));
}
}

JNA part:

import com.sun.jna.WString;
import com.sun.jna.win32.StdCallLibrary;

public interface Shlwapi extends StdCallLibrary {

Shlwapi INSTANCE = Native.load("Shlwapi", Shlwapi.class);

int StrCmpLogicalW(WString psz1, WString psz2);
}

Handling file names that contain digits

I mentioned earlier that the way that Windows Explorer sorts files is configurable. You can change how numbers in file names are handled and toggle so-called "numerical sorting". You can read how to configure this here. Numerical sorting as explained in the docs:

Treat digits as numbers during sorting, for example, sort "2" before "10".

-- https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-comparestringex#SORT_DIGITSASNUMBERS

With numerical sorting enabled the result is:

file names sorted with numerical sorting enabled

whereas with numerical sorting disabled it looks like this:

file names sorted with numerical sorting disabled

This makes me think that Windows Explorer in fact uses CompareStringEx function for sorting which can be parameterized to enable this feature.

Sorting file names using CompareStringEx function

JNA part:

import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.win32.StdCallLibrary;

public interface Kernel32 extends StdCallLibrary {

Kernel32 INSTANCE = Native.load("Kernel32", Kernel32.class);
WString INVARIANT_LOCALE = new WString("");

int CompareStringEx(WString lpLocaleName,
int dwCmpFlags,
WString lpString1,
int cchCount1,
WString lpString2,
int cchCount2,
Pointer lpVersionInformation,
Pointer lpReserved,
int lParam);

default int CompareStringEx(int dwCmpFlags,
String str1,
String str2) {
return Kernel32.INSTANCE
.CompareStringEx(
INVARIANT_LOCALE,
dwCmpFlags,
new WString(str1),
str1.length(),
new WString(str2),
str2.length(),
Pointer.NULL,
Pointer.NULL,
0);
}
}

Numeric sort comparator:

public class CompareStringExNumericComparator implements Comparator<String> {

private static int SORT_DIGITSASNUMBERS = 0x00000008;

@Override
public int compare(String o1, String o2) {
int compareStringExComparisonResult =
Kernel32.INSTANCE.CompareStringEx(SORT_DIGITSASNUMBERS, o1, o2);

// CompareStringEx returns 1, 2, 3 respectively instead of -1, 0, 1
return compareStringExComparisonResult - 2;
}
}

Non-numeric sort comparator:

public class CompareStringExNonNumericComparator implements Comparator<String> {

private static String INVARIANT_LOCALE = "";
private static int NO_OPTIONS = 0;

@Override
public int compare(String o1, String o2) {
int compareStringExComparisonResult =
Kernel32.INSTANCE.CompareStringEx(NO_OPTIONS, o1, o2);

// CompareStringEx returns 1, 2, 3 respectively instead of -1, 0, 1
return compareStringExComparisonResult - 2;
}
}

References

  • Martin Liversage's answer to "What is the shortest way in .NET to sort strings starting with 1, 10 and 2 and respect the number ordering?
  • hmuelner's answer to "What is the first character in the sort order used by Windows Explorer?"

Sorting an array of folder names like Windows Explorer (Numerically and Alphabetically) - VB.NET

You would need to implement an IComparer, as opposed to creating a class that implements IComparable. The difference is that an IComparer has the necessary "knowledge" to compare two objects whereas IComparable is implemented by a class that knows how to compare itself against something else.

And the way Windows Explorer sorts filenames is using a function called StrCmpLogicalW. You can use this function in your own IComparer to get the same sort behavior as Windows Explorer. This function treats numeric parts of strings as numbers so that 9 sorts before 10.

public class MyComparer : IComparer<string> {

[DllImport("shlwapi.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
static extern int StrCmpLogicalW(String x, String y);

public int Compare(string x, string y) {
return StrCmpLogicalW(x, y);
}

}

Array.Sort(unsortedNames, new MyComparer());

And since I just noticed the question is tagged VB... Forgive my rusty VB!

Public Class MyComparer
Implements IComparer(Of String)

Declare Unicode Function StrCmpLogicalW Lib "shlwapi.dll" ( _
ByVal s1 As String, _
ByVal s2 As String) As Int32

Public Function Compare(Byval x as String, Byval y as String) As Integer _
Implements System.Collections.Generic.IComparer(Of String).Compare

Return StrCmpLogicalW(x, y)

End Function

End Class

Java - Sort Strings like Windows Explorer

This is my second try to answer this. I used http://www.interact-sw.co.uk/iangblog/2007/12/13/natural-sorting as a start. Unfortunatly I think I found there problems as well. But I think in my code these problems are correctly adressed.

Info: Windows Explorer uses the API function StrCmpLogicalW() function to do its sorting. There it is called natural sort order.

So here is my unterstanding of the WindowsExplorerSort - Algorithm:

  • Filenames are compared part wise. As for now I identified the following parts: numbers, '.', spaces and the rest.
  • Each number within the filename is considered for a possible number compare.
  • Numbers are compared as numbers but if they are equal, the longer base string comes first. This happens with leading zeros.
    • filename00.txt, filename0.txt
  • If one compares a number part with a non number part, it will be compared as text.
  • Text will be compared case insensitive.

This list is based partly on try and error. I increased the number of test filenames, to adress more of the in comments mentioned pitfalls and the result was checked against a Windows Explorer.

So here is the output of this:

filename
filename 00
filename 0
filename 01
filename.jpg
filename.txt
filename00.jpg
filename00a.jpg
filename00a.txt
filename0
filename0.jpg
filename0a.txt
filename0b.jpg
filename0b1.jpg
filename0b02.jpg
filename0c.jpg
filename01.0hjh45-test.txt
filename01.0hjh46
filename01.1hjh45.txt
filename01.hjh45.txt
Filename01.jpg
Filename1.jpg
filename2.hjh45.txt
filename2.jpg
filename03.jpg
filename3.jpg

The new comparator WindowsExplorerComparator splits the filename in the already mentioned parts and does a part wise comparing of two filenames. To be correct, the new comparator uses Strings as its input so one has to create an adaptor Comparator like

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

So here is the new Comparators source code and its test:

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class WindowsSorter {

public static void main(String args[]) {
//huge test data set ;)
List<File> filenames = Arrays.asList(new File[]{new File("Filename01.jpg"),
new File("filename"), new File("filename0"), new File("filename 0"),
new File("Filename1.jpg"), new File("filename.jpg"), new File("filename2.jpg"),
new File("filename03.jpg"), new File("filename3.jpg"), new File("filename00.jpg"),
new File("filename0.jpg"), new File("filename0b.jpg"), new File("filename0b1.jpg"),
new File("filename0b02.jpg"), new File("filename0c.jpg"), new File("filename00a.jpg"),
new File("filename.txt"), new File("filename00a.txt"), new File("filename0a.txt"),
new File("filename01.0hjh45-test.txt"), new File("filename01.0hjh46"),
new File("filename2.hjh45.txt"), new File("filename01.1hjh45.txt"),
new File("filename01.hjh45.txt"), new File("filename 01"),
new File("filename 00")});

//adaptor for comparing files
Collections.sort(filenames, 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());
}
});

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

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

File sorting problem

You have to pad the numbers with zeroes, like so:

Slide01.png
Slide02.png
...
Slide10.png
...

I've had many problems with this myself, so if you have many of them to do (I had 600 images), try a batch renamer, like Metamorphose.



Related Topics



Leave a reply



Submit