Multi-Key Dictionary in C#

Create a Dictionary with multiple keys and get value using one of keys

Dictionaries in .NET are expected to have close to O(1) lookup times. To achieve this, they make use of the GetHashCode() and Equals() methods of the key objects. The resulting hash code is used to divide the dictionary's contents into partitions. When you look up an item, the partition is identified using the hash code, all the items in that partition with a matching hash code* are compared to the key you're looking up using the Equals() method.

Here you are trying to create a dictionary with two keys for every object. You're doing this using a Tuple to make one key. The GetHashCode() result of a Tuple is based on both of its values, so the performance of a dictionary is lost if you want to look up values by only half of the key. You would need to go through the entire dictionary comparing each individual item, rendering it little better than a list.

One solution would be to make a dictionary that has a string->int key lookup, and then the other dictionary just be int->string. This would require two lookups when using string keys, but might be a good solution.

Example:

Dictionary<string, int> stringKeyToIntKey = new Dictionary<string, int>();
Dictionary<int, string> intKeyDict = new Dictionary<int, string>();

intKeyDict[1] = "Test";
stringKeyToIntKey["I1"] = 1;

Console.WriteLine(intKeyDict[1]);
Console.WriteLine(intKeyDict[stringKeyToIntKey["I1"]]);

An add method could look like this:

public void AddEntry(int intKey, string stringKey, string value)
{
intKeyDict[intKey] = value;
stringKeyToIntKey[stringKey] = intKey;
}

And you could wrap TryGetValue to make life easier:

public bool TryGetValue(string stringKey, out string value)
{
value = null;
return stringKeyToIntKey.TryGetValue(stringKey, out int intKey) && intKeyDict.TryGetValue(intKey, out value);
}

Delete would look like this:

public void DeleteEntry(string stringKey)
{
if (stringKeyToIntKey.TryGetValue(stringKey, out int intKey))
{
intKeyDict.Remove(intKey);
stringKeyToIntKey.Remove(stringKey);
}
}

You would have to make sure that items are added and removed from both dictionaries at the same time. When you add an item to intKey, you would need to add the corresponding key mapping to stringKeyToIntKey.

Alternatively, you could have two dictionaries: one with a string key and one with an int key, and each would have the same values. Again you would have to add and remove items at the same time, and you would also have to update the values in both at the same time.

Example:

Dictionary<string, string> stringKeyDict = new Dictionary<string, string>();
Dictionary<int, string> intKeyDict = new Dictionary<int, string>();

stringKeyDict["I1"] = "hello";
intKeyDict[1] = "hello";

Console.WriteLine(stringKeyDict["I1"]);
Console.WriteLine(intKeyDict[1]);

This is my favoured approach where the values are class instances, since both dictionaries will reference the same class instances for my items, and thus changes to properties of those instances will be reflected in both. For strings, however, the first option might be better.

* Hash codes are not unique and multiple objects can potentially have the same hash code, even if their values are not the same

Two Keys and A Value in A Dictionary

Actually you can do that already without an additional class which is even limited to only two keys. Use a Lookup<TKey, TElement> which uses an anonymousy type as key:

var idStatusLookup = employees.ToLookup(x => new {x.EmployeeId, x.Status});
var matchingEmployees = idStatusLookup[new { EmployeeId = 1001, Status = "Active"}];

foreach (Employee emp in matchingEmployees)
{
Console.WriteLine(emp.EmployeeId + " " + emp.EmployeeName);
}

A lookup is similar to a dictionary but it allows to have multiple keys and there is always a value, even for non-contained keys. How is that possible? By returning Enumerable.Empty<TValue> if the key is not contained and by returning a sequence of values for the same key.

Multi-key dictionaries (of another kind) in C#?

This blog post seems to detail a rather decent implementation.

Multi-key generic dictionary class for C#


