user356178
user356178

Reputation:

Dictionary where the key is a pair of integers

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

Answers (6)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

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

paparazzo
paparazzo

Reputation: 45106

For performance Dictionary requires a key that generates unique GetHashValue.

KeyValuePair is a value type and not recommended for a key.

ValueType.GetHashCode

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

cuongle
cuongle

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 comparer EqualityComparer<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 the System.IEquatable<T> interface and, if so, returns an EqualityComparer that uses that implementation. Otherwise, it returns an EqualityComparer<T> that uses the overrides of Object.Equals and Object.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

Mr.Mindor
Mr.Mindor

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

J Burnett
J Burnett

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

Mike Perrenoud
Mike Perrenoud

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

Related Questions