Dictionary of string array lookup - c#

I am working with a list of dictionaries containing string arrays. The dictionaries are defined/filled via a loop over a DataTable. In the following code test evaluates to false (twice), can somebody tell me why?
List<Dictionary<string[], int>> mydix = new List<Dictionary<string[], int>>();
mydix.Add(new Dictionary<string[], int>());
mydix.Add(new Dictionary<string[], int>());
mydix.Add(new Dictionary<string[], int>());
string[] s = {"tree"};
mydix[1].Add(s, 1);
bool test = mydix[1].ContainsKey(s); // This evaluates to true, which I understand
var entry= mydix[1][s]; // This is 1
DataTable dt=new DataTable();
dt.Columns.Add("test");
dt.Rows.Add(new string[] {"key"});
mydix[2].Add(dt.Rows[0].ItemArray.Select(x => x.ToString()).ToArray(), 2);
test = mydix[2].ContainsKey(new string[] { "key" }); // Why does this evaluate to false?
// Here is an example with an array with two elements
DataTable dt2 = new DataTable();
dt2.Columns.Add("test");
dt2.Columns.Add("test2");
string[] t={"tree1","tree2"};
dt2.Rows.Add(t);
mydix[0].Add(dt2.Rows[0].ItemArray.Select(x => x.ToString()).ToArray(), 3);
test = mydix[0].ContainsKey(t); // Why does this evaluate to false?

