Two Lists of Keys and Values Mapped To a Dictionary In C#

Two Lists of Keys and Values Mapped To a Dictionary Banner Image

Introduction

Two lists can be mapped to a dictionary if the two lists are separated into a list of keys and a list of values. This is an interesting issue to resolve. The lists will are related based on the position in the list. The first entry of each key and value list will be the first entry in the dictionary and so forth. There are a couple of ways to accomplish this. Of course, we can write a method ourselves and then there is the C# library called LINQ query expression that we can utilize. Let's start with writing our own and how that looks.

Manual Mapping Method

The idea here is to have the two lists and take the length of the list with keys and use the list indexer to obtain the values from each list index by index. There needs to be protection in case one list is short or longer than the other so we don't hit an index out-of-range exception. Let's look at an example.

Manual Mapping Method Example Code

In this example, there are two lists of movies and their directors. Our code will check to make sure that the length of the key is less than the value's length other wise there would be an exception in our code. If there are duplicate keys then the last value overwrites the previous but there no exception will be thrown on duplicated keys.

List<string> movieNames = new List<string>(){
    "Avatar",
    "Titanic",
    "Star Wars: Episode VII - The Force Awakens",
    "Avengers: Endgame",
    "The Lion King",
    "Jurassic World",
    "Marvel's The Avengers",
    "Furious 7",
    "Frozen II",
    "Frozen"
};

List<string> directorNames = new List<string>(){
    "James Cameron",
    "James Cameron",
    "J.J. Abrams",
    "Anthony and Joe Russo",
    "Jon Favreau",
    "Colin Trevorrow",
    "Joss Whedon",
    "James Wan",
    "Chris Buck and Jennifer Lee",
    "Chris Buck and Jennifer Lee"
};

Dictionary<string, string> dictionary = MapListToDictionary(movieNames, directorNames);//Map the two lists to a dictionary

int count = 1;
foreach (KeyValuePair<string, string> entry in dictionary)
{
    Console.WriteLine($"{count++}. key:{entry.Key}, value:{entry.Value}");//Print screen
}

Dictionary<string, string> MapListToDictionary(List<string> movieNames, List<string> directorNames)
{
    Dictionary<string, string> dictionary = new Dictionary<string, string>();
    int movieNameCount = movieNames.Count;//Get count of the keys
    for (int i = 0; i < movieNameCount; i++)//loop through all the indexes of the key list
    {
        if (i < directorNames.Count)//make sure that index exists in the value list
        {
            dictionary[movieNames[i]] = directorNames[i];//if an entry exists already then overwrite otherwise add a new entry.
        }
    }
    return dictionary;
}
Code Output
1. key:Avatar, value:James Cameron
2. key:Titanic, value:James Cameron
3. key:Star Wars: Episode VII - The Force Awakens, value:J.J. Abrams
4. key:Avengers: Endgame, value:Anthony and Joe Russo
5. key:The Lion King, value:Jon Favreau
6. key:Jurassic World, value:Colin Trevorrow
7. key:Marvel's The Avengers, value:Joss Whedon
8. key:Furious 7, value:James Wan
9. key:Frozen II, value:Chris Buck and Jennifer Lee
10. key:Frozen, value:Chris Buck and Jennifer Lee

In this case, there were no duplicated keys but there is more than one of the same value and that's ok. Next, we want to test the speed of this method.

Manual Mapping Method Speed Test

This will be a test that averages the speed of 10 separate tests. Each test has 100 loops of the function call. Each function call will be dealing with two lists of 1 million objects each. Below are the summarized test parameters.

Test Parameters
Test ParametersTotal
Tests10
Function Calls Per Test100
Objects Per Function Call1 Million Per List

Manual Mapping Method Speed Test Code

