Best C# Sort Method for Dictionary Values

Best Sort Method for Dictionary Values Banner Image

Introduction

In C#, a dictionary by its nature is not sorted and doesn't hold an order. Sometimes it is necessary to sort the dictionary by value. Usually, these are just one-time operations where we wouldn't need to convert the dictionary to a list to save the order. Also, the goal is here not to convert the dictionary to a different collection type like a list. The reason why you wouldn't want to be converted between collection types is that it would be costly in performance.

How To Sort A Dictionary

There are LINQ expressions that put the dictionary in a sorted order, but when you try to use the dictionary at another time to loop through the keys or values it is a random order of the dictionary.

LINQ Query

We can use the LINQ query to write an expression to order the dictionary in the loop by ascending or descending order.

LINQ Query Example Code

This code works by surrounding the expression in parentheses while in the foreach loop. This expression orders each key-value pair by values so you can still access the key and value. See the example below.

Dictionary<string, DateTime> keyValuePairs = new Dictionary<string, DateTime>() { { "May", new DateTime(2023, 5, 28) }, { "Oct", new DateTime(2023, 10, 29) }, { "March", new DateTime(2023, 3, 18) } };//create dictionary

foreach (var keyValuePair in (from dictionaryPair in keyValuePairs orderby dictionaryPair.Value ascending select dictionaryPair))//dictionary sort method in ascending order
{
    Console.WriteLine($"key:{keyValuePair.Key}, value:{keyValuePair.Value}");//print sorted dictionary key and values
}
Code Output
key:March, value:3/18/2023 12:00:00 AM
key:May, value:5/28/2023 12:00:00 AM
key:Oct, value:10/29/2023 12:00:00 AM

This returns the list in ascending order, which is least to greatest.

The following example shows in descending order which is greatest to least.

Dictionary<string, DateTime> keyValuePairs = new Dictionary<string, DateTime>() { { "April", new DateTime(2023, 4, 12) }, { "September", new DateTime(2023, 9, 03) }, { "Feburary", new DateTime(2023, 2, 10) } };//create dictionary

foreach (var keyValuePair in (from dictionaryPair in keyValuePairs orderby dictionaryPair.Value descending select dictionaryPair))//dictionary sort method in descending order
{
    Console.WriteLine($"key:{keyValuePair.Key}, value:{keyValuePair.Value}");//print sorted dictionary key and values
}
Code Output
key:September, value:9/3/2023 12:00:00 AM
key:April, value:4/12/2023 12:00:00 AM
key:Feburary, value:2/10/2023 12:00:00 AM

LINQ Query Speed Test

For this test, 10 tests will be averaged. Each test will have 10 thousand function calls and will sort and loop through 10 thousand objects.


using System.Diagnostics;
int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 10000;//Number of function calls made per test
int numberOfObjectsToCreate = 10000;//1 million test objects
string testName = " LINQ Query";//Test name to print to average

void TestMethod(Dictionary<string, DateTime> dictionary)
{
    foreach (var keyValuePair in (from dictionaryPair in dictionary orderby dictionaryPair.Value ascending select dictionaryPair))//dictionary sort method in ascending order
    {
       //some logic
    }
}

Supporting Code
List<double> testSpeedList = new List<double>();
for (int i = 0; i < numberOfTests; i++)
{
    testSpeedList.Add(StartTest());
}
Console.WriteLine($"{testName} Average speed:{Math.Round(testSpeedList.Average())}ms, In {numberOfTests} tests");

double StartTest()
{
    Stopwatch stopwatch = new Stopwatch();
    Dictionary<string, DateTime> testData = GetDictionaryData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {
        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData);//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Function calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms");
    return stopwatch.Elapsed.TotalMilliseconds;
}

Dictionary<string, DateTime> GetDictionaryData()
{
    Dictionary<string, DateTime> testData = new Dictionary<string, DateTime>();

    while (numberOfObjectsToCreate > testData.Count)
    {
        DateTime date = GetDateTime();
        string dateString = date.ToShortDateString();
        if (!testData.ContainsKey(dateString))
        {
            testData[dateString] = date;
        }
    }
    return testData;
}

int GetRandomInt(int minNumber, int maxNumber)
{
    Random random = new Random();//Create Random class
    int randomInt = random.Next(minNumber, maxNumber);//Get a random number between 1 and the maxnumber
    return randomInt;
}

DateTime GetDateTime()
{
    DateTime dateTime = new DateTime(GetRandomInt(1, 6000), GetRandomInt(1, 12), GetRandomInt(1, 29));
    return dateTime;
}

Code Output
Function calls:10000, In 0m 10s 566ms
Function calls:10000, In 0m 10s 309ms
Function calls:10000, In 0m 10s 282ms
Function calls:10000, In 0m 10s 109ms
Function calls:10000, In 0m 10s 626ms
Function calls:10000, In 0m 10s 391ms
Function calls:10000, In 0m 10s 375ms
Function calls:10000, In 0m 10s 212ms
Function calls:10000, In 0m 10s 100ms
Function calls:10000, In 0m 10s 455ms
LINQ Query Average speed:10343ms, In 10 tests