The problem is that the string array you are using as the key to the dictionary does object comparison, not content comparison.
In order to support this type of data as a key, the easiest solution is to use an IEqualityComparer.
First, create the comparer (this is a sample; yours will need additional sanity checking and logic):
private class ArrayComparer : IEqualityComparer<string[]>
{
public bool Equals(string[] item1, string[] item2)
{
if (item1[0] == item2[0])
{
return true;
}
else
{
return false;
}
}
public int GetHashCode(string[] item)
{
return item[0].GetHashCode();
}
Then, change the instantiation of your dictionaries to use this new comparer:
mydix.Add(new Dictionary<string[], int>(new ArrayComparer()));
mydix.Add(new Dictionary<string[], int>(new ArrayComparer()));
mydix.Add(new Dictionary<string[], int>(new ArrayComparer()));
Once you have done this, both tests will return true.

Hopefully someone will correct me if I'm wrong, but it's my understanding that when you call ContainsKey, the Dictionary has a private method (exploring in dotPeek), which runs to decide wether the objects you're comparing are equal or not.
Depending on what type you're using for the key, a different equality comparison will occur, based on various implementations of IEqualityComparer, this way the most appropriate comparison can be run, based on the types you wish to compare.
You're using string arrays as the keys, so you're essentially checking the equality of the array objects themselves, not their contents. So it's entirely correct that your ContainsKey is returning false, you aren't asking your Dictionary if it contains the same array as a key, you're asking it if it contains a different array, which happens to contain the same contents.
The IEqualityComparer GetHashCode method in this case (an array), will return a hash based on the reference of the object, not the contents.
If you wanted this behaviour, the magic Mr Skeet has written a custom IEqualityComparer<T> for arrays in this post:
Compare Objects?

Related

Fastest way to check a Dictionary<> is equal to another [duplicate]

Assuming dictionary keys and values have their equals and hash methods implemented correctly, what is the most succinct and efficient way to test for equality of two dictionaries?
In this context, two dictionaries are said to be equal if they contain the same set of keys (order not important), and for every such key, they agree on the value.
Here are some ways I came up with (there are probably many more):
public bool Compare1<TKey, TValue>(
Dictionary<TKey, TValue> dic1,
Dictionary<TKey,TValue> dic2)
{
return dic1.OrderBy(x => x.Key).
SequenceEqual(dic2.OrderBy(x => x.Key));
}
public bool Compare2<TKey, TValue>(
Dictionary<TKey, TValue> dic1,
Dictionary<TKey, TValue> dic2)
{
return (dic1.Count == dic2.Count &&
dic1.Intersect(dic2).Count().
Equals(dic1.Count));
}
public bool Compare3<TKey, TValue>(
Dictionary<TKey, TValue> dic1,
Dictionary<TKey, TValue> dic2)
{
return (dic1.Intersect(dic2).Count().
Equals(dic1.Union(dic2).Count()));
}
dic1.Count == dic2.Count && !dic1.Except(dic2).Any();
It really depends on what you mean by equality.
This method will test that two dictionaries contain the same keys with the same values (assuming that both dictionaries use the same IEqualityComparer<TKey> implementation).
public bool CompareX<TKey, TValue>(
Dictionary<TKey, TValue> dict1, Dictionary<TKey, TValue> dict2)
{
if (dict1 == dict2) return true;
if ((dict1 == null) || (dict2 == null)) return false;
if (dict1.Count != dict2.Count) return false;
var valueComparer = EqualityComparer<TValue>.Default;
foreach (var kvp in dict1)
{
TValue value2;
if (!dict2.TryGetValue(kvp.Key, out value2)) return false;
if (!valueComparer.Equals(kvp.Value, value2)) return false;
}
return true;
}
You could use linq for the key/value comparisons:
public bool Compare<TKey, TValue>(Dictionary<TKey, TValue> dict1, Dictionary<TKey, TValue dict2)
{
IEqualityComparer<TValue> valueComparer = EqualityComparer<TValue>.Default;
return dict1.Count == dict2.Count &&
dict1.Keys.All(key => dict2.ContainsKey(key) && valueComparer.Equals(dict1[key], dict2[key]));
}
In addition to #Nick Jones answer, you're going to need to implement gethashcode in the same, order agnostic way. I would suggest something like this:
public override int GetHashCode()
{
var hash = 13;
var orderedKVPList = this.DictProp.OrderBy(kvp => kvp.Key);
foreach (var kvp in orderedKVPList)
{
hash = (hash * 7) + kvp.Key.GetHashCode();
hash = (hash * 7) + kvp.Value.GetHashCode();
}
return hash;
}
I thought the accepted answer would be correct based on what I was reading in the smarthelp for the Except method: "Produces the set difference of two sequences by using the default equality comparer to compare values." But I discovered it is not a good answer.
Consider this code:
Dictionary<string, List<string>> oldDict = new Dictionary<string, List<string>>()
{{"001A", new List<string> {"John", "Doe"}},
{"002B", new List<string> {"Frank", "Abignale"}},
{"003C", new List<string> {"Doe", "Jane"}}};
Dictionary<string, List<string>> newDict = new Dictionary<string, List<string>>()
{{"001A", new List<string> {"John", "Doe"}},
{"002B", new List<string> {"Frank", "Abignale"}},
{"003C", new List<string> {"Doe", "Jane"}}};
bool equal = oldDict.Count.Equals(newDict.Count) && !oldDict.Except(newDict).Any();
Console.WriteLine(string.Format("oldDict {0} newDict", equal?"equals":"does not equal"));
equal = oldDict.SequenceEqual(newDict);
Console.WriteLine(string.Format("oldDict {0} newDict", equal ? "equals" : "does not equal"));
Console.WriteLine(string.Format("[{0}]", string.Join(", ",
oldDict.Except(newDict).Select(k =>
string.Format("{0}=[{1}]", k.Key, string.Join(", ", k.Value))))));
This results in the following:
oldDict does not equal newDict
oldDict does not equal newDict
[001A=[John, Doe], 002B=[Frank, Abignale], 003C=[Doe, Jane]]
As you can see, both "oldDict" and "newDict" are setup exactly the same. And neither the suggested solution nor a call to SequenceEqual works properly. I wonder if it is a result of the Except using lazy loading or the way the comparer is setup for the Dictionary. (Although, looking at the structure and reference explanations suggest it should.)
Here's the solution I came up with. Note that the rule I used is as follows: two dictionaries are equal if both contain the same keys and the values for each key match. Both keys and values must be in the same sequential order. And my solution may not be the most efficient, since it relies on iterating through the entire set of keys.
private static bool DictionaryEqual(
Dictionary<string, List<string>> oldDict,
Dictionary<string, List<string>> newDict)
{
// Simple check, are the counts the same?
if (!oldDict.Count.Equals(newDict.Count)) return false;
// Verify the keys
if (!oldDict.Keys.SequenceEqual(newDict.Keys)) return false;
// Verify the values for each key
foreach (string key in oldDict.Keys)
if (!oldDict[key].SequenceEqual(newDict[key]))
return false;
return true;
}
Also see how the results change if:
Key order is not the same. (returns false)
newDict = new Dictionary<string, List<string>>()
{{"001A", new List<string> {"John", "Doe"}},
{"003C", new List<string> {"Doe", "Jane"}},
{"002B", new List<string> {"Frank", "Abignale"}}};
and
Key order matches, but Value does not match (returns false)
newDict = new Dictionary<string, List<string>>()
{{"001A", new List<string> {"John", "Doe"}},
{"002B", new List<string> {"Frank", "Abignale"}},
{"003C", new List<string> {"Jane", "Doe"}}};
If sequence order does not matter, the function can be changed to the following, but there is likely a performance hit.
private static bool DictionaryEqual_NoSort(
Dictionary<string, List<string>> oldDict,
Dictionary<string, List<string>> newDict)
{
// Simple check, are the counts the same?
if (!oldDict.Count.Equals(newDict.Count)) return false;
// iterate through all the keys in oldDict and
// verify whether the key exists in the newDict
foreach(string key in oldDict.Keys)
{
if (newDict.Keys.Contains(key))
{
// iterate through each value for the current key in oldDict and
// verify whether or not it exists for the current key in the newDict
foreach(string value in oldDict[key])
if (!newDict[key].Contains(value)) return false;
}
else { return false; }
}
return true;
}
Check out if the DictionaryEqual_NoSort using the following for newDict (DictionaryEquals_NoSort returns true):
newDict = new Dictionary<string, List<string>>()
{{"001A", new List<string> {"John", "Doe"}},
{"003C", new List<string> {"Jane", "Doe"}},
{"002B", new List<string> {"Frank", "Abignale"}}};
Simple O(N) time, O(1) space solution with null checks
The other solutions using Set operations Intersect, Union or Except are good but these require additional O(N) memory for the final resultant dictionary which is just used for counting elements.
Instead, use Linq Enumerable.All to check this. First validate the count of two dictionaries, next, iterate over all D1's Key Value pairs and check if they are equal to D2's Key Value pairs. Note: Linq does allocate memory for a collection iterator but it's invariant of the collection size - O(1) space. Amortized complexity for TryGetValue is O(1).
// KV is KeyValue pair
var areDictsEqual = d1.Count == d2.Count && d1.All(
(d1KV) => d2.TryGetValue(d1KV.Key, out var d2Value) && (
d1KV.Value == d2Value ||
d1KV.Value?.Equals(d2Value) == true)
);
Why d1KV.Value == d2Value? - this is to check if object references are equal. Also, if both are null, d1KV.Value == d2Value will evaluate to true.
Why d1Kv.Value?.Equals(d2Value) == true? - Value?. is for null safe check and .Equals is meant to test equality of two objects based on your object's Equals and HashCode methods.
You can tweak the equality checks as you like. I'm assuming the Dict Values are nullable type to make the solution more generic (eg: string, int?, float?). If it's non-nullable type, the checks could be simplified.
Final note: In C# dictionary, the Keys can't be null. But Values can be null. Docs for reference.
#Allen's answer:
bool equals = a.Intersect(b).Count() == a.Union(b).Count()
is about arrays but as far as IEnumerable<T> methods are used, it can be used for Dictionary<K,V> too.
If two dictionaries contain the same keys, but in different order, should they be considered equal? If not, then the dictionaries should be compared by running enumerators through both simultaneously. This will probably be faster than enumerating through one dictionary and looking up each element in the other. If you have advance knowledge that equal dictionaries will have their elements in the same order, such a double-enumeration is probably the way to go.

BinarySearch in two dimensional list

I have dimensional list:
List<List<string>> index_en_bg = new List<List<string>>();
index_en_bg.Add(new List<string>() { word1, translation1 });
index_en_bg.Add(new List<string>() { word2, translation2 });
index_en_bg.Add(new List<string>() { word3, translation3 });
I would do binary search by the first column (words), something like this:
int row = index_en_bg.BinarySearch(searchingstr);
but it works only for a one-dimensional list. How would I extend it to work for two-dimensional lists in my case? I don't want to use Dictionary class.
In this case you need to provide your own customer IComparer-implementing comparator
public class Comparer: IComparer<IList<string>>
{
public int Compare(IList<string> x, IList<string> y)
{
// base the comparison result on the first element in the respective lists
// eg basically
return x[0].CompareTo(y[0]);
}
And you'll call it like this, offering a List where only the field you're searching is filled in.
int row = index_en_bg.BinarySearch(new List<string>() {searchingstr},new Comparer());
Well as far as I understand you should use Dictionary<K,V> instead, this way:
// 1 creating the dictionary
var dic = new Dictionary<string, string>();
dic["word1"] = "translation1";
dic["word2"] = "translation2";
dic["word3"] = "translation3";
// 2 finding a translation
var trans = dic["word1"];
And Dictionary<K,V> is really performant.
But if you insist on using BinarySearch you can implement IComparer<List<string>> and pass it to the function.
As you always search using the first item of the list you could use dictionary too.
var d = Dictionary<string, List<string>>();
as answered previously it's preforms much better than List.

SortedDictionary duplicate keys?

Okai, i have the following method:
public void Insert(SortedDictionary<byte[], uint> recs)
{
SortedDictionary<byte[], uint> records = new SortedDictionary(recs, myComparer);
}
What I am hoping to achieve is to sort the records in "recs" with a new rule specified by "myComparer" which implements IComparer. It pretty much does so, but I get hit by an exception with the following message:
An entry with the same key already
exists.
I am wondering how this is possible since "recs" is already a dictionary with about 130k keys.
public int Compare(byte[] a, byte[] b)
{
return Inhouse.ByteConverter.ToString(a).CompareTo(
Inhouse.ByteConverter.ToString(b));
}
(it's just a snipette..)
If "recs" has a different comparer than the one you inject into records you may get duplicates; that is if "recs" compares by object reference and myComparer compares the actual bytes, you will have collisions.
Check the comparer code:
Every key in a SortedDictionary(Of
TKey, TValue) must be unique according
to the specified comparer; therefore, every key in the source dictionary must also be
unique according to the specified comparer.
with your new comparer, 2 different keys with normal byte[] comparaison may become equal.
It's what msdn says...
You must be using the same Dictionary object in the calling method. So I imagine your code is something like this:
SortedDictionary<byte[], uint> dic = new SortedDictionary<byte[], uint>();
foreach (var thing in things)
{
dic.Clear();
Populate(dic);
Insert(dic);
}
Where it should be like this:
SortedDictionary<byte[], uint> dic = new SortedDictionary<byte[], uint>();
foreach (var thing in things)
{
dic = new SortedDictionary<byte[], uint>();
Populate(dic);
Insert(dic);
}
Can you post the code that is calling your Insert method?

how to add an associative index to an array. c#

i have an array of custom objects. i'd like to be able to reference this array by a particular data member, for instance myArrary["Item1"]
"Item1" is actually the value stored in the Name property of this custom type and I can write a predicate to mark the appropriate array item. However I am unclear as to how to let the array know i'd like to use this predicate to find the array item.
I'd like to just use a dictionary or hashtable or NameValuePair for this array, and get around this whole problem but it's generated and it must remain as CustomObj[]. i'm also trying to avoid loading a dictionary from this array as it's going to happen many times and there could be many objects in it.
For clarification
myArray[5] = new CustomObj() // easy!
myArray["ItemName"] = new CustomObj(); // how to do this?
Can the above be done? I'm really just looking for something similar to how DataRow.Columns["MyColumnName"] works
Thanks for the advice.
What you really want is an OrderedDictionary. The version that .NET provides in System.Collections.Specialized is not generic - however there is a generic version on CodeProject that you could use. Internally, this is really just a hashtable married to a list ... but it is exposed in a uniform manner.
If you really want to avoid using a dictionary - you're going to have to live with O(n) lookup performance for an item by key. In that case, stick with an array or list and just use the LINQ Where() method to lookup a value. You can use either First() or Single() depending on whether duplicate entries are expected.
var myArrayOfCustom = ...
var item = myArrayOfCustom.Where( x => x.Name = "yourSearchValue" ).First();
It's easy enough to wrap this functionality into a class so that external consumers are not burdened by this knowledge, and can use simple indexers to access the data. You could then add features like memoization if you expect the same values are going to be accessed frequently. In this way you could amortize the cost of building the underlying lookup dictionary over multiple accesses.
If you do not want to use "Dictionary", then you should create class "myArrary" with data mass storage functionality and add indexers of type "int" for index access and of type "string" for associative access.
public CustomObj this [string index]
{
get
{
return data[searchIdxByName(index)];
}
set
{
data[searchIdxByName(index)] = value;
}
}
First link in google for indexers is: http://www.csharphelp.com/2006/04/c-indexers/
you could use a dictionary for this, although it might not be the best solution in the world this is the first i came up with.
Dictionary<string, int> d = new Dictionary<string, int>();
d.Add("cat", 2);
d.Add("dog", 1);
d.Add("llama", 0);
d.Add("iguana", -1);
the ints could be objects, what you like :)
http://dotnetperls.com/dictionary-keys
Perhaps OrderedDictionary is what you're looking for.
you can use HashTable ;
System.Collections.Hashtable o_Hash_Table = new Hashtable();
o_Hash_Table.Add("Key", "Value");
There is a class in the System.Collections namespace called Dictionary<K,V> that you should use.
var d = new Dictionary<string, MyObj>();
MyObj o = d["a string variable"];
Another way would be to code two methods/a property:
public MyObj this[string index]
{
get
{
foreach (var o in My_Enumerable)
{
if (o.Name == index)
{
return o;
}
}
}
set
{
foreach (var o in My_Enumerable)
{
if (o.Name == index)
{
var i = My_Enumerable.IndexOf(0);
My_Enumerable.Remove(0);
My_Enumerable.Add(value);
}
}
}
}
I hope it helps!
It depends on the collection, some collections allow accessing by name and some don't. Accessing with strings is only meaningful when the collection has data stored, the column collection identifies columns by their name, thus allowing you to select a column by its name. In a normal array this would not work because items are only identified by their index number.
My best recommendation, if you can't change it to use a dictionary, is to either use a Linq expression:
var item1 = myArray.Where(x => x.Name == "Item1").FirstOrDefault();
or, make an extension method that uses a linq expression:
public static class CustomObjExtensions
{
public static CustomObj Get(this CustomObj[] Array, string Name)
{
Array.Where(x => x.Name == Name).FirstOrDefault();
}
}
then in your app:
var item2 = myArray.Get("Item2");
Note however that performance wouldn't be as good as using a dictionary, since behind the scenes .NET will just loop through the list until it finds a match, so if your list isn't going to change frequently, then you could just make a Dictionary instead.
I have two ideas:
1) I'm not sure you're aware but you can copy dictionary objects to an array like so:
Dictionary dict = new Dictionary();
dict.Add("tesT",40);
int[] myints = new int[dict.Count];
dict.Values.CopyTo(myints, 0);
This might allow you to use a Dictionary for everything while still keeping the output as an array.
2) You could also actually create a DataTable programmatically if that's the exact functionality you want:
DataTable dt = new DataTable();
DataColumn dc1 = new DataColumn("ID", typeof(int));
DataColumn dc2 = new DataColumn("Name", typeof(string));
dt.Columns.Add(dc1);
dt.Columns.Add(dc2);
DataRow row = dt.NewRow();
row["ID"] = 100;
row["Name"] = "Test";
dt.Rows.Add(row);
You could also create this outside of the method so you don't have to make the table over again every time.

