简体   繁体   中英

c# organize dictionary keys in numerical order

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?

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..

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.

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

        }
    }
}

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