How to implement a comparer on a dictionary value - c#

I have:
Dictionary<int, MyClass> ItemList = new Dictionary<int, MyClass>();
Where MyClass is something like:
public class MyClass
{
public int BaseItemID;
public string Description;
public MyClass()
{
}
public class Comparer : IEqualityComparer<MyClass>
{
public bool Equals(MyClass x, MyClass y)
{
if (ReferenceEquals(x, y))
return true;
else if (x == null || y == null)
return false;
return x.BaseItemID == y.BaseItemID;
}
public int GetHashCode(MyClass obj)
{
unchecked
{
int hash = 17;
hash = hash * 23 + obj.BaseItemID.GetHashCode();
return hash;
}
}
}
}
I need to pass this comparer to the the contains on the dictionary but am struggling. I see the dictionary takes something implementing IEqualityComparer in the constructor but:
Dictionary<int, MyClass> ItemList = new Dictionary<int, MyClass>(new MyClass.Comparer());
doesn't seem to work and raises an error on compile time.
I assume I need to implement some KeyValuePair types somewhere but I'm not sure where.
If I was doing this on a normal List<MyClass> then List.Contains(Obj, new MyClass.Comparer()) would work but not in this case.

If am not mistaken Dictionary contructor overload requires IEqualityComparer
public Dictionary(IEqualityComparer<TKey> comparer);
in your code you pass "IEqualityComparer of TValue", you can compare only with keys in dictionary not with values

Related

Prevent adding two items with the same byte[] key in from being added to a KeyedCollection<byte[], MyObject>

Code:
public class Coll : KeyedCollection<byte[], MyObject>
{
protected override byte[] GetKeyForItem(MyObject item) => item.Key;
}
public class EquComparer : IEqualityComparer<byte[]>
{
public bool Equals(byte[]? x, byte[]? y)
{
if (x is null && y is null) return true;
if (x is null) return false;
if (y is null) return false;
return x.SequenceEqual(y);
}
public int GetHashCode([DisallowNull] byte[] obj)
{
int result = Int32.MinValue;
foreach (var b in obj)
{
result += b;
}
return result;
}
}
My key is byte[]. I want to set the default equality comparer to compare keys with to something using byte[]::SequenceEqual() to keep two items with the same keys from being added.
Is there a way to do this?
Edit: As other have pointed out I could use the constructor to specify a non default equality comparer. I am certain it will be forgotten at some point giving rise to a bug that will be difficult to find. That is why I want to add some code to the class that makes my custom equality comparer the default for that class.
The KeyedCollection<TKey,TItem> class has a constructor that accepts an IEqualityComparer<TKey>. You could invoke this constructor when instantiating the derived class:
public class Coll : KeyedCollection<byte[], MyObject>
{
public Coll() : base(new EquComparer()) { }
protected override byte[] GetKeyForItem(MyObject item) => item.Key;
}

How to get a distinct result for list of array?

I have a list of long type array.
List<ulong[]> TestList = new List<ulong[]>();
and list has following items.
{1,2,3,4,5,6},
{2,3,4,5,6,7},
{3,4,5,6,7,8},
{1,2,3,4,5,6}
and expected distinct result is
{1,2,3,4,5,6},
{2,3,4,5,6,7},
{3,4,5,6,7,8}
So I try as following, but useless.
TestList = TestList.Distinct().ToList();
Am I need something special comparer for getting distinct list?
Distinct() uses the default equality check, which for arrays is reference equality. It does not check the contents of the array for equality.
If you want to do that, you'll need the overload of Distinct() that takes an IEqualityComparer<T>. This allows you to customize the behaviour to determine if two items are equal or not.
For comparing arrays, IStructuralEquatable and friends already do the heavy lifting. You can wrap it simply, like so:
sealed class StructuralComparer<T> : IEqualityComparer<T>
{
public static IEqualityComparer<T> Instance { get; } = new StructuralComparer<T>();
public bool Equals(T x, T y)
=> StructuralComparisons.StructuralEqualityComparer.Equals(x, y);
public int GetHashCode(T obj)
=> StructuralComparisons.StructuralEqualityComparer.GetHashCode(obj);
}
Then, use it in the Distinct() call like this:
TestList = TestList.Distinct(StructuralComparer<ulong[]>.Instance).ToList();
You need to provide an equality comparer, default implementation does not know how to compare arrays of long (it uses reference equality):
class LongArrayComparer : EqualityComparer<long[]>
{
public override bool Equals(long[] a1, long[] a2)
{
if (a1 == null && a2 == null)
return true;
else if (a1 == null || a2 == null)
return false;
return a1.SequenceEqual(a2);
}
public override int GetHashCode(long[] arr)
{
long hCode = arr.Aggregate(0, (acc, it) => acc ^ it);
return hCode.GetHashCode();
}
}
Then use it:
TestList = TestList.Distinct(new LongArrayComparer()).ToList();
List<ulong[]> TestList = new List<ulong[]>() {
new ulong[]{ 1,2,3,4,5,6},
new ulong[]{ 2,3,4,5,6,7},
new ulong[]{ 3,4,5,6,7,8},
new ulong[]{ 1,2,3,4,5,6}
};
var result = TestList.GroupBy(x => String.Join(",", x))
.Select(x => x.First().ToArray())
.ToList();
You can implement an IEqualityComparer
public class IntArrayComparer : IEqualityComparer<string[]>
{
public bool Equals(int[] x, int[] y)
{
var shared = x.Intersect(y);
return x.Length == y.Length && shared.Count() == x.Length;;
}
public int GetHashCode(int[] obj)
{
int hashCode=obj.Length;
for(int i=0;i<obj.Length;++i)
{
hashCode=unchecked(hashCode*314159 +obj[i]);
}
return hashCode;
}
}
Then can implement it:
TestList = TestList.Distinct(new IntArrayComparer()).ToList();

