Reputation: 2139
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.
Upvotes: 1
Views: 2968
Reputation: 24626
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();
}
}
Upvotes: 2
Reputation: 7703
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 andVariableB
are equal andSancts
List are equal, considering that the Sancti
elements with the same Symbol
must have the same Percentage
to be equalYou 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 Input
s are equal:
test1.Inputs.SequenceEqual(test2.Inputs);
Upvotes: 2