Sort List<T> using regex

Asked

Viewed 288 times

14

I have a List of directories that is ordered by name:

List<DirectoryInfo> dirs = parentdir.GetDirectories().OrderBy(c => c.Name).ToList();

The problem is that I have folders whose name are numbers separated by point (as if it were the version of an app A.B.C.D => 1.2.3.4) and then this sorting no longer works, because sorting by name the folder 1.1.3.10 comes before the 1.1.3.3.

Is there any way to sort that out with the help of regex?

2 answers

12


You can build a comparator (IComparer) which compares strings containing numbers, and passes it to the method OrderBy:

lista.OrderBy(c => c.Str, meuComparador)

This comparator you can do using Regex.Split to split the string at positions where numbers are found:

Regex.Split(str, @"(\d+)")
  • A regex \d+ serve to indicate that we want find strings with 1 or + digits:

    • \d means any digit
    • + means find one or more of the previous item
  • The parenthesis around the \d+, serves to indicate to the split, that the number must be kept in the string breakdown array, so that we can use it in the comparison. See how it’s different:

    Regex.Split("a123b", @"\d+")   => array ["a", "b"]
    
    Regex.Split("a123b", @"(\d+)") => array ["a", "123", "b"]
    

The string comparator class containing numbers

I implemented the class, to be present to those who need it in the future. = D

public class ComparerStringComNumeros : IComparer<string>
{
    public static ComparerStringComNumeros Instancia
        = new ComparerStringComNumeros();

    private ComparerStringComNumeros() { }

    public int Compare(string x, string y)
    {
        var itemsA = Regex.Split(x, @"(\d+)");
        var itemsB = Regex.Split(y, @"(\d+)");

        for (int it = 0; ; it++)
        {
            if (it == itemsA.Length)
                return it == itemsB.Length ? 0 : -1;

            if (it == itemsB.Length)
                return 1;

            if ((it % 2) == 0)
            {
                // parte não numérica
                var strCompare = StringComparer.CurrentCulture.Compare(
                    itemsA[it],
                    itemsB[it]);

                if (strCompare != 0)
                    return strCompare;
            }
            else
            {
                // parte numérica
                var numCompare = Comparer<int>.Default.Compare(
                    int.Parse(itemsA[it]),
                    int.Parse(itemsB[it]));

                if (numCompare != 0)
                    return numCompare;
            }
        }
    }
}

Test the class above, using the OrderBy:

public void TesteDeOrdenacao()
{
    var l = new[]
        {
            "x0.2",
            "m1.2",
            "m1.04",
            "m10.0",
            "x1.2",
            "x1.04",
            "m10.0.0",
            "x1.2.2",
            "x1.04.8 a",
            "x1.04.8 b",
            "x1.04.8 c2",
            "x1.04.8 c3",
            "x1.04.8 c1",
            "x10.0",
            "m0.2"
        };

    var l2 = l.OrderBy(x => x, ComparerStringComNumeros.Instancia).ToList();

    // l2 irá conter:
    //
    // "m0.2",
    // "m1.2",
    // "m1.04",
    // "m10.0",
    // "m10.0.0",
    // "x0.2",
    // "x1.2",
    // "x1.2.2",
    // "x1.04",
    // "x1.04.8 a",
    // "x1.04.8 b",
    // "x1.04.8 c1",
    // "x1.04.8 c2",
    // "x1.04.8 c3",
    // "x10.0"
}

How to use in your code:

var dirs = parentdir.GetDirectories()
    .OrderBy(c => c.Name, ComparerStringComNumeros.Instancia)
    .ToList();
  • Perfect guy, thank you very much. Looking at first I can not understand how you did it (even because I don’t know regex), but I will use this class to study.

  • 1

    I edited it! I added more information about regex, and how to interpret it.

3

The @Miguelangelo response is good in a general context - but in this specific context, there is a solution much easier and more appropriate to the problem.

You have to convert strings to class instances Version. How this class already implements IComparable<Version>, the OrderBy will work properly, without having to write additional code.

parentdir.GetDirectories()
         .OrderBy(c => new Version(c.Name))
         .ToList();

Browser other questions tagged

You are not signed in. Login or sign up in order to post.