6

I have the following data structure:

Dictionary<string, List<string>>

how can i make comparison to make sure that the values are equal between two different objects?

i.e:

    Dictionary<string, List<string>> expected = new Dictionary<string, List<string>>();
    expected.Add("CREDIT", new List<string> { "K   R   EH   D   IH   T" });
    expected.Add("CARD", new List<string> { "K   AA   R   D" });

    Dictionary<string, List<string>> actual;
    actual = target.GetTermDictionary();
    if (!Enumerable.SequenceEqual(expected, actual))
    {
        Assert.Fail();
    }

I don't think that SequanceEqual is good here..

Thanks

user829174
  • 6,132
  • 24
  • 75
  • 125

3 Answers3

5

First shortcut on quick trues and falses:

if(ReferenceEqual(actual, expected))
  return true;
if(actual == null || expected == null || actual.Count != expected.Count)
  return false;

This also handles null-checking so everthing else we do can't throw a null reference exception. You can skip all of this bar comparing the counts if you have it just after the creation as in your example, but should keep it in if you put this in a separate method, just in case.

We can't just call SequenceEqual on the two dictionaries, because we aren't guaranteed to get the keys back in the same order. With other types for the value we could do:

return actual.OrderBy(kvp => kvp.Key).SequenceEqual(expected.OrderBy(kvp => kvp.Key));

But this won't work because the two sequence-equal List<string> values won't be considered equal to the DefaultEqualityComparer<List<string>>.Equals() method that this will call into.

We could create an IEqualityComparer<KeyValuePair<string, List<string>>> if we were hell-bound on using SequenceEqual, but it's probably simpler to do the non-Linq approach, even though Linq is normally simpler and more concise (once you find the way to do it. Hence:

List<string> expectedVal;
foreach(KeyValuePair<string, List<string> kvp in actual)
{
  if(!expected.TryGetValue(kvp.key, out expectedVal) || kvp.Value.Count != expectedVal.Count || !kvp.Value.SequenceEquals(expectedVal))
    return false;

}
return true;

Variants can deal with different views of equality. For example, we may use kvp.Value.OrderBy(x => x).SequenceEquals(expectedVal.OrderBy(x => x)) if we wanted to consider two lists of the same items in different orders as equal.

In summary, the lot together:

if(ReferenceEqual(actual, expected))
  return true;
if(actual == null || expected == null || actual.Count != expected.Count)
  return false;
List<string> expectedVal;
foreach(KeyValuePair<string, List<string> kvp in actual)
{
  if(!expected.TryGetValue(kvp.key, out expectedVal) || kvp.Value.Count != expectedVal.Count || !kvp.Value.SequenceEquals(expectedVal))
    return false;

}
return true;

Edit: Just for fun, the way that uses SequenceEquals:

internal class KvpSLSEq : IEqualityComparer<KeyValuePair<string, List<string>>>
{
  public bool Equals(KeyValuePair<string, List<string>> x, KeyValuePair<string, List<string>> y)
  {
    return x.Key == y.Key && x.Value.Count == y.Value.Count && x.Value.SequenceEquals(y.Value);
  }
  public int GetHashCode(KeyValuePair<string, List<string>> obj)
  {
    //you could just throw NotImplementedException unless you'll reuse this elsewhere.
    int hash = obj.Key.GetHashCode;
    foreach(string val in obj.Value)
       hash = hash * 31 + (val == null ? 0 : val.GetHashCode());
  }
}

This done we can use the concise:

actual.OrderBy(kvp => kvp.Key).SequenceEqual(expected.OrderBy(kvp => kvp.Key), new KvpSLSEq());

But it's only really concise if KvpSLSEq will be used elsewhere as well.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
1

I don't think there's built in method, but you can compare the list values within each dictionary entry. Something like this:

// Check actual doesn't contain excess keys
if (actual.Keys.Count != expected.Keys.Count)
{
    return false;
}

foreach(var key in expected.Keys)
{
    if (!actual.ContainsKey(key) || !actual[key].SequenceEqual(expected[key]))
    {
       return false;
    }
}

return true;

Take a look here: Comparing 2 Dictionary<string, string> Instances and here: Is there a built-in method to compare collections in C#?

Community
  • 1
  • 1
Amittai Shapira
  • 3,749
  • 1
  • 30
  • 54
  • 1
    Also need to check that actual doesn't contain any excess keys. – Rawling Dec 14 '11 at 09:58
  • It will, and also short-cut on any case where either is shorter for a performance boost. I'd use `actual.TryGetValue` myself, so I can roll the contains-key check and obtaining the list into one. – Jon Hanna Dec 14 '11 at 10:29
0

If you're using NUnit you could use CollectionAssert.AreEquivalent, see this question: NUnit: Dictionary Assert

Community
  • 1
  • 1
Amittai Shapira
  • 3,749
  • 1
  • 30
  • 54