Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Case Insensitive Dictionary with Tuple Key

I have a dictionary where the key is a Tuple where the first item is a Date and the second item is a string. I would like the dictionary to be case insensitive.

I know that if the key was just a string I could pass StringComparer.OrdinalIgnoreCase as a parameter when declaring the dictionary, but this does not seem to work when the key is a Tuple.

Is there some way to specify the StringComparer to use on the second item of the Tuple?

Thanks

like image 588
Eric Avatar asked May 07 '13 21:05

Eric


2 Answers

Use this overload of the Dictionary constructor, which allows you to specify a custom comparer for the keys. You would accompany this with creating a class that implements

IEqualityComparer<Tuple<string, DateTime>>

Which might look like this:

class CustomEqualityComparer : IEqualityComparer<Tuple<string, DateTime>>
{

    public bool Equals(Tuple<string, DateTime> lhs, Tuple<string, DateTime> rhs)
    {
        return
          StringComparer.CurrentCultureIgnoreCase.Equals(lhs.Item1, rhs.Item1)
       && lhs.Item2 == rhs.Item2;
    }


    public int GetHashCode(Tuple<string, DateTime> tuple)
    {
        return StringComparer.CurrentCultureIgnoreCase.GetHashCode(tuple.Item1)
             ^ tuple.Item2.GetHashCode();
    }
}

There are no argument checks here, so please don't treat this as production code. Also, care needs to be taken so that the Equals and GetHashCode implementations satisfy the all-important condition that if two tuples compare equal, they must have the same hash code. When dealing with custom text comparisons it is easy to introduce bugs if not extra careful: for example, using ToLowerInvariant instead of ToLower above would be a bug (albeit one that might not surface for some time).

like image 104
Jon Avatar answered Sep 21 '22 07:09

Jon


I needed this in a Dictionary<Tuple<>> wrapper, so I used @Jon 's code to create a generic version

public class TupleEqualityComparer<T1, T2> : IEqualityComparer<Tuple<T1, T2>>
{
    private IEqualityComparer<T1> comparer1;
    private IEqualityComparer<T2> comparer2;

    public TupleEqualityComparer(IEqualityComparer<T1> comparer1, IEqualityComparer<T2> comparer2)
    {
        this.comparer1 = comparer1 ?? EqualityComparer<T1>.Default;
        this.comparer2 = comparer2 ?? EqualityComparer<T2>.Default;
    }

    public bool Equals(Tuple<T1, T2> lhs, Tuple<T1, T2> rhs)
    {
        return comparer1.Equals(lhs.Item1, rhs.Item1) && comparer2.Equals(lhs.Item2, rhs.Item2);
    }

    public int GetHashCode(Tuple<T1, T2> tuple)
    {
        return comparer1.GetHashCode(tuple.Item1) ^ comparer2.GetHashCode(tuple.Item2);
    }

}

public class Dictionary<TKey1, TKey2, TValue> : Dictionary<Tuple<TKey1, TKey2>, TValue>()
{
    public Dictionary() : base() { }
    public Dictionary(IEqualityComparer<TKey1> comparer1, IEqualityComparer<TKey2> comparer2) : base(new TupleEqualityComparer<TKey1, Tkey2>(comparer1, comparer2) { }

    public TValue this[TKey1 key1, TKey2 key2]
    {
        get { return base[Tuple.Create(key1, key2)]; }
    }

    public void Add(TKey1 key1, TKey2 key2, TValue value)
    {
        base.Add(Tuple.Create(key1, key2), value);
    }

    public bool ContainsKey(TKey1 key1, TKey2 key2)
    {
        return base.ContainsKey(Tuple.Create(key1, key2));
    }

    public bool TryGetValue(TKey1 key1, TKey2 key2, out TValue value)
    {
        return base.TryGetValue(Tuple.Create(key1, key2), out value);
    }
}

Usage

var dict = new Dictionary<string, DateTime, int>(
    StringComparer.OrdinalIgnoreCase, null);
dict.Add("value1", DateTime.Now, 123);
Assert.IsTrue(dict.ContainsKey("VALUe1"));
like image 41
Jürgen Steinblock Avatar answered Sep 18 '22 07:09

Jürgen Steinblock