Sort ListT using regex

14

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

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

The problem is that I have folders whose names are numbers separated by periods (as if it were the version of an ABCD app = > 1.2.3.4 ), and there this sort order no longer works, because sorting the 1.1.3.10 folder by name comes before 1.1.3.3 .

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

    
asked by anonymous 10.04.2015 / 19:43

2 answers

12

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

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

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

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

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

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

The class comparator of strings containing numbers

Implemented the class, to be present for whoever needs 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 above class using 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 it in your code:

var dirs = parentdir.GetDirectories()
    .OrderBy(c => c.Name, ComparerStringComNumeros.Instancia)
    .ToList();
    
10.04.2015 / 20:36
3

The response of @MiguelAngelo is good in a general context - however, in this specific context, there is a much easier and more adequate solution to the problem.

You have to convert the strings to instances of the Version . Because this class already implements IComparable<Version> , OrderBy will work correctly without writing additional code.

parentdir.GetDirectories()
         .OrderBy(c => new Version(c.Name))
         .ToList();
    
12.04.2015 / 03:38