UnitTesting List<T> of custom objects with List<S> of custom objects for equality

I'm writing some UnitTests for a parser and I'm stuck at comparing two List<T> where T is a class of my own, that contains another List<S>.
My UnitTest compares two lists and fails. The code in the UnitTest looks like this:
CollectionAssert.AreEqual(list1, list2, "failed");
I've written a test scenario that should clarify my question:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ComparerTest
{
class Program
{
static void Main(string[] args)
{
List<SimplifiedClass> persons = new List<SimplifiedClass>()
{
new SimplifiedClass()
{
FooBar = "Foo1",
Persons = new List<Person>()
{
new Person(){ ValueA = "Hello", ValueB="Hello"},
new Person(){ ValueA = "Hello2", ValueB="Hello2"},
}
}
};
List<SimplifiedClass> otherPersons = new List<SimplifiedClass>()
{
new SimplifiedClass()
{
FooBar = "Foo1",
Persons = new List<Person>()
{
new Person(){ ValueA = "Hello2", ValueB="Hello2"},
new Person(){ ValueA = "Hello", ValueB="Hello"},
}
}
};
// The goal is to ignore the order of both lists and their sub-lists.. just check if both lists contain the exact items (in the same amount). Basically ignore the order
// This is how I try to compare in my UnitTest:
//CollectionAssert.AreEqual(persons, otherPersons, "failed");
}
}
public class SimplifiedClass
{
public String FooBar { get; set; }
public List<Person> Persons { get; set; }
public override bool Equals(object obj)
{
if (obj == null) { return false;}
PersonComparer personComparer = new PersonComparer();
SimplifiedClass obj2 = (SimplifiedClass)obj;
return this.FooBar == obj2.FooBar && Enumerable.SequenceEqual(this.Persons, obj2.Persons, personComparer); // I think here is my problem
}
public override int GetHashCode()
{
return this.FooBar.GetHashCode() * 117 + this.Persons.GetHashCode();
}
}
public class Person
{
public String ValueA { get; set; }
public String ValueB { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
Person obj2 = (Person)obj;
return this.ValueA == obj2.ValueA && this.ValueB == obj2.ValueB;
}
public override int GetHashCode()
{
if (!String.IsNullOrEmpty(this.ValueA))
{
//return this.ValueA.GetHashCode() ^ this.ValueB.GetHashCode();
return this.ValueA.GetHashCode() * 117 + this.ValueB.GetHashCode();
}
else
{
return this.ValueB.GetHashCode();
}
}
}
public class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x != null)
{
return x.Equals(y);
}
else
{
return y == null;
}
}
public int GetHashCode(Person obj)
{
return obj.GetHashCode();
}
}
}
The question is strongly related to C# Compare Lists with custom object but ignore order, but I can't find the difference, other than I wrap a list into another object and use the UnitTest one level above.
I've tried to use an IEqualityComparer:
public class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x != null)
{
return x.Equals(y);
}
else
{
return y == null;
}
}
public int GetHashCode(Person obj)
{
return obj.GetHashCode();
}
}
Afterwards I've tried to implement the ''IComparable'' interface thats allows the objects to be ordered. (Basically like this: https://stackoverflow.com/a/4188041/225808)
However, I don't think my object can be brought into a natural order. Therefore I consider this a hack, if I come up with random ways to sort my class.
public class Person : IComparable<Person>
public int CompareTo(Person other)
{
if (this.GetHashCode() > other.GetHashCode()) return -1;
if (this.GetHashCode() == other.GetHashCode()) return 0;
return 1;
}
I hope I've made no mistakes while simplifying my problem. I think the main problems are:
How can I allow my custom objects to be comparable and define the equality in SimplifiedClass, that relies on the comparision of subclasses (e.g. Person in a list, like List<Person>). I assume Enumerable.SequenceEqual should be replaced with something else, but I don't know with what.
Is CollectionAssert.AreEqual the correct method in my UnitTest?
Equals on a List<T> will only check reference equality between the lists themselves, it does not attempt to look at the items in the list. And as you said you don't want to use SequenceEqual because you don't care about the ordering. In that case you should use CollectionAssert.AreEquivalent, it acts just like Enumerable.SequenceEqual however it does not care about the order of the two collections.
For a more general method that can be used in code it will be a little more complicated, here is a re-implemented version of what Microsoft is doing in their assert method.
public static class Helpers
{
public static bool IsEquivalent(this ICollection source, ICollection target)
{
//These 4 checks are just "shortcuts" so we may be able to return early with a result
// without having to do all the work of comparing every member.
if (source == null != (target == null))
return false; //If one is null and one is not, return false immediately.
if (object.ReferenceEquals((object)source, (object)target) || source == null)
return true; //If both point to the same reference or both are null (We validated that both are true or both are false last if statement) return true;
if (source.Count != target.Count)
return false; //If the counts are different return false;
if (source.Count == 0)
return true; //If the count is 0 there is nothing to compare, return true. (We validated both counts are the same last if statement).
int nullCount1;
int nullCount2;
//Count up the duplicates we see of each element.
Dictionary<object, int> elementCounts1 = GetElementCounts(source, out nullCount1);
Dictionary<object, int> elementCounts2 = GetElementCounts(target, out nullCount2);
//It checks the total number of null items in the collection.
if (nullCount2 != nullCount1)
{
//The count of nulls was different, return false.
return false;
}
else
{
//Go through each key and check that the duplicate count is the same for
// both dictionaries.
foreach (object key in elementCounts1.Keys)
{
int sourceCount;
int targetCount;
elementCounts1.TryGetValue(key, out sourceCount);
elementCounts2.TryGetValue(key, out targetCount);
if (sourceCount != targetCount)
{
//Count of duplicates for a element where different, return false.
return false;
}
}
//All elements matched, return true.
return true;
}
}
//Builds the dictionary out of the collection, this may be re-writeable to a ".GroupBy(" but I did not take the time to do it.
private static Dictionary<object, int> GetElementCounts(ICollection collection, out int nullCount)
{
Dictionary<object, int> dictionary = new Dictionary<object, int>();
nullCount = 0;
foreach (object key in (IEnumerable)collection)
{
if (key == null)
{
++nullCount;
}
else
{
int num;
dictionary.TryGetValue(key, out num);
++num;
dictionary[key] = num;
}
}
return dictionary;
}
}
What it does is it makes a dictionary out of the two collections, counting the duplicates and storing it as the value. It then compares the two dictionaries to make sure that the duplicate count matches for both sides. This lets you know that {1, 2, 2, 3} and {1, 2, 3, 3} are not equal where Enumerable.Execpt would tell you that they where.

