-2

I have a program where I am parsing sql scripts in order of the file directory. The idea from the team was that changes or additions to the sql scripts were done in order, and so the folders are named from 0 to 12. So I need to parse through these folders in numerical order, however when they are parsed in order, these are the order in which I am placing them as the Key in the dictionary:

C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\0 
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\1  
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\10
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\11
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\12
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\2 
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\3 
etc...

When I iterate through this dictionary, I want to run these folders in numerical order so I can build my sql scripts in the order they were designed. I have the path saved as the key (string), and I need to reorganize them so that the path folders are listed in numerical order. So that they look like this:

C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\0 
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\1  
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\2 
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\3 
etc...
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\9
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\10
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\11
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\12

My Dictionary structure is in the form of <string, List<string>>. I am looking at a file directory that holds a series of folders, and each folder holds a handful of SQL files for building a database. My Dictionary Keys are the folder paths for the subfolders inside this directory, and the values is a List of strings that hold the paths for those files inside the folder path in the Key. I'll edit my question to mention this. How can I order my dictionary by my keys in numerical order?

as.beaulieu
  • 464
  • 1
  • 10
  • 26
  • 2
    A dictionary has no concept of order. If it's only a display issue then your UI layer needs to order them appropriately. Lookup `NaturalSort` or `SemiNaturalSort` – JSteward Mar 05 '18 at 18:11
  • 1
    Investigate [`SortedDictionary`](https://msdn.microsoft.com/en-us/library/f7fta44c(v=vs.110).aspx). – Dour High Arch Mar 05 '18 at 18:11
  • @DourHighArch Sorted Dictionary would not fix this. – paparazzo Mar 05 '18 at 18:12
  • Can you use List as it preserves order. – paparazzo Mar 05 '18 at 18:13
  • From MSDN *For purposes of enumeration, each item in the dictionary is treated as a KeyValuePair structure representing a value and its key. **The order in which the items are returned is undefined.*** – Ňɏssa Pøngjǣrdenlarp Mar 05 '18 at 18:13
  • @Plutonix: It would if you provided a suitable `IComparer` that performed the appropriate comparisons. – Jon Skeet Mar 05 '18 at 18:13
  • He can use a NaturalSort for the `IComparer` in `SortedDictionary` if that is what he wants. The question is unclear, which is why I said only to “investigate” `SortedDictionary`. – Dour High Arch Mar 05 '18 at 18:15
  • I have edited my question to state my desire for sorted keys. The sql files are in order numerically and I want to build these sql files in the order they were designed. @JSteward would you have a good link or example for Natural Sort? – as.beaulieu Mar 05 '18 at 18:15
  • @as.beaulieu **[sure](https://stackoverflow.com/questions/248603/natural-sort-order-in-c-sharp)**, could be almost a duplicate as well – JSteward Mar 05 '18 at 18:17
  • @DourHighArch For the second time a string does NOT sort integer in a proper order. – paparazzo Mar 05 '18 at 18:58
  • @Paparazzi Do you know what Natural sort is? You can easily write a Comparer and use in *orderby* method – Eser Mar 05 '18 at 19:00
  • @Paparazzi I never made any such claim; I claimed if he wants a Dictionary sorted by integers stored as strings he can use a [Natural sort](https://stackoverflow.com/questions/248603/natural-sort-order-in-c-sharp) for the `IComparer` of a `SortedDictionary`. – Dour High Arch Mar 05 '18 at 19:01
  • @Eser Not going to debate with you. – paparazzo Mar 05 '18 at 19:01
  • @DourHighArch What does natural sort of FileInfo have to do with natural sort of string? – paparazzo Mar 05 '18 at 19:04
  • @Eser Please understand the difference between natural sort and supply a (custom) Comparer. – paparazzo Mar 05 '18 at 19:06
  • @Paparazzi please read the link I mentioned; he's talking about Directories, not files, and if `DirectoryInfo.Name` contains strings like "1", "2", ... "10", ... then providing an `IComparer` using this in a `SortedDirectory` will return dictionary values in natural order. I did not bother writing an answer implementing this because it is not clear to me this will solve his problem. He hasn't said what the keys or values are. – Dour High Arch Mar 05 '18 at 19:11
  • @DourHighArch Provide an IComparer is NOT using the natural soft. – paparazzo Mar 05 '18 at 19:13
  • I have a file directory that holds a series of folders, and each folder holds a handful of SQL files for building a database. My Dictionary Keys are the folder paths for the subfolders inside this directory, and the values is a List of strings that hold the paths for those files inside the folder path in the Key. I'll edit my question to mention this. – as.beaulieu Mar 05 '18 at 19:45

3 Answers3

2

Per request of @Paparazzi

public class NaturalSortComparer : IComparer<string>
{
    Regex _Regex = new Regex(@"(\d+)|(\D+)");

    public int Compare(string s1, string s2)
    {
        var list1 = _Regex.Matches(s1).Cast<Match>().Select(m => m.Value.Trim()).ToList();

        var list2 = _Regex.Matches(s2).Cast<Match>().Select(m => m.Value.Trim()).ToList();

        var min = Math.Min(list1.Count, list2.Count);
        int comp = 0;

        for (int i = 0; i < min; i++)
        {
            int intx, inty;

            if (int.TryParse(list1[i], out intx) && int.TryParse(list2[i], out inty))
                comp = intx - inty;
            else
                comp = String.Compare(list1[i], list2[i]);

            if (comp != 0) return comp;
        }

        return list1.Count - list2.Count;
    }
}

Now you can use it, in SortedDictionary, HashSet, SortedList etc, also as

foreach(var entry in dict.OrderBy(x => x.Key, new NaturalSortComparer())
{
}

PS: Since It uses regex everytime when comparison is needed, It will not be very performant... If needed a cached version can be produced for sorting bigger Lists..

Eser
  • 12,346
  • 1
  • 22
  • 32
1

So I've learned a lot from some of the comments, and it helped guide me to this answer. The answer to what I needed relies on the following points:

A Dictionary cannot be ordered. To store the key value pairs in a desired order, a StoredDictionary needs to be used

Natural Sort is not something that is handled easily. To achieve this, the Linq package needs to be used with the OrderBy() function.

.OrderedBy() needs to use a IComparer and in this case, a custom one had to be designed

I was very fortunate to come across an article that makes a custom comparer specifically for natural ordering of strings with numbers in them. A special thanks to James McCormack for this comparer.

First off, I've turned my scriptsPaths dictionary into a SortedDictionary. And I also instantiate a organizedPaths SortedDictionary.

public static IDictionary<string, List<string>> scriptsPaths = new SortedDictionary<string, List<string>>();
public static IDictionary<string, List<string>> organizedPaths = new SortedDictionary<string, List<string>>();

Then, once my program has parsed the directories for the subdirectory paths, I then created a new class, named NaturalSortComparer, where I've placed the customer IComparer found from the link above:

int IComparer<string>.Compare(string x, string y)
    {
        if (x == y)
            return 0;

        string[] x1, y1;

        if (!table.TryGetValue(x, out x1))
        {
            x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
            table.Add(x, x1);
        }

        if (!table.TryGetValue(y, out y1))
        {
            y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
            table.Add(y, y1);
        }

        int returnVal;

        for (int i = 0; i < x1.Length && i < y1.Length; i++)
        {
            if (x1[i] != y1[i])
            {
                returnVal = PartCompare(x1[i], y1[i]);
                return isAscending ? returnVal : -returnVal;
            }
        }

        if (y1.Length > x1.Length)
        {
            returnVal = 1;
        }
        else if (x1.Length > y1.Length)
        {
            returnVal = -1;
        }
        else
        {
            returnVal = 0;
        }

        return isAscending ? returnVal : -returnVal;
    }

    private static int PartCompare(string left, string right)
    {
        int x, y;
        if (!int.TryParse(left, out x))
            return left.CompareTo(right);

        if (!int.TryParse(right, out y))
            return left.CompareTo(right);

        return x.CompareTo(y);
    }

With the custom comparer in place, I now used the .OrderBy() with the custom comparer

var organizedPaths = directoryManager
                .ProcessDirectory(dbDirectory)
                .OrderBy(x => x.Key, new NaturalSortComparer<string>());

After a foreach loop to Console.WriteLine() the keys, I get the order in which I wanted:

C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\0
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\1
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\2
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\3
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\4
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\5
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\6
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\7
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\8
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\9
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\10
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\11
C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\12

A final note Every resource I found on SortedDictionary states that they are much more resource intensive than a Dictionary, so watch out with very large ones. However, I can only hope that my folder structure will not grow much more, so this is acceptable for me.

as.beaulieu
  • 464
  • 1
  • 10
  • 26
-2

Try following :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] input = {
                                 @"C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\0",
                                 @"C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\1",
                                 @"C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\10",
                                 @"C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\11",
                                 @"C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\12",
                                 @"C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\2",
                                 @"C:\Dev\FileResearch\ParseSqlConsole\DbScriptFolders\3"
                             };

            Dictionary<string,List<int>> dict = input.GroupBy(x => x.Substring(0,x.LastIndexOf(@"\") + 1), y => int.Parse(y.Substring(y.LastIndexOf(@"\") + 1)))
                .ToDictionary(x => x.Key, y => y.OrderBy(z => z).ToList());

        }
    }
}
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • This seems to get me pretty close, but I'm specifically dealing with a Dictionary. So how would I best maintain my KeyValuePair matching instead of pulling the keys out into a new array? – as.beaulieu Mar 05 '18 at 19:39
  • Your dictionary should be >. I updated code to use dictionary. So the key is the folder name ending in a backslash and the file names are integers in order. To get file name you can combine the key and the int using To.String(); – jdweng Mar 05 '18 at 21:03