I have two objects using the ff. class:
public class Test {
public string Name {get; set;}
public List<Input> Inputs {get;set;}
......
//some other properties I don't need to check
}
public class Input {
public int VariableA {get;set;}
public int VariableB {get;set;}
public List<Sancti> Sancts {get;set;}
}
public class Sancti {
public string Symbol {get;set;}
public double Percentage {get;set;}
}
I want to check if two instance of Test has the same Inputs value. I've done this using a loop but I believe this is not the way to do this.
I've read some links: link1, link2 but they seem gibberish for me. Are there simpler ways to do this, like a one-liner something like:
test1.Inputs.IsTheSameAs(test2.Inputs)?
I was really hoping for a more readable method. Preferrably Linq.
NOTE: Order of inputs should not matter.
One way is to check the set negation between the two lists. If the result of listA negated by listB has no elements, that means that everything in listA exists in listB. If the reverse is also true, then the two lists are equal.
bool equal = testA.Inputs.Except(testB.Inputs).Count() == 0
&& testB.Inputs.Except(testA.Inputs).Count() == 0;
Another is to simply check each element of listA and see if it exists in listB (and vice versa):
bool equal = testA.Inputs.All(x => testB.Inputs.Contains(x))
&& testB.Inputs.All(x => testA.Inputs.Contains(x));
This being said, either of these can throw a false positive if there is one element in a list that would be "equal" to multiple elements in the other. For example, the following two lists would be considered equal using the above approaches:
listA = { 1, 2, 3, 4 };
listB = { 1, 1, 2, 2, 3, 3, 4, 4 };
To prevent that from happening, you would need to perform a one-to-one search rather than the nuclear solution. There are several ways to do this, but one way to do this is to first sort both lists and then checking their indices against each other:
var listASorted = testA.Inputs.OrderBy(x => x);
var listBSorted = testB.Inputs.OrderBy(x => x);
bool equal = testA.Inputs.Count == testB.Inputs.Count
&& listASorted.Zip(listBSorted, (x, y) => x == y).All(b => b);
(If the lists are already sorted or if you'd prefer to check the lists exactly (with ordering preserved), then you can skip the sorting step of this method.)
One thing to note with this method, however, is that Input needs to implement IComparable in order for them to be properly sorted. How you implement it exactly is up to you, but one possible way would be to sort Input based on the XOR of VariableA and VariableB:
public class Input : IComparable<Input>
{
...
public int Compare(Input other)
{
int a = this.VariableA ^ this.VariableB;
int b = other.VariableA ^ other.VariableB;
return a.Compare(b);
}
}
(In addition, Input should also override GetHashCode and Equals, as itsme86 describes in his answer.)
EDIT:
After being drawn back to this answer, I would now like to offer a much simpler solution:
var listASorted = testA.Inputs.OrderBy(x => x);
var listBSorted = testB.Inputs.OrderBy(x => x);
bool equal = listASorted.SequenceEqual(listBSorted);
(As before, you can skip the sorting step if the lists are already sorted or you want to compare them with their existing ordering intact.)
SequenceEqual uses the equality comparer for a particular type for determining equality. By default, this means checking that the values of all public properties are equal between two objects. If you want to implement a different approach, you can define an IEqualityComparer for Input:
public class InputComparer : IEqualityComparer<Input>
{
public bool Equals(Input a, Input b)
{
return a.variableA == b.variableA
&& a.variableB == b.variableB
&& ... and so on
}
public int GetHashCode(Input a)
{
return a.GetHashCode();
}
}
You can change your Input and Sancti class definitions to override Equals and GetHasCode. The following solution considers that 2 Inputs are equal when:
VariableA are equal and
VariableB are equal and
The Sancts List are equal, considering that the Sancti elements with the same Symbol must have the same Percentage to be equal
You may need to change this if your specifications are different:
public class Input
{
public int VariableA { get; set; }
public int VariableB { get; set; }
public List<Sancti> Sancts { get; set; }
public override bool Equals(object obj)
{
Input otherInput = obj as Input;
if (ReferenceEquals(otherInput, null))
return false;
if ((this.VariableA == otherInput.VariableA) &&
(this.VariableB == otherInput.VariableB) &&
this.Sancts.OrderBy(x=>x.Symbol).SequenceEqual(otherInput.Sancts.OrderBy(x => x.Symbol)))
return true;
else
{
return false;
}
}
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
// Suitable nullity checks etc, of course :)
hash = hash * 23 + VariableA.GetHashCode();
hash = hash * 23 + VariableB.GetHashCode();
hash = hash * 23 + Sancts.GetHashCode();
return hash;
}
}
}
public class Sancti
{
public string Symbol { get; set; }
public double Percentage { get; set; }
public override bool Equals(object obj)
{
Sancti otherInput = obj as Sancti;
if (ReferenceEquals(otherInput, null))
return false;
if ((this.Symbol == otherInput.Symbol) && (this.Percentage == otherInput.Percentage) )
return true;
else
{
return false;
}
}
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
// Suitable nullity checks etc, of course :)
hash = hash * 23 + Symbol.GetHashCode();
hash = hash * 23 + Percentage.GetHashCode();
return hash;
}
}
}
Doing this, you just have to do this to check if Inputs are equal:
test1.Inputs.SequenceEqual(test2.Inputs);
Related
I have a class named accessoire :
class accessoire
{
public int value1 { get; set; }
public string Value2 { get; set; }
}
then i have a List of that product
List<accessoire> accessoires
And i have an UI where the user pick the product he wants from a DataGridview and when he selected it launch an event that add this item to the list :
private void ProductBrowser_OnItemAdded(Accessoire item)
{
if (Cart.Contains(item))
{
MessageBox.Show("Produit deja ajoutée au panier ! ");
}
else
{
Cart.Add(item);
ProductView.Rows.Add(item.Ref, item.Name, Function.CatName(item.Cat), item.SellPrice, "1", Convert.ToDouble(item.SellPrice) * Convert.ToDouble(item.QtetoSell));
TotalPriceSet();
MessageBox.Show("Produit Ajouté !");
}
}
this doesnt work , but when i change it to :
private void ProductBrowser_OnItemAdded(Accessoire item)
{
var InList = Cart.Find(product => product.Ref == item.Ref);
if (Cart.Contains(InList))
{
MessageBox.Show("Product already in list ! ");
}
else
{
Cart.Add(item);
ProductView.Rows.Add(item.Ref, item.Name, Function.CatName(item.Cat), item.SellPrice, "1", Convert.ToDouble(item.SellPrice) * Convert.ToDouble(item.QtetoSell));
TotalPriceSet();
MessageBox.Show("product added !");
}
}
it works , but i'am still wondering why the first code doesnt work it keep adding that item to the list ? in other way how does the method .Contains()works ? what does it check to know if the item is or the list on not ?
The reason it doesn't find the object in the list is because it is a reference comparison, comparing the instances of the object, not the values. You can have two instances of your class with the same values in their properties, but if you compare them, they are not equal:
accessoire item1 = new accessoire();
item1.value1 = 1;
item1.value2 = "one";
accessoire item2 = new accessoire();
item2.value1 = 1;
item2.value2 = "one";
if(item1 == item2) MessageBox.Show("Same");
else MessageBox.Show("Different");
When you select the item from the list to compare with, you are pulling the specific instance, which does exist in the list.
You need to implement the Equals method of accessoire to properly compare all properties/fields in it. The default implementation of Equals uses ReferenceEquals, which only works if the two instances are in fact the same.
if (Cart.Contains(item))
is matching by equality.
If object.Equals(T) is not satisified, it will fail. That means the smallest change, even whitespace in a string, will return false. You'll also get a false result if you have two instances of the same class. Contains must refer to exactly item.
var InList = Cart.Find(product => product.Ref == item.Ref) is a match by property. This means that other properties of the product/item can all be different, as long as .Ref matches. I presume Ref is a primary key, which is why you're not getting problems in your result where Find() returns the wrong item.
You can get around the difference by overriding Equals for Cart, but I don't recommend it. It can make debugging hell later.
Just implement the equals method
// override object.Equals
public override bool Equals(object obj)
{
//
// See the full list of guidelines at
// http://go.microsoft.com/fwlink/?LinkID=85237
// and also the guidance for operator== at
// http://go.microsoft.com/fwlink/?LinkId=85238
//
if (obj == null || GetType() != obj.GetType())
{
return false;
}
var data = (accessoire)obj;
return this.Ref.Equals(data.Ref);
}
// override object.GetHashCode
public override int GetHashCode()
{
return this.Ref.GetHashCode()
}
You were doing reference compare and the references don't match in your first example, but do in your second. You probably want to do equality comparison. Are the values of both objects the same.
Below is your class implemented with the various methods used for equality comparing. You would just need to modify them to suit your purposes.
// IEquatable<T> provides typed equality comparing
class accessoire : IEquatable<accessoire>
{
public int Value1 { get; set; }
public string Value2 { get; set; }
// you can override Equals.
public override bool Equals(object obj)
{
return this.Equals(obj as accessoire);
}
// this comes from IEquatable<T>
public bool Equals(accessoire other)
{
if (ReferenceEquals(null, other))
{
return false;
}
// return the comparison that makes them equal.
return
this.Value1.Equals(this.Value1) &&
this.Value2.Equals(this.Value2);
}
public override int GetHashCode()
{
unchecked
{
int hash = 37;
hash *= 23 + this.Value1.GetHashCode();
hash *= 23 + this.Value2.GetHashCode();
return hash;
}
}
// allows you to check equality with the == operator
public static bool operator ==(accessoire left, accessoire right)
{
if (ReferenceEquals(left, right))
{
return true;
}
if ((oject)left == null || (object)right == null)
{
return false;
}
return left.Equals(right);
}
public static bool operator !=(accessoire left, accessoire right)
{
return !left.Equals(right);
}
}
I've got an object which has 3 integer values, combined the 3 integer are always unique. I want a quick way to find the specific object out of thousands.
my idea was to combine the 3 integers in a string so 1, 2533 and 9 would become a unique string: 1-2533-9. But is this the most efficient way? The numbers cannot be bigger than 2^16, so I could also use bit shifting and create a long which would be faster than creating a string from them I think. Are there other options? what should I do?
The main thing I want to achieve is finding the object quickly even with a collection of thousands of objects.
public class SomeClass
{
private readonly IDictionary<CompositeIntegralTriplet, object> _dictionary = new Dictionary<CompositeIntegralTriplet, object>();
}
public sealed class CompositeIntegralTriplet : IEquatable<CompositeIntegralTriplet>
{
public CompositeIntegralTriplet(int first, int second, int third)
{
First = first;
Second = second;
Third = third;
}
public int First { get; }
public int Second { get; }
public int Third { get; }
public override bool Equals(object other)
{
var otherAsTriplet = other as CompositeIntegralTriplet;
return Equals(otherAsTriplet);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = First;
hashCode = (hashCode*397) ^ Second;
hashCode = (hashCode*397) ^ Third;
return hashCode;
}
}
public bool Equals(CompositeIntegralTriplet other) => other != null && First == other.First && Second == other.Second && Third == other.Third;
}
This is how the custom object is defined:
public class AccountDomain
{
public string MAILDOMAIN { get; set; }
public string ORG_NAME { get; set; }
}
This is how I am populating the List of objects:
List<AccountDomain> mainDBAccountDomain = mainDB.GetAllAccountsAndDomains();
List<AccountDomain> manageEngineAccountDomain = ManageEngine.GetAllAccountsAndDomains();
This code works fine - if I look at the locals windows I can see a List of Objects in both mainDBAccountDomain and manageEngineAccountDomain.
I'm struggling with the next bit, ideally I want a new list of type AccountDomain that contains all entries that are in mainDBAccountDomain and not ManageEngineAccountDomain
Any help greatly appreciated, even if it's just a pointer in the right direction!
I want a new list of type AccountDomain that contains all entries that are in mainDBAccountDomain and not ManageEngineAccountDomain
It's very simple with linq to objects, it's exactly what the Enumerable.Except function does:
var result = mainDBAccountDomain.Except(manageEngineAccountDomain).ToList();
You can pass a comparer to the Except function if you need something different from reference equality, or you could implement Equals and GetHashCode in AccountDomain (and optionally implement IEquatable<AccountDomain> on top of these).
See this explanation if you need more details about comparers.
Here's an example:
public class AccountDomainEqualityComparer : IEqualityComparer<AccountDomain>
{
public static readonly AccountDomainEqualityComparer Instance
= new AccountDomainEqualityComparer();
private AccountDomainEqualityComparer()
{
}
public bool Equals(AccountDomain x, AccountDomain y)
{
if (ReferenceEquals(x, y))
return true;
if (x == null || y == null)
return false;
return x.MAILDOMAIN == y.MAILDOMAIN
&& x.ORG_NAME == y.ORG_NAME;
}
public int GetHashCode(AccountDomain obj)
{
if (obj == null)
return 0;
return (obj.MAILDOMAIN ?? string.Empty).GetHashCode()
^ (397 * (obj.ORG_NAME ?? string.Empty).GetHashCode());
}
}
Then, you use it like this:
var result = mainDBAccountDomain.Except(manageEngineAccountDomain,
AccountDomainEqualityComparer.Instance)
.ToList();
I have implemented a comparison class for my custom class, so that I can use Intersect on two lists (StudentList1 and StudentList2). However, when I run the following code, I don't get any results.
Student:
class CompareStudent : IEqualityComparer<Student>
{
public bool Equals(Student x, Student y)
{
if (x.Age == y.Age && x.StudentName.ToLower() == y.StudentName.ToLower())
return true;
return false;
}
public int GetHashCode(Student obj)
{
return obj.GetHashCode();
}
}
class Student
{
public int StudentId{set;get;}
public string StudentName{set;get;}
public int Age{get;set;}
public int StandardId { get; set; }
}
Main:
IList<Student> StudentList1 = new List<Student>{
new Student{StudentId=1,StudentName="faisal",Age=29,StandardId=1},
new Student{StudentId=2,StudentName="qais",Age=19,StandardId=2},
new Student{StudentId=3,StudentName="ali",Age=19}
};
IList<Student> StudentList2 = new List<Student>{
new Student{StudentId=1,StudentName="faisal",Age=29,StandardId=1},
new Student{StudentId=2,StudentName="qais",Age=19,StandardId=2},
};
var NewStdList = StudentList1.Intersect(StudentList2, new CompareStudent());
Console.ReadLine();
The problem is within your GetHashCode() method, change it to:
public int GetHashCode(Student obj)
{
return obj.StudentId ^ obj.Age ^ obj.StandardId ^ obj.StudentName.Length;
}
In your current code, Equals is not called as the current GetHashCode() returns two different integers, so no point in calling Equals.
If GetHashCode of the first object is different than the second, the objects are not equal, if the result is the same, Equals is being called.
The GetHashCode I've written above is not ultimate, see What is the best algorithm for an overridden System.Object.GetHashCode? for more details on how to implement GetHashCode.
GetHashCode() is not (and cannot be) collision free, which is why the Equals method is required in the first place.
You are calling GetHashCode() on the base object, which will return a different value for the different references. I would implement it like this:
public override int GetHashCode(Student obj)
{
unchecked
{
return obj.StudentName.GetHashCode() + obj.Age.GetHashCode();
}
}
I am a bit puzzled about the behaviour of SortedSet, see following example:
public class Blah
{
public double Value { get; private set; }
public Blah(double value)
{
Value = value;
}
}
public class BlahComparer : Comparer<Blah>
{
public override int Compare(Blah x, Blah y)
{
return Comparer<double>.Default.Compare(x.Value, y.Value);
}
}
public static void main()
{
var blahs = new List<Blah> {new Blah(1), new Blah(2),
new Blah(3), new Blah(2)}
//contains all 4 entries
var set = new HashSet<Blah>(blahs);
//contains only Blah(1), Blah(2), Blah(3)
var sortedset = new SortedSet<Blah>(blahs, new BlahComparer());
}
So SortedSet discards entries if Compare(x,y) returns 0. Can I prevent this, such that my SortedSet behaves like HashSet and discards entries only if Equals() returns true?
Description
SortedSet: You have many elements you need to store, and you want to store them in a sorted order and also eliminate all duplicates from the data structure. The SortedSet type, which is part of the System.Collections.Generic namespace in the C# language and .NET Framework, provides this functionality.
According to MSDN Compare method returns
Less than zero if x is less than y.
Zero if x equals y.
Greater than zero if x is greater than y.
More Information
Dotnetperls - C# SortedSet Examples
MSDN: Compare Method
Update
If your Bla class implements IComparable and you want your list sorted you can do this.
var blahs = new List<Blah> {new Blah(1), new Blah(2),
new Blah(3), new Blah(2)};
blahs.Sort();
If your Bla class NOT implements IComparable and you want your list sorted you can use Linq (System.Linq namespace) for that.
blahs = blahs.OrderBy(x => x.MyProperty).ToList();
You can do this if you provide an alternate comparison when the Values are equal and the Compare method would otherwise return 0. In most cases this would probably just defer the problem instead of solving it. As others have noted, the SortedSet discards duplicates and when you provide a custom comparer it uses that to determine duplicity.
static void Main(string[] args)
{
var blahs = new List<Blah>
{
new Blah(1, 0), new Blah(2, 1),
new Blah(3, 2), new Blah(2, 3)
};
blahs.Add(blahs[0]);
//contains all 4 entries
var set = new HashSet<Blah>(blahs);
//contains all 4 entries
var sortedset = new SortedSet<Blah>(blahs, new BlahComparer());
}
}
public class Blah
{
public double Value { get; private set; }
public Blah(double value, int index)
{
Value = value;
Index = index;
}
public int Index { get; private set; }
public override string ToString()
{
return Value.ToString();
}
}
public class BlahComparer : Comparer<Blah>
{
public override int Compare(Blah x, Blah y)
{
// needs null checks
var referenceEquals = ReferenceEquals(x, y);
if (referenceEquals)
{
return 0;
}
var compare = Comparer<double>.Default.Compare(x.Value, y.Value);
if (compare == 0)
{
compare = Comparer<int>.Default.Compare(x.Index, y.Index);
}
return compare;
}
}
You can't find the other Blah(2) because you're using a Set.
Set - A collection of well defined and **distinct** objects
MultiSet, for instance, allows duplicate objects.
Sounds what you want is property-based sorting, but duplicate checking should be based on reference equality. To accomplish this (and if you don't mind that the memory consumption of your comparer can increase over time) we can add a fallback to the comparer that calculates the compare result based on IDs unique to the instances:
public class BlahComparer : Comparer<Blah>
{
private readonly ObjectIDGenerator _idGenerator = new();
public override int Compare(Blah x, Blah y)
{
int compareResult = Comparer<double>.Default.Compare(x.Value, y.Value);
if (compareResult == 0)
{
// Comparing hash codes is optional and is only done in order to potentially avoid using _idGenerator further below which is better for memory consumption.
compareResult =
Comparer<int>.Default.Compare(RuntimeHelpers.GetHashCode(x), RuntimeHelpers.GetHashCode(y));
if (compareResult == 0)
{
// HashCodes are the same but it might actually still be two different objects, so compare unique IDs:
compareResult = Comparer<long>.Default.Compare(_idGenerator.GetId(x, out bool _), _idGenerator.GetId(y, out bool _)); // This increases the memory consumption of the comparer for every newly encountered Blah
}
}
return compareResult;
}
}