MultiKeyDictionary is a C# class
that wraps and extends the Generic
Dictionary object provided by
Microsoft in .NET 2.0 and above. This
allows a developer to create a generic
dictionary of values and reference the
value list through two keys instead of
just the one provided by the Microsoft
implementation of the Generic
Dictionary<...>. You can see my
article on CodeProject (here), however
this code is more up-to-date and bug
free.

C# Multiple Keys with Multiple Values in Dictionary and postprocess the Dictonary

I think the following code should work for you:

// Test data to mock data from the CSV file
static IEnumerable<(string key, double value)> ReadCsv() =>
new[]
{
("ABC", 42.0),
("ABC", 123.0),
("DEF", 35.0),
("DEF", 15.0)
};

static void Main(string[] args)
{
// Task 1: Constructing a Dictionary<string, List<double>>
var data = ReadCsv()
.GroupBy(entry => entry.key)
.ToDictionary(
group => group.Key,
group => group.Select(entry => entry.value));

// Task 2: Iterating through the keys and verifying the total
var isSuccess = data.All(entry => entry.Value.Sum() > 50);
if (isSuccess)
Console.WriteLine("The total of values for each and every entry is greater than 50");
else
Console.WriteLine("The total of values for each and every entry is not greater than 50");
}

To read data from a CSV file, I would suggest using something like:

static IEnumerable<(string key, double value)> ReadCsv(string filePath, uint nHeaderLines = 0)
{
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using (var reader = new StreamReader(stream))
{
var lineIndex = 0u;
while (reader.ReadLine() is string dataEntry)
{
if (lineIndex++ < nHeaderLines // Skip header
|| string.IsNullOrWhiteSpace(dataEntry)) // Ignore blank lines
{
continue;
}

var dataElements = dataEntry.Split(',', StringSplitOptions.RemoveEmptyEntries);
var key = dataElements[0];
var value = double.Parse(dataElements[7], NumberStyles.Float, NumberFormatInfo.InvariantInfo);

yield return (key, value);
}
}
}

Please note, that for the real solution it would also make sense to check the data line if it has all the entries, i.e., that elements at index 0 and 7 always exist and are valid

Dictionary with multiple keys and multiple values for each key

Personally, I'd probably use a Dictionary of Dictionaries, e.g. IDictionary<int, IDictionary<int, IList<int>>>. Not I am not entirely sure how you intend to access or facilitate this data; that will have a large impact on how efficient my suggestion is. On the upside, it would allow you to -- relatively easily -- access data, if and only if you access it in the order you set up your dictionaries.

(On second thought, simply the type declaration itself is so ugly and meaningless, you might want to skip what I said above.)

If you are accessing fields rather randomly, maybe a simple denormalized ICollection<Tuple<int, int, int>> (or equivalent) will have to do the trick, with aggregation in other parts of your application as needed. LINQ can help here a lot, especially its aggregation, grouping, and lookup features.

Update: Hopefully this clarifies it:

var outerDictionary = new Dictionary<int, Dictionary<int, List<int>>>();

/* fill initial values
* assuming that you get your data row by row from an ADO.NET data source, EF, or something similar. */
foreach (var row in rows) {
var employeeId = (int) row["EmpID"];
var payYear = (int) row["PayYr"];
var payId = (int) row["PayID"];


Dictionary<int, int> innerDictionary;
if (!outerDictionary.TryGet(employeeId, out innerDictionary)) {
innerDictionary = new Dictionary<int, int>();
outerDictionary.Add(employeeId, innerDictionary);
}

List<int> list;
if (!innerDictionary.TryGet(payYear)) {
list = new List<int>();
innerDictionary.Add(payYear, list);
}

list.Add(payId);
}

/* now use it, e.g.: */
var data = outerDictionary[1000][2011]; // returns a list with { 1, 2, 3 }

Take it with a grain of salt though; see comment.

Multi-key dictionary with optional keys

In order to maintain only 1 dictionary, consider converting the GroupId (int) into a string and use it as a key (number 'keys' should not conflict with name keys). Maintain a references to the keys, so that if one gets deleted, the rest will be deleted.

