Reputation:
I need to use a Dictionary, where TKey is a pair of ints.
I thought of using KeyValuePair for the type of my keys and I was wondering if this was the best way around.
I'm also curious to know if the Dictionary will create separate entries for two different KeyValuePair objects with the same ints and why.
For instance:
var myDictionary = new Dictionary<KeyValuePair<int,int>, string>();
myDictionary.Add(new KeyValuePair<int,int>(3, 3), "FirstItem");
myDictionary.Add(new KeyValuePair<int,int>(3, 3), "SecondItem");
// does the dictionary allow this?
Upvotes: 15
Views: 26535
Reputation: 112772
Simply use a long
as key and combine the two int
keys
public class IntIntDict<T> : Dictionary<long, T>
{
public void Add(int key1, int key2, T value)
{
Add((((long)key1) << 32) + key2, value);
}
//TODO: Overload other methods
}
UPDATE
C# 7 introduces the new ValueTuple Struct together with a simplified tuple syntax. These tuples come in handy for compound keys. You can declare your dictionary and add entries like this:
var myDictionary = new Dictionary<(int, int), string>();
myDictionary.Add((3, 3), "FirstItem");
myDictionary.Add((5, 5), "SecondItem");
and look up values like this
string result = myDictionary[(5, 5)];
or
if (myDictionary.TryGetValue((5, 7), out string result)) {
//TODO: use result
}
Upvotes: 15
Reputation: 45106
For performance Dictionary requires a key that generates unique GetHashValue.
KeyValuePair is a value type and not recommended for a key.
If you call the derived type's GetHashCode method, the return value is not likely to be suitable for use as a key in a hash table. Additionally, if the value of one or more of those fields changes, the return value might become unsuitable for use as a key in a hash table. In either case, consider writing your own implementation of the GetHashCode method that more closely represents the concept of a hash code for the type.
Point is also a value value type and also not recommended for a key.
Tuple also generates a lot of duplicate GetHashCode and is not a good key.
The optimal key is one that generates unique keys.
Consider UInt16 i and UInt j as the two keys.
How can they be combined and generate unique hash?
Easy combine them into and UInt32.
UInt32 natively generates a perfect hash.
The alogorithm for packing two UInt16 into UInt32 is
(i * (UInt16.MaxValue + 1)) + j;
but it is even faster with
(UInt32)i << 16 | j;
myDictionary = new Dictionary<UInt32, string>();
With a perfect hash the Dictionary is O(1).
With a poor hash the Dictionary becomes O(n).
Upvotes: 5
Reputation: 75326
Dictionary requires an equality implementation to determine whether keys are equal. You can specify an implementation of the
IEqualityComparer<T>
generic interface by using a constructor that accepts a comparer parameter; if you do not specify an implementation, the default generic equality comparerEqualityComparer<T>.Default
is used.
So, in your case because you don't specify IEqualityComparer<T>
, Default will be used.
The
EqualityComparer<T>.Default
checks whether type T implements theSystem.IEquatable<T>
interface and, if so, returns an EqualityComparer that uses that implementation. Otherwise, it returns anEqualityComparer<T>
that uses the overrides ofObject.Equals
andObject.GetHashCode
provided by T.
T is struct KeyValuePair
does not implement System.IEquatable<T>
, so it uses Equal
and GetHashCode
method of struct KeyValuePair
. These two methods use both Key
and Value
to check equal and generate hash code:
public override int GetHashCode()
{
return Key.GetHashCode() ^ Value.GetHashCode();
}
So, to sum up, in your sample Dictionary does not allow.
Upvotes: 0
Reputation: 4129
Using KeyValuePair as the key for your dictionary:
It will functionally work to use a KeyValuePair as the key in a dictionary; however, conceptually probably not the best choice for your application as it implies the Key-Value relationship between the two ints.
Instead as Mike suggests you should use Tuple for your key.
For the second question:
var myDictionary = new Dictionary<KeyValuePair<int,int>, string>();
myDictionary.Add(new KeyValuePair<int,int>(3, 3), "FirstItem");
myDictionary.Add(new KeyValuePair<int,int>(3, 3), "SecondItem");
// does the dictionary allow this?
The dictionary will not allow this, dictionaries themselves are sets of key-value pairs where the keys must be unique. If you want to be able to map mulitple values to the same key, one option would be to have the value be another collection:
var myDictionary = new Dictionary<KeyValuePair<int,int>, List<string>>();
but then you would still not be able to use myDictionary.Add as in your example. instead you would have to provide additional functionality to determine if they key were part of the dictionary and act accordingly:
public static class DictionaryHelper
{
public static void Add(this Dictionary<Tuple<int,int>, List<string>> dict,Tuple<int,int> key, string value)
{
if(dict.ContainsKey(key))
{
dict[key].Add(value);
}
else
{
dict.Add(key, new List<string>{value});
}
}
}
Upvotes: -1
Reputation: 1659
UPDATE: Based on your comments to other responders, the code below answers your questions. Yes, duplicates will generate an exception, System.ArgumentException
The code you listed will work, but will not accept duplicate KeyValuePairs. System.ArgumentException or similar will be thrown if you add KeyValuePair that already exists in the dictionary.
For example, this code
using System;
using System.Collections;
using System.Collections.Generic;
namespace test{
public class App {
public static void Main(string[] args) {
var myDictionary = new Dictionary<KeyValuePair<int,int>, string>();
Console.WriteLine("Adding 2 items...");
myDictionary.Add(new KeyValuePair<int,int>(3, 3), "FirstItem");
myDictionary.Add(new KeyValuePair<int,int>(5, 5), "SecondItem");
Console.WriteLine("Dictionary items: {0}", myDictionary.Count);
Console.WriteLine("Adding 2 duplicate items...");
myDictionary.Add(new KeyValuePair<int,int>(3, 3), "FirstItem");
myDictionary.Add(new KeyValuePair<int,int>(5, 5), "SecondItem");
Console.WriteLine("Dictionary items: {0}", myDictionary.Count);
}
}
}
gives the following
Microsoft (R) Visual C# Compiler version 4.0.30319.17626 for Microsoft (R) .NET Framework 4.5 Copyright (C) Microsoft Corporation. All rights reserved.
Adding 2 items... Dictionary items: 2 Adding 2 duplicate items...
Unhandled Exception: System.ArgumentException: An item with the same key has already been added. at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) at test.App.Main(String[] args)
Upvotes: 0
Reputation: 67948
Maybe you should consider using a Tuple
var myDictionary = new Dictionary<Tuple<int,int>, List<string>>();
myDictionary.Add(new Tuple<int,int>(3, 3), "FirstItem");
myDictionary.Add(new Tuple<int,int>(5, 5), "SecondItem");
According to MSDN documentation, a Tuple
objects Equals
method will use the values of the two Tuple
objects. This would result in one entry per Tuple
in the outer dictionary and allow you to store a listing of the values per key.
Upvotes: 30