At high level getting unique values for reference types requires implementing IEqualityComparer with HashSet but with SortedSet which is HashSet as well it does not seem to be required.
Here is an example. Lets say we have Employee class and EmployeeComparer classes below -
public class Employee
{
public string Name { get; set; }
public int Age { get; set; }
}
public class EmployeeComparer : IEqualityComparer<Employee>, IComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
return string.Equals(x.Name, y.Name);
}
public int GetHashCode(Employee obj)
{
return obj.Name.GetHashCode();
}
public int Compare(Employee x, Employee y)
{
return string.Compare(x.Name, y.Name);
}
}
If I have to use HashSet to get unique Employees based on name it works only if I have EmployeeComparer implementing IEqualityComparer but if I use SortedSet it gives me unique values even if the class EmployeeComparer does not implement IEqualityComparer and just the IComparer. What happens to the requirement of providing GetHashCode() and Equals() method for uniqueness here?
An IComparer<T> is fully sufficient for determining whether two objects are semantically equal.
If ICompararer<T>.Compare() returns 0, then the objects are considered to be equal. If it returns a nonzero value, then they are considered to be nonequal. Since the SortedSet<T> is supposed to put the values in sorted order, it needs a comparison function, but it doesn't need an equality function on top of that.
Related
I'm new to C#, just a question on how to use IComparable and IComparer properly. Let's say we have the following class:
public class Student
{
int score;
string name;
}
and I want to sort by socre (desc) first then sort by name (asc).
Now assume that I can't access to the Student class, so I can only use IComparer
so I have to make one help class(let's say it is called StudentComparer) and put the same logic into
public class StudentComparer: IComparer
{
int Compare(object o1, object o2)
{
Student s1 = o1 as Student;
Student s2 = o2 as Student;
// not checking null for simplicity
if (s1.score == s2.score)
return String.Compare(s1.name, s2.name);
else if (s1.score < s2.score)
return -1
else
return 1
}
}
Here is my question, if I just need to use a single rule later, for example, sometimes just sort by name and sometimes just sort by score. So I have to make another two help classes(ScoreComparer and NameComparer) that implement IComparer, which have duplicated code in StudentComparer as
public class ScoreComparer : IComparer
{
int Compare(object o1, object o2)
{
//half logic of StudentComparer
}
}
public class NameComparer: IComparer
{
int Compare(object o1, object o2)
{
//half logic of StudentComparer
}
}
my case is pretty simple, image that if there is a complicated case, each comparer consist of hundreds line of code,so how can I avoid duplicated code? or is it a way to combine multiple Comparer A, B, C, D ... into a common Comparer so that it check A,B,C,D in sequence, just like order by clause in SQL
Back in the days before linq, I used to use comparers a lot.
The way I've handled different sort options was by specifying these options in the constructor of the comparer implementation and then use that information in the Compare method.
Lucky for us, we now have linq so this entire thing can be done with a single, fluent line of code:
// sortedStudents is an IEnumerable<Student> sorted by score and name.
var sortedStudents = students.OrderBy(s => s.Score).ThenBy(s => s.Name);
However, if for some reason you need to work the old-fashion way, using comparers and stuff like that, here is how I would handle this back then:
internal enum CompareBy
{
NameOnly,
ScoreAndName
}
public class StudentComparer: IComparer<Student>
{
private CompareBy _compareBy
public StudentComparer(CompareBy compareBy)
{
_compareBy = compareBy;
}
public int Compare(Student s1, Student s2)
{
// not checking null for simplicity
var nameCompare = string.Compare(s1.name, s2.name);
if(_compareBy == NameOnly)
{
return nameCompare;
}
// since there are only two members in the enum it's safe to write it like this.
// if the enum grows, you must change the code.
if (s1.score == s2.score)
{
return nameCompare;
}
else if (s1.score < s2.score)
{
return -1
}
return 1
}
}
I'm comparing two objects of type Triangle and apparently they are deemed equal (I implemented my custom GetHaschCode as well as Equal method and operator).
List<Triangle> triangles = ...;
bool same = triangles[0] == triangles[1];
// same is true
However, when I go Distinct() on that list, it keeps all the elements (which sound to me like it's comparing by reference and not by my custom conditions). Is it so and what can I do about it?
int countBefore = triangles.Count();
int countAfter = triangles.Distinct().Count();
bool same = countBefore == countAfter;
// same is true, again
I'm missing something fairly obvious, am I not?
You can do this in one of two ways...
Either as Andrew says implement IEquatable
public class Triangle : IEquatable<Triangle>
{
bool IEquatable<Triangle>.Equals(Triangle other)
{
return Equals(other);
}
public override bool Equals(object obj)
{
//...
}
public override int GetHashCode()
{
//...
}
}
Or you could create another class that implements IEqualityComparer(T) and pass that into the Distinct method call.
public class TriangleComparer : IEqualityComparer<Triangle>
{
public bool Equals(Triangle x, Triangle y)
{
return x.Equals(y);
}
public int GetHashCode(Triangle obj)
{
return obj.GetHashCode();
}
}
You need to implement the IEquatable(T) interface in your Triangle class.
From the Enumerable.Distinct() documentation:
The default equality comparer, Default, is used to compare values of
the types that implement the IEquatable generic interface. To
compare a custom data type, you need to implement this interface and
provide your own GetHashCode and Equals methods for the type.
Consider these two structures:
struct Task
{
public Int32 Id;
public String Name;
public List<Registration> Registrations;
}
struct Registration
{
public Int32 Id;
public Int32 TaskId;
public String Comment;
public Double Hours;
}
I am selecting a bunch of entries in a DataTable into new structures, like so:
var tasks = data.AsEnumerable().Select(t => new Task
{
Id = Convert.ToInt32(t["ProjectTaskId"]),
Name = Convert.ToString(t["ProjectTaskName"]),
Registrations = new List<Registration>()
});
But when I call Distinct() on the collection, it doesn't recognize objects with the same values (Id, Name, Registrations) as being equal.
But if I use an equality comparer; comparing the Id property on the objects, it's all fine and dandy...:
class TaskIdComparer : IEqualityComparer<Task>
{
public bool Equals(Task x, Task y)
{
return x.Id == y.Id;
}
public Int32 GetHashCode(Task t)
{
return t.Id.GetHashCode();
}
}
What am I missing here? Is Distinct() checking something else than the value of properties?
LINQ's Distinct method compares objects using the objects' Equals and GetHashCode implementations.
Therefore, if these methods are not overridden, it will compare by reference, not by value.
You need to use an EqualityComparer. (Or implement Equals and GetHashCode for the Task class)
my guess is that it's the list in there. Almost certainly, the two list objects are different, even if they contain the same info.
I have a class, show below, which is used as a key in a Dictionary<ValuesAandB, string>
I'm having issues when trying to find any key within this dictionary, it never finds it at all. As you can see, I have overridden Equals and GetHashCode.
To look for the key I'm using
ValuesAandB key = new ValuesAandB(A,B);
if (DictionaryName.ContainsKey(key)) {
...
}
Is there anything else that I'm missing? Can anyone point out what I'm doing wrong?
private class ValuesAandB {
public string valueA;
public string valueB;
// Constructor
public ValuesAandB (string valueAIn, string valueBIn) {
valueA = valueAIn;
valueB = ValueBIn;
}
public class EqualityComparer : IEqualityComparer<ValuesAandB> {
public bool Equals(ValuesAandB x, ValuesAandB y) {
return ((x.valueA.Equals(y.valueA)) && (x.valueB.Equals(y.valueB)));
}
public int GetHashCode(ValuesAandB x) {
return x.valueA.GetHashCode() ^ x.valueB.GetHashCode();
}
}
}
And before anyone asks, yes the values are in the Dictionary!
You have not overridden Equals and GetHashCode. You have implemented a second class which can serve as an EqualityComparer. If you don't construct the Dictionary with the EqualityComparer, it will not be used.
The simplest fix would be to override GetHashCode and Equals directly rather than implementing a comparer (comparers are generally only interesting when you need to supply multiple different comparison types (case sensitive and case insensitive for example) or when you need to be able to perform comparisons on a class which you don't control.
I had this problem, turns out the dictionary was comparing referances for my key, not the values in the object.
I was using a custom Point class as keys. I overrode the ToString() and the GetHashCode() methods and viola, key lookup worked fine.
It looks like you're comparing two strings. Iirc, when using .Equals(), you are comparing the reference of the strings, not the actual contents. To implement an EqualityComparer that works with strings, you would want to use the String.Compare() method.
public class EqualityComparer : IEqualityComparer<ValuesAandB>
{
public bool Equals(ValuesAandB x, ValuesAandB y)
{
return ((String.Compare(x.valueA,y.valueA) == 0) &&
(String.Compare(x.valueB, y.valueB) == 0));
}
// gethashcode stuff here
}
I could be a little off with the code, that should get you close...
I have to sort ArrayList which consists of objects. Object: ID, Quantity. The ArrayList should be sorted by ID. How to implement this?
ItemIdQuantity = new ItemIdQuantity (ID, Quantity);
ItemIdQuantity.Sort(); // where must be sorting by ID
public class IdComparer : IComparer {
int IComparer.Compare(object x, object y) {
return Compare((ItemIdQuantity)x, (ItemIdQuantity)y);
}
public int Compare(ItemIdQuantity x, ItemIdQuantity y) {
return x.ID - y.ID;
}
}
arrayList.Sort(new IdComparer());
Assuming that this is Java:
If the ItemIdQuantity class implements Comparable based on the ID field, use Collections.sort() with the list as single parameter.
Otherwise, implement a Comparator that compares the objects using their ID, and use it as second paramter to Collections.sort().