How to assign one value to multiple keys in a C# dictionary?

A Dictionary<TKey, TValue> is a collection of keys and values. In this collection, each key is mapped to just one value. And it has been implemented in the System.Collection.Generics namespace.

On the other hand, there is a collection named Lookup<TKey, TElement> which represents a one to many mapping between keys and values. I.e. it maps one key to one or more values, and it is in the System.Linq namespace.

You can convert any collections which have implemented the IEnumerable interface and the ToLookup() method in the System.Linq namespace to construct the Lookup<TKey, TElement> collection.

Read more about Lookup collection and Dictionary in the Microsoft tutorials about C# language.

In this question, we could consider a package consisting of two fields, the "Alphabet" character and its "Counts" in a sentence.

class Package
{
public char Alphabet;
public int Counts;
}

Then construct the Lookup data structure:

public static void LookupExample()
{
// Create a dictionary of Packages to put into a Lookup data structure.
var testDict = new Dictionary <char, int>() {
new Package { Alphabet = 'A', Counts = 1},
new Package { Alphabet = 'B', Counts = 3},
new Package { Alphabet = 'C', Counts = 3},
new Package { Alphabet = 'D', Counts = 2},
new Package { Alphabet = 'E', Counts = 1},
new Package { Alphabet = 'F', Counts = 4},
new Package { Alphabet = 'G', Counts = 2},
new Package { Alphabet = 'H', Counts = 4},
new Package { Alphabet = 'I', Counts = 1},
new Package { Alphabet = 'J', Counts = 8},
new Package { Alphabet = 'K', Counts = 5},
new Package { Alphabet = 'L', Counts = 1},
new Package { Alphabet = 'M', Counts = 3},
new Package { Alphabet = 'N', Counts = 1},
new Package { Alphabet = 'O', Counts = 1},
new Package { Alphabet = 'P', Counts = 3},
new Package { Alphabet = 'Q', Counts = 10},
new Package { Alphabet = 'R', Counts = 1},
new Package { Alphabet = 'S', Counts = 1},
new Package { Alphabet = 'T', Counts = 1},
new Package { Alphabet = 'U', Counts = 1},
new Package { Alphabet = 'V', Counts = 4},
new Package { Alphabet = 'W', Counts = 4},
new Package { Alphabet = 'X', Counts = 8},
new Package { Alphabet = 'Y', Counts = 4},
new Package { Alphabet = 'Z', Counts = 10}};


// Create a Lookup to organize the packages. Use the Counts of each Alphabet as the key value.
// Select Alpahbet appended to each Counts in the Lookup.
Lookup<int, char> lookup = (Lookup<int, char>)testDict.ToLookup(p => p.Counts, p => p.Alphabet);

// Iterate through each IGrouping in the Lookup and output the contents.
foreach (IGrouping<int, char> packageGroup in lookup)
{
// Print the key value of the IGrouping.
Console.WriteLine(packageGroup.Key);
// Iterate through each value in the IGrouping and print its value.
foreach (char chr in packageGroup)
Console.WriteLine(" {0}", Convert.ToString(chr));
}
}

This helps to consider the "Counts" as the new key and assign multiple "Alphabet" characters to it which have same amount for their "Counts" value!

C# Dictionary: Multiple KEYS per Value

Do the dictionary the other way around and make the value a list of items.

if for example Value is a string and Key 1-4 are ints your dictionary could look something like:

var theDictionary = new Dictionary<string, List<int>>();

retrieving Value by theDictionary["Value"] would then return a list of ints containing 1, 2, 3 and 4.

Edit - Added example:

var theDictionary = new Dictionary<string, List<string>>
{
{"Value", new List<string> {"Key 1", "Key 2", "Key 3", "Key 4", "Key 5",}},
{"Value2", new List<string> {"Key 5", "Key 2"}}
};

var oneToFour = theDictionary["Value"];


Related Topics



Leave a reply



Submit