void TestMethod(List<string> testKeysData, List<string> testValuesData)
{
    Dictionary<string, string> dictionary = new Dictionary<string, string>();
    int testKeysDataCount = testKeysData.Count;//Get count of the keys
    for (int i = 0; i < testKeysDataCount; i++)//loop through all the indexes of the key list
    {
        if (i < testValuesData.Count)//make sure that index exists in the value list
        {
            dictionary[testKeysData[i]] = testValuesData[i];//if an entry exists already then overwrite otherwise add a new entry
        }
    }
}
Code Output
Test 1:Function Calls:100, In 0m 9s 164ms
Test 2:Function Calls:100, In 0m 8s 642ms
Test 3:Function Calls:100, In 0m 8s 712ms
Test 4:Function Calls:100, In 0m 8s 635ms
Test 5:Function Calls:100, In 0m 8s 488ms
Test 6:Function Calls:100, In 0m 8s 669ms
Test 7:Function Calls:100, In 0m 8s 692ms
Test 8:Function Calls:100, In 0m 8s 956ms
Test 9:Function Calls:100, In 0m 8s 925ms
Test 10:Function Calls:100, In 0m 8s 795ms
Manual Mapper Method Average Speed:8768ms, In 10 Tests

This is the baseline test that we'll compare the other tests to. 9 seconds is not bad and we'll see if the other tests can do any better.

Full Manual Mapping Method Speed Test Code

using System.Diagnostics;

int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 100;//Number of function calls made per test
int numberOfObjectsToCreate = 1000000;//Number test objects
string testName = "Manual Mapper Method";//Test name to print to average
void TestMethod(List<string> testKeysData, List<string> testValuesData)
{
    Dictionary<string, string> dictionary = new Dictionary<string, string>();
    int testKeysDataCount = testKeysData.Count;//Get count of the keys
    for (int i = 0; i < testKeysDataCount; i++)//loop through all the indexes of the key list
    {
        if (i < testValuesData.Count)//make sure that index exists in the value list
        {
            dictionary[testKeysData[i]] = testValuesData[i];//if entry exists already then overwrite otherwise add new entry.
        }
    }
}

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

double StartTest(int testIndex)
{
    Stopwatch stopwatch = new Stopwatch();
    List<string> testData = GetListData();//Get intial random generated data
    List<string> testData2 = GetListData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {

        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData, testData2);//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Test {testIndex + 1}:Function Calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms");
    return stopwatch.Elapsed.TotalMilliseconds;
}

List<string> GetListData()
{
    HashSet<string> unique = new HashSet<string>();
    List<string> testData = new List<string>();
    while (numberOfObjectsToCreate > testData.Count)
    {
        DateTime date = GetDateTime();
        string dateString = date.ToShortDateString();
        if (!unique.Contains(dateString))
        {
            unique.Add(dateString);
            testData.Add(dateString);
        }
    }
    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;
}

LINQ Zip + ToDictionary Method

We can use LINQ functions to quicken the time it takes to write our method as some handy functions of Zip make collections and then the ToDictionary to convert the mapping to a dictionary. The nice thing about this is that is very compact and only takes up one line. Let's see it in action by an example.

LINQ Zip + ToDictionary Method Example Code

We use the Zip function to map the movie and director together then use the ToDictionary to map them to a dictionary. The zip function can take in different lengths of lists

List<string> movieNames = new List<string>(){
    "Avatar",
    "Titanic",
    "Star Wars: Episode VII - The Force Awakens",
    "Avengers: Endgame",
    "The Lion King",
    "Jurassic World",
    "Marvel's The Avengers",
    "Furious 7",
    "Frozen II",
    "Frozen"
};

List<string> directorNames = new List<string>(){
    "James Cameron",
    "James Cameron",
    "J.J. Abrams",
    "Anthony and Joe Russo",
    "Jon Favreau",
    "Colin Trevorrow",
    "Joss Whedon",
    "James Wan",
    "Chris Buck and Jennifer Lee",
    "Chris Buck and Jennifer Lee"
};

Dictionary<string, string> dictionary = MapListToDictionary(movieNames, directorNames);//Map the two lists to a dictionary

int count = 1;
foreach (KeyValuePair<string, string> entry in dictionary)
{
    Console.WriteLine($"{count++}. key:{entry.Key}, value:{entry.Value}");//Print screen
}

Dictionary<string, string> MapListToDictionary(List<string> movieNames, List<string> directorNames)
{
    Dictionary<string, string> dictionary = movieNames.Zip(directorNames).ToDictionary(kvp => kvp.First, kvp => kvp.Second);
    return dictionary;
}