This test shows a baseline of about 10 seconds.

LINQ Enumerable

This is an extension method that we can use on the dictionary. This also creates an ordered dictionary.

Dictionary<string, DateTime> keyValuePairs = new Dictionary<string, DateTime>() { { "June", new DateTime(2023, 6, 3) }, { "November", new DateTime(2023, 11, 06) }, { "January", new DateTime(2023, 1, 20) } };//create dictionary

foreach (KeyValuePair<string,DateTime> keyValuePair in keyValuePairs.OrderBy(x=>x.Value))//dictionary sort method in accending order
{
    Console.WriteLine($"key:{keyValuePair.Key}, value:{keyValuePair.Value}");//print sorted dictionary key and values
}
Code Output
key:January, value:1/20/2023 12:00:00 AM
key:June, value:6/3/2023 12:00:00 AM
key:November, value:11/6/2023 12:00:00 AM

This is also the same example but in descending order.

Dictionary<string, DateTime> keyValuePairs = new Dictionary<string, DateTime>() { { "July", new DateTime(2023, 7, 6) }, { "December", new DateTime(2023, 12, 8) }, { "May", new DateTime(2023, 5, 22) } };//create dictionary

foreach (KeyValuePair<string, DateTime> keyValuePair in keyValuePairs.OrderByDescending(x => x.Value))//dictionary sort method in descending order
{
    Console.WriteLine($"key:{keyValuePair.Key}, value:{keyValuePair.Value}");//print sorted dictionary key and values
}
Code Output
key:May, value:5/22/2023 12:00:00 AM
key:July, value:7/6/2023 12:00:00 AM
key:December, value:12/8/2023 12:00:00 AM

LINQ Enumerable Speed Test

This will be the same test as before. Below is the sample code.

using System.Diagnostics;
int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 10000;//Number of function calls made per test
int numberOfObjectsToCreate = 10000;//1 million test objects
string testName = "LINQ Enumerable";//Test name to print to average

void TestMethod(Dictionary<string, DateTime> dictionary)
{
    foreach (KeyValuePair<string, DateTime> keyValuePair in dictionary.OrderBy(x => x.Value))//dictionary sort method in accending order
    {
        //some logic
    }
}
Supporting Code
List<double> testSpeedList = new List<double>();
for (int i = 0; i < numberOfTests; i++)
{
    testSpeedList.Add(StartTest());
}
Console.WriteLine($"{testName} Average speed:{Math.Round(testSpeedList.Average())}ms, In {numberOfTests} tests");

double StartTest()
{
    Stopwatch stopwatch = new Stopwatch();
    Dictionary<string, DateTime> testData = GetDictionaryData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {
        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData);//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Function calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms");
    return stopwatch.Elapsed.TotalMilliseconds;
}

Dictionary<string, DateTime> GetDictionaryData()
{
    Dictionary<string, DateTime> testData = new Dictionary<string, DateTime>();

    while (numberOfObjectsToCreate > testData.Count)
    {
        DateTime date = GetDateTime();
        string dateString = date.ToShortDateString();
        if (!testData.ContainsKey(dateString))
        {
            testData[dateString] = date;
        }
    }
    return testData;
}

int GetRandomInt(int minNumber, int maxNumber)
{
    Random random = new Random();//Create Random class
    int randomInt = random.Next(minNumber, maxNumber);//Get a random number between 1 and the maxnumber
    return randomInt;
}

DateTime GetDateTime()
{
    DateTime dateTime = new DateTime(GetRandomInt(1, 6000), GetRandomInt(1, 12), GetRandomInt(1, 29));
    return dateTime;
}
Code Output
Function calls:10000, In 0m 10s 567ms
Function calls:10000, In 0m 10s 254ms
Function calls:10000, In 0m 10s 96ms
Function calls:10000, In 0m 9s 933ms
Function calls:10000, In 0m 10s 405ms
Function calls:10000, In 0m 10s 289ms
Function calls:10000, In 0m 10s 206ms
Function calls:10000, In 0m 10s 359ms
Function calls:10000, In 0m 9s 969ms
Function calls:10000, In 0m 10s 390ms
LINQ Enumerable Average speed:10247ms, In 10 tests

We can see that this method is very close in performance to the previous method.

Conclusion

Overall RankMethodSpeed
1LINQ Enumerable10247ms
2LINQ Query10343ms

There is a tie in the performance category in this case. Both LINQ methods have the same performance however, I prefer the LINQ Enumerable method because it is less code and more compact and you don't need to know the query syntax. But if you are already familiar with the LINQ query expressions then you can use that form as well to order the dictionary values. It works just as well.

Do you have any other methods of sorting the values of a dictionary? Let me know in the comments.

Get Latest Updates