views:

318

answers:

6

Lets say I have an object that has stringProp1, stringProp2. I wish to store each combination of stringProp1, stringProp2 in a Dictionary. Initially I was storing the key as key = stringProp1+stringProp2 but this can actually cause a bug depending on the 2 values. Is the best solution for this problem to create a custom dictionary class or is there a better way using built-in .NET classes?

A: 

You can put a delimiter in there, if there is a character that won't turn up in either string.

eg

stringProp1 + "|" + stringProp2

If there isn't then I'd recommend Dictionary<string, Dictionary<string, MyValueType>> as in

var dictionary = new Dictionary<string, Dictionary<string, MyValueType>>();
// .... Do stuff
if (!dictionary.ContainsKey(stringProp1))
    dictionary.Add(stringProp1, new Dictionary<string, MyValueType>());
dictionary[stringProp1][stringProp2] = myValue;
pdr
This won't work. How about the pair "blah|","blah" and the pair "blah","|blah". You need to be careful.
Keltex
@Keltex Like I said: "if there is a character that won't turn up in either string". Have now added an alternate solution.
pdr
A: 

You can use the MD5 algorithm to produce a value for each string and then sum up the two values. The result is the key.
.NET provide the class MD5CryptoServiceProvider in the System.Security.Cryptography namespace. That class contains the method ComputeHash to compute the hash value.

Maurizio Reginelli
This is not reliable. If the strings are random, the probability 1/2^128 is that the md5 hashes will collide. And this is going to be rather slow.
Vlad
A: 

Why not just use a struct with 2 strings as a key? This would be the simplest.

Vlad
+1  A: 

In .NET 4, you can use a System.Tuple as a key.

var dict = new Dictionary<Tuple<string,string>, int>();
dict.Add(Tuple.Create("foo","bar"), 1);
Joel Mueller
You could also reference FSharp.Core if you wanted to use this in earlier versions of .NET
Michael Greene
You can also getaway with using keyvaluepair in .Net 3.5 as new dictionary<keyvaluepair<string,string>>. But looks ugly.
Biswanath
+1  A: 

It does not matter what data structure you use as the key as long as the key holds the necessary information for a comparer to properly compare/hash.

You can even use your objects as keys in the dictionary and compare on any field you like with an appropriate EqualityComparer implementation. This one compares on two string properties using ordinal comparison:

class MyObject
{
    public string StringProp1 { get; set; }
    public string StringProp2 { get; set; }
    public MyObject(string prop1, string prop2)
    {
        StringProp1 = prop1;
        StringProp2 = prop2;
    }
}

class MyObjectComparerS1S2 : EqualityComparer<MyObject>
{
    //Change this if you need e.g. case insensitivity or 
    //culture-specific comparisons
    static StringComparer comparer = StringComparer.Ordinal;

    public override bool Equals(MyObject x, MyObject y)
    {
        return 
            comparer.Equals(x.StringProp1, y.StringProp1) &&
            comparer.Equals(x.StringProp2, y.StringProp2);
    }

    public override int GetHashCode(MyObject obj)
    {
        //Uncomment this if running in a checked context
        //Copycat of Jon Skeet's string hash combining
        //unchecked
        //{
            return 
                (527 + comparer.GetHashCode(obj.StringProp1)) * 31 +
                comparer.GetHashCode(obj.StringProp2);
        //}
    }

    public static readonly MyObjectComparerS1S2 Instance = 
        new MyObjectComparerS1S2();

}

static void Main(string[] args)
{
    Dictionary<MyObject, MyObject> dict = 
        new Dictionary<MyObject, MyObject>(MyObjectComparerS1S2.Instance);
    MyObject obj = new MyObject("apple", "plum");
    dict.Add(obj, obj);
    MyObject search = new MyObject("apple", "plum");
    MyObject result = dict[search];
    Console.WriteLine("{0}:{1}", result.StringProp1, result.StringProp2);
}

You can search for an object by creating a dummy one, filling in the string keys and using the dummy as the key for the lookup. If you do not like this idea, or it is not feasible, just do as @Vlad said and extract the keys in a struct or class. In this case, modify the comparer to derive from EqualityComparer<MyKeyStructOrClass>.

Note that I've used Jon Skeet's method for combining string hashes. It might be better than the XOR method found on MSDN. If you feel that it is steel inadequate, feel free to treat the strings with another hash implementation - Hsieh, Murmur, Bob Jenkin's, or whatever you believe in. Here is a nice page about hash functions that actually has some C# code as well.

andras
A: 

Depending on how you populate the dictionary and what you do with it, it's possible to use an anonymous type as the key. For example if you have a class Person:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

And if you had a sequence of these, an IEnumerable<Person>, and wanted to create a dictionary that maps names to ages, you can write:

var personDictionary = people.ToDictionary(p => new { p.FirstName, p.LastName });

This gives you a dictionary with the first name and last name as the key, and stores the entire Person as the value. You can later look up a key with:

personDictionary.TryGetValue(new { FirstName = "John", LastName = "Smith" },
    out person);

This won't help help you much if you're trying to pass the dictionary between different classes or even methods, it becomes hard to manage, but for doing some quick data processing in a single method, it works great. In fact, it's quite common to use anonymous classes as the key for the GroupBy extension method or the group by query comprehension syntax.

Aaronaught