Is there a built-in method to compare collections?

I would like to compare the contents of a couple of collections in my Equals method. I have a Dictionary and an IList. Is there a built-in method to do this?
Edited:
I want to compare two Dictionaries and two ILists, so I think what equality means is clear - if the two dictionaries contain the same keys mapped to the same values, then they're equal.
Enumerable.SequenceEqual
Determines whether two sequences are equal by comparing their elements by using a specified IEqualityComparer(T).
You can't directly compare the list & the dictionary, but you could compare the list of values from the Dictionary with the list
As others have suggested and have noted, SequenceEqual is order-sensitive. To solve that, you can sort the dictionary by key (which is unique, and thus the sort is always stable) and then use SequenceEqual. The following expression checks if two dictionaries are equal regardless of their internal order:
dictionary1.OrderBy(kvp => kvp.Key).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key))
EDIT: As pointed out by Jeppe Stig Nielsen, some object have an IComparer<T> that is incompatible with their IEqualityComparer<T>, yielding incorrect results. When using keys with such an object, you must specify a correct IComparer<T> for those keys. For example, with string keys (which exhibit this issue), you must do the following in order to get correct results:
dictionary1.OrderBy(kvp => kvp.Key, StringComparer.Ordinal).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
In addition to the mentioned SequenceEqual, which
is true if two lists are of equal length and their corresponding
elements compare equal according to a comparer
(which may be the default comparer, i.e. an overriden Equals())
it is worth mentioning that in .Net4 there is SetEquals on ISet objects,
which
ignores the order of elements and any duplicate elements.
So if you want to have a list of objects, but they don't need to be in a specific order, consider that an ISet (like a HashSet) may be the right choice.
Take a look at the Enumerable.SequenceEqual method
var dictionary = new Dictionary<int, string>() {{1, "a"}, {2, "b"}};
var intList = new List<int> {1, 2};
var stringList = new List<string> {"a", "b"};
var test1 = dictionary.Keys.SequenceEqual(intList);
var test2 = dictionary.Values.SequenceEqual(stringList);
This is not directly answering your questions, but both the MS' TestTools and NUnit provide
CollectionAssert.AreEquivalent
which does pretty much what you want.
I didn't know about Enumerable.SequenceEqual method (you learn something every day....), but I was going to suggest using an extension method; something like this:
public static bool IsEqual(this List<int> InternalList, List<int> ExternalList)
{
if (InternalList.Count != ExternalList.Count)
{
return false;
}
else
{
for (int i = 0; i < InternalList.Count; i++)
{
if (InternalList[i] != ExternalList[i])
return false;
}
}
return true;
}
Interestingly enough, after taking 2 seconds to read about SequenceEqual, it looks like Microsoft has built the function I described for you.
.NET Lacks any powerful tools for comparing collections. I've developed a simple solution you can find at the link below:
http://robertbouillon.com/2010/04/29/comparing-collections-in-net/
This will perform an equality comparison regardless of order:
var list1 = new[] { "Bill", "Bob", "Sally" };
var list2 = new[] { "Bob", "Bill", "Sally" };
bool isequal = list1.Compare(list2).IsSame;
This will check to see if items were added / removed:
var list1 = new[] { "Billy", "Bob" };
var list2 = new[] { "Bob", "Sally" };
var diff = list1.Compare(list2);
var onlyinlist1 = diff.Removed; //Billy
var onlyinlist2 = diff.Added; //Sally
var inbothlists = diff.Equal; //Bob
This will see what items in the dictionary changed:
var original = new Dictionary<int, string>() { { 1, "a" }, { 2, "b" } };
var changed = new Dictionary<int, string>() { { 1, "aaa" }, { 2, "b" } };
var diff = original.Compare(changed, (x, y) => x.Value == y.Value, (x, y) => x.Value == y.Value);
foreach (var item in diff.Different)
Console.Write("{0} changed to {1}", item.Key.Value, item.Value.Value);
//Will output: a changed to aaa
To compare collections you can also use LINQ. Enumerable.Intersect returns all pairs that are equal. You can comparse two dictionaries like this:
(dict1.Count == dict2.Count) && dict1.Intersect(dict2).Count() == dict1.Count
The first comparison is needed because dict2 can contain all the keys from dict1 and more.
You can also use think of variations using Enumerable.Except and Enumerable.Union that lead to similar results. But can be used to determine the exact differences between sets.
How about this example:
static void Main()
{
// Create a dictionary and add several elements to it.
var dict = new Dictionary<string, int>();
dict.Add("cat", 2);
dict.Add("dog", 3);
dict.Add("x", 4);
// Create another dictionary.
var dict2 = new Dictionary<string, int>();
dict2.Add("cat", 2);
dict2.Add("dog", 3);
dict2.Add("x", 4);
// Test for equality.
bool equal = false;
if (dict.Count == dict2.Count) // Require equal count.
{
equal = true;
foreach (var pair in dict)
{
int value;
if (dict2.TryGetValue(pair.Key, out value))
{
// Require value be equal.
if (value != pair.Value)
{
equal = false;
break;
}
}
else
{
// Require key be present.
equal = false;
break;
}
}
}
Console.WriteLine(equal);
}
Courtesy : https://www.dotnetperls.com/dictionary-equals
For ordered collections (List, Array) use SequenceEqual
for HashSet use SetEquals
for Dictionary you can do:
namespace System.Collections.Generic {
public static class ExtensionMethods {
public static bool DictionaryEquals<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> d1, IReadOnlyDictionary<TKey, TValue> d2) {
if (object.ReferenceEquals(d1, d2)) return true;
if (d2 is null || d1.Count != d2.Count) return false;
foreach (var (d1key, d1value) in d1) {
if (!d2.TryGetValue(d1key, out TValue d2value)) return false;
if (!d1value.Equals(d2value)) return false;
}
return true;
}
}
}
(A more optimized solution will use sorting but that will require IComparable<TValue>)
No, because the framework doesn't know how to compare the contents of your lists.
Have a look at this:
http://blogs.msdn.com/abhinaba/archive/2005/10/11/479537.aspx
public bool CompareStringLists(List<string> list1, List<string> list2)
{
if (list1.Count != list2.Count) return false;
foreach(string item in list1)
{
if (!list2.Contains(item)) return false;
}
return true;
}
There wasn't, isn't and might not be, at least I would believe so. The reason behind is collection equality is probably an user defined behavior.
Elements in collections are not supposed to be in a particular order though they do have an ordering naturally, it's not what the comparing algorithms should rely on. Say you have two collections of:
{1, 2, 3, 4}
{4, 3, 2, 1}
Are they equal or not? You must know but I don't know what's your point of view.
Collections are conceptually unordered by default, until the algorithms provide the sorting rules. The same thing SQL server will bring to your attention is when you trying to do pagination, it requires you to provide sorting rules:
https://learn.microsoft.com/en-US/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-2017
Yet another two collections:
{1, 2, 3, 4}
{1, 1, 1, 2, 2, 3, 4}
Again, are they equal or not? You tell me ..
Element repeatability of a collection plays its role in different scenarios and some collections like Dictionary<TKey, TValue> don't even allow repeated elements.
I believe these kinds of equality are application defined and the framework therefore did not provide all of the possible implementations.
Well, in general cases Enumerable.SequenceEqual is good enough but it returns false in the following case:
var a = new Dictionary<String, int> { { "2", 2 }, { "1", 1 }, };
var b = new Dictionary<String, int> { { "1", 1 }, { "2", 2 }, };
Debug.Print("{0}", a.SequenceEqual(b)); // false
I read some answers to questions like this(you may google for them) and what I would use, in general:
public static class CollectionExtensions {
public static bool Represents<T>(this IEnumerable<T> first, IEnumerable<T> second) {
if(object.ReferenceEquals(first, second)) {
return true;
}
if(first is IOrderedEnumerable<T> && second is IOrderedEnumerable<T>) {
return Enumerable.SequenceEqual(first, second);
}
if(first is ICollection<T> && second is ICollection<T>) {
if(first.Count()!=second.Count()) {
return false;
}
}
first=first.OrderBy(x => x.GetHashCode());
second=second.OrderBy(x => x.GetHashCode());
return CollectionExtensions.Represents(first, second);
}
}
That means one collection represents the other in their elements including repeated times without taking the original ordering into account. Some notes of the implementation:
GetHashCode() is just for the ordering not for equality; I think it's enough in this case
Count() will not really enumerates the collection and directly fall into the property implementation of ICollection<T>.Count
If the references are equal, it's just Boris
I've made my own compare method. It returns common, missing, and extra values.
private static void Compare<T>(IEnumerable<T> actual, IEnumerable<T> expected, out IList<T> common, out IList<T> missing, out IList<T> extra) {
common = new List<T>();
missing = new List<T>();
extra = new List<T>();
var expected_ = new LinkedList<T>( expected );
foreach (var item in actual) {
if (expected_.Remove( item )) {
common.Add( item );
} else {
extra.Add( item );
}
}
foreach (var item in expected_) {
missing.Add( item );
}
}
Comparing dictionaries' contents:
To compare two Dictionary<K, V> objects, we can assume that the keys are unique for every value, thus if two sets of keys are equal, then the two dictionaries' contents are equal.
Dictionary<K, V> dictionaryA, dictionaryB;
bool areDictionaryContentsEqual = new HashSet<K>(dictionaryA.Keys).SetEquals(dictionaryB.Keys);
Comparing collections' contents:
To compare two ICollection<T> objects, we need to check:
If they are of the same length.
If every T value that appears in the first collection appears an equal number of times in the second.
public static bool AreCollectionContentsEqual<T>(ICollection<T> collectionA, ICollection<T> collectionB)
where T : notnull
{
if (collectionA.Count != collectionB.Count)
{
return false;
}
Dictionary<T, int> countByValueDictionary = new(collectionA.Count);
foreach(T item in collectionA)
{
countByValueDictionary[item] = countByValueDictionary.TryGetValue(item, out int count)
? count + 1
: 1;
}
foreach (T item in collectionB)
{
if (!countByValueDictionary.TryGetValue(item, out int count) || count < 1)
{
return false;
}
countByValueDictionary[item] = count - 1;
}
return true;
}
These solutions should be optimal since their time and memory complexities are O(n), while the solutions that use ordering/sorting have time and memory complexities greater than O(n).

Categories

Resources