xGeo
xGeo

Reputation: 2139

How to check if properties of two objects are equal

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

Answers (2)

Abion47
Abion47

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

Pikoh
Pikoh

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:

  1. VariableA are equal and
  2. VariableB are equal and
  3. 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);

Upvotes: 2

Related Questions