Code Output
1. key:Avatar, value:James Cameron
2. key:Titanic, value:James Cameron
3. key:Star Wars: Episode VII - The Force Awakens, value:J.J. Abrams
4. key:Avengers: Endgame, value:Anthony and Joe Russo
5. key:The Lion King, value:Jon Favreau
6. key:Jurassic World, value:Colin Trevorrow
7. key:Marvel's The Avengers, value:Joss Whedon
8. key:Furious 7, value:James Wan
9. key:Frozen II, value:Chris Buck and Jennifer Lee
10. key:Frozen, value:Chris Buck and Jennifer Lee

We get the same result as the manual approach. Next is to test the speed of this function.

LINQ Zip + ToDictionary Method Speed Test

This will be the same test as the previous one. If you want to see the test parameters then expand the below arrow.

Test Parameters
Test ParametersTotal
Tests10
Function Calls Per Test100
Objects Per Function Call1 Million Per List

LINQ Zip + ToDictionary Method Speed Test Code

void TestMethod(List<string> testKeysData, List<string> testValuesData)
{
    Dictionary<string, string> dictionary = testKeysData.Zip(testValuesData).ToDictionary(kvp => kvp.First, kvp => kvp.Second);
}
Code Output
Test 1:Function Calls:100, In 0m 10s 332ms
Test 2:Function Calls:100, In 0m 9s 995ms
Test 3:Function Calls:100, In 0m 10s 868ms
Test 4:Function Calls:100, In 0m 10s 480ms
Test 5:Function Calls:100, In 0m 10s 686ms
Test 6:Function Calls:100, In 0m 10s 794ms
Test 7:Function Calls:100, In 0m 10s 712ms
Test 8:Function Calls:100, In 0m 10s 205ms
Test 9:Function Calls:100, In 0m 10s 471ms
Test 10:Function Calls:100, In 0m 10s 582ms
LINQ Zip + ToDictionary  Method Average Speed:10513ms, In 10 Tests

We can see that this is about 10 seconds and is slower than the manual method.

Full LINQ Zip + ToDictionary Method Speed Test Code

using System.Diagnostics;

int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 100;//Number of function calls made per test
int numberOfObjectsToCreate = 1000000;//Number test objects
string testName = "LINQ Zip + ToDictionary  Method";//Test name to print to average
void TestMethod(List<string> testKeysData, List<string> testValuesData)
{
    Dictionary<string, string> dictionary = testKeysData.Zip(testValuesData).ToDictionary(kvp => kvp.First, kvp => kvp.Second);
}

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

double StartTest(int testIndex)
{
    Stopwatch stopwatch = new Stopwatch();
    List<string> testData = GetListData();//Get intial random generated data
    List<string> testData2 = GetListData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {

        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData, testData2);//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Test {testIndex + 1}:Function Calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms");
    return stopwatch.Elapsed.TotalMilliseconds;
}

List<string> GetListData()
{
    HashSet<string> unique = new HashSet<string>();
    List<string> testData = new List<string>();
    while (numberOfObjectsToCreate > testData.Count)
    {
        DateTime date = GetDateTime();
        string dateString = date.ToShortDateString();
        if (!unique.Contains(dateString))
        {
            unique.Add(dateString);
            testData.Add(dateString);
        }
    }
    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;
}

Conclusion

Overall RankMethodSpeedDuplicate Keys Allowed
1Manual Mapping8768msYes
2LINQ Zip + ToDictionary10513msNo

The best method for converting two lists into a dictionary is to manually write the code ourselves into a for loop that gets an entry the index from both the key and value list. This is the fastest approach. It is versatile that we don't have to be concerned about duplicated keys.

Using LINQ with Zip + ToDictionary is slower but not that much slower so I would still recommend you use this method from a productive and readability angle. The other downside to ToDictionary is that it requires keys to be unique in the list. So you would need to ensure that they are unique before calling ToDictionary otherwise an exception will be thrown. Also, this method is extremely compact and only takes up one line so it's hard to avoid its simplicity. But if you need top performance and have key duplicates then the first method is a clear choice.

Do you know any ways to map two lists to a dictionary that I missed? Let me know in the comments below.

Get Latest Updates