Remove objects with duplicate properties from List

I have a List of objects in C#. All of the objects contain the properties dept and course.
There are several objects that have the same dept and course.
How can I trim the List(or make a new List) where there is only one object per unique (dept & course) properties.
[Any additional duplicates are dropped out of the List]
I know how to do this with a single property:
fooList.GroupBy(x => x.dept).Select(x => x.First());
However, I am wondering how to do this for multiple properties (2 or more)?
To use multiple properties you can use an anonymous type:
var query = fooList.GroupBy(x => new { x.Dept, x.Course })
.Select(x => x.First());
Of course, this depends on what types Dept and Course are to determine equality. Alternately, your classes can implement IEqualityComparer<T> and then you could use the Enumerable.Distinct method that accepts a comparer.
Another approach is to use the LINQ Distinct extension method together with an IEqualityComparer<Foo>. It requires you to implement a comparer; however, the latter is reusable and testable.
public class FooDeptCourseEqualityComparer : IEqualityComparer<Foo>
{
public bool Equals(Foo x, Foo y)
{
return
x.Dept == y.Dept &&
x.Course.ToLower() == y.Course.ToLower();
}
public int GetHashCode(Foo obj)
{
unchecked {
return 527 + obj.Dept.GetHashCode() * 31 + obj.Course.GetHashCode();
}
}
#region Singleton Pattern
public static readonly FooDeptCourseEqualityComparer Instance =
new FooDeptCourseEqualityComparer();
private FooDeptCourseEqualityComparer() { }
#endregion
}
My example uses the singleton pattern. Since the class does not have any state information, we do not need to create a new instance each time we use it.
My code does not handle null values. Of course you would have to handle them, if they can occur.
The unique values are returned like this
var result = fooList.Distinct(FooDeptCourseEqualityComparer.Instance);
UPDATE
I suggest using a generic EqualityComparer class that accepts lambda expressions in the constructor and can be reused in multiple situations
public class LambdaEqualityComparer<T> : IEqualityComparer<T>
{
private Func<T, T, bool> _areEqual;
private Func<T, int> _getHashCode;
public LambdaEqualityComparer(Func<T, T, bool> areEqual,
Func<T, int> getHashCode)
{
_areEqual = areEqual;
_getHashCode = getHashCode;
}
public LambdaEqualityComparer(Func<T, T, bool> areEqual)
: this(areEqual, obj => obj.GetHashCode())
{
}
#region IEqualityComparer<T> Members
public bool Equals(T x, T y)
{
return _areEqual(x, y);
}
public int GetHashCode(T obj)
{
return _getHashCode(obj);
}
#endregion
}
You can use it like this
var comparer = new LambdaEqualityComparer<Foo>(
(x, y) => x.Dept == y.Dept && x.Course == y.Course,
obj => {
unchecked {
return 527 + obj.Dept.GetHashCode() * 31 + obj.Course.GetHashCode();
}
}
);
var result = fooList.Distinct(comparer);
Note: You have to provide a calculation of the hash code, since Distinct uses an internal Set<T> class, which in turn uses hash codes.
UPDATE #2
An even more generic equality comparer implements the comparison automatically and accepts a list of property accessors; however, you have no control, on how the comparison is performed.
public class AutoEqualityComparer<T> : IEqualityComparer<T>
{
private Func<T, object>[] _propertyAccessors;
public AutoEqualityComparer(params Func<T, object>[] propertyAccessors)
{
_propertyAccessors = propertyAccessors;
}
#region IEqualityComparer<T> Members
public bool Equals(T x, T y)
{
foreach (var getProp in _propertyAccessors) {
if (!getProp(x).Equals(getProp(y))) {
return false;
}
}
return true;
}
public int GetHashCode(T obj)
{
unchecked {
int hash = 17;
foreach (var getProp in _propertyAccessors) {
hash = hash * 31 + getProp(obj).GetHashCode();
}
return hash;
}
}
#endregion
}
Usage
var comparer = new AutoEqualityComparer<Foo>(foo => foo.Dept,
foo => foo.Course);
var result = fooList.Distinct(comparer);

How to attach a value with two keys

I am using a IDictionary, but it allows one key for one value is there any way or IEnumerable list that I can use to add value with teo keys??
Thanks
you could use anything in the generic Dictionary as key...
for example:
class MyKey /*: IComparable*/ {
public string Key1 {get;set;}
public string Key2 {get;set;}
/* //CompareTo seems to be the wrong thing to implement...
public int CompareTo(object o) {
if(!(o is MyKey))
return -1;
int k1 = Key1.CompareTo(((MyKey)o).Key1);
return k1 == 0 ? Key2.CompareTo(((MyKey)o).Key2) : k1;
}*/
public override bool Equals(object o) {
return (o is MyKey) &&
(Key1 == ((MyKey)o).Key1) &&
(Key2 == ((MyKey)o).Key2);
}
public override int GetHashCode() {
return Key1.GetHashCode() ^ Key2.GetHashCode();
}
//to be very kewl we'll add the (in)equality-op's too...:
public static bool operator ==(MyKey m1, MyKey m2) {
return m1.Equals(m2);
}
public static bool operator !=(MyKey m1, MyKey m2) {
return !m1.Equals(m2);
}
}
Dictionary<MyKey, string> myKewlDictionary...
If you are looking for a way to generate a composite key from two values and you are using .NET 4.0 you can use a Tuple as a key - e.g.
var _myDictionary = new Dictionary<Tuple<int, int>, OtherClass>();
_myDictionary.Add(Tuple.Create(item1.Id, item2.Id), item3);
var item = _myDictionary[Tuple.Create(item1.Id, item2.Id)];
Hmm... Really, I don't know why you need that solution, it seems strange. Anyways, you can use custom IEnumerable class as your key collection.
You can find my test example below.
using System;
using System.Collections;
using System.Collections.Generic;
namespace TestApplication
{
class Program
{
static void Main(string[] args)
{
IDictionary<IEnumerable, object> dictionary1 = new Dictionary<IEnumerable, object>();
IEnumerable key11 = new string[] { "key1", "key2" };
IEnumerable key12 = new string[] { "key1", "key2" };
dictionary1.Add(key11, new object());
// Exception doesn't occur because key11 and key12 are not equal objects
dictionary1.Add(key12, new object());
IDictionary<KeyCollection<string>, object> dictionary2 = new Dictionary<KeyCollection<string>, object>();
KeyCollection<string> key21 = new KeyCollection<string>(new string[] { "key1", "key2" });
KeyCollection<string> key22 = new KeyCollection<string>(new string[] { "key1", "key2" });
dictionary2.Add(key21, new object());
// ArgumentEception: An item with the same key has already been added
dictionary2.Add(key22, new object());
}
private class KeyCollection<T> : IEnumerable where T : class
{
private IEnumerable<T> m_KeyCollection;
public KeyCollection() : this(new List<T>())
{
}
public KeyCollection(IEnumerable<T> array)
{
if (array == null)
{
throw (new NullReferenceException("'array' parameter must be initialized!"));
}
IList<T> list = new List<T>();
IEnumerator<T> enumerator = array.GetEnumerator();
while (enumerator.MoveNext())
{
list.Add(enumerator.Current);
}
m_KeyCollection = list;
}
public IEnumerator GetEnumerator()
{
return m_KeyCollection.GetEnumerator();
}
public override bool Equals(object obj)
{
KeyCollection<T> collection = (obj as KeyCollection<T>);
if (collection == null)
{
return false;
}
IEnumerator<T> enumerator1 = m_KeyCollection.GetEnumerator();
IEnumerator enumerator2 = collection.GetEnumerator();
bool moveNext1 = false, moveNext2 = false;
while (true)
{
moveNext1 = enumerator1.MoveNext();
moveNext2 = enumerator2.MoveNext();
if (moveNext1 && moveNext2)
{
T current1 = enumerator1.Current;
T current2 = (enumerator2.Current as T);
if ((current1 == null) || (current2 == null) || (!current1.Equals(current2)))
{
return false;
}
continue;
}
return ((!moveNext1) && (!moveNext2));
}
}
public override int GetHashCode()
{
IEnumerator<T> enumerator = m_KeyCollection.GetEnumerator();
string stringHash = string.Empty;
while (enumerator.MoveNext())
{
stringHash += string.Format("_{0}", ((enumerator.Current != null) ? enumerator.Current.GetHashCode().ToString() : "-1"));
}
return (string.IsNullOrEmpty(stringHash) ? -1 : stringHash.GetHashCode());
}
}
}
}
If your UserID and SessionID can never collide then you can use them both as hash keys: stuff your Userinfo in the dictionary once with the UserID, once with the SessionID, and in the cases when you only have one or the other, add to the dictionary with the only one you have.
(You may need to be concerned about adding a second key to a Userinfo object; say, if method login has a UserID and is deciding whether it needs to create a new Userinfo object to insert into the dictionary vs looking up a Userinfo object via the UserID, and method returning has already inserted the 'correct' Userinfo object into the dictionary using a SessionID but no UserID, login would incorrectly create a new Userinfo object. This may or may not be an issue for your application.)
If the UserID and SessionID can collide then you could use two dictionaries, and search them sequentially when needed. This might still be cleaner than using one dictionary with two different types of keys.
If you will always have one key and sometimes the other, you could use two different kinds of dictionaries: e.g., one to store UserID -> SessionID, and one for SessionID -> Userinfo; OR, one to store SessionID -> UserID, and one for UserID -> Userinfo. This would let you quickly chain your lookups based on the information available. (It feels more like a relational database.)

Categories

Resources