I have a list of object like below
public class Person
{
public string Name {get; set;}
public int Age {get; set;}
}
public class SomeClass
{
public int DoSomething ()
{
int result;
List<Person> personList = new List<Person>();
personList.Add(new Person { //with 1 object, just to keep simple
Name = "Someone",
Age = 18});
Person eighteenYearsOld = _checkAge.FindEighteenYearsOld (personList);
int index = personList.IndexOf (eighteenYearsOld);
//do something
return result;
}
}
[TestMethod]
public void DoSomething_Test()
{
//Given:
//When: I call SomeClass object
Person eightnneYears = new Person {
Name = "Someone",
Age = 18};
_mockCheckAge.Setup (x => x.FindEighteenYearsOld(It.IsAny<List<Person>>())).Returns(eightnneYears);
_someClass = new SomeClass (_mockCheckAge.Object);
int result = _someClass.DoSomething();
//Then:
}
As I have mocked the FindEighteenYearsOld method it returns a Person object with same states which is presents in the personList. But when personList.IndexOf() executes it returns index -1, which is suppose to be 0. What should I do.
List.IndexOf uses Equals to find equal objects. If your class doesn't override it only references are compared. Since your two instances are not the same List.IndexOf returns -1.
So override Equals (and also always GetHashCode then):
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals (Object obj)
{
Person otherPerson = obj as Person;
return otherPerson != null
&& otherPerson.Name == Name
&& otherPerson.Age == Age;
}
// http://stackoverflow.com/a/263416/284240
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
hash = hash * 23 + (Name?.GetHashCode() ?? 0); // or: hash = hash * 23 + (Name == null ? 0 : Name.GetHashCode());
hash = hash * 23 + Age; ;
return hash;
}
}
}
Related
There is a class for passengers which contain string name, int age, string job etc. I made an array of this class, lets say it has 10 places.
I wanna find the oldest passenger.
My code doesn't work, because it is not possible to compare passenger[i] with an integer. I mean I need only the age in passenger[]
How to find the oldest one in passenger[]?
EDIT: The return value should be a passenger by his name and major, not only its age.
public Passenger Oldest()
{
int oldest = 0;
for (int i = 0; i < passengers.Length; i++)
{
if (passengers[i] > oldest)
{
oldest = passengers[i];
}
}
return oldest;
}
class Passenger
{
int age;
string name;
string major;
public Passenger(int _age, string _name, string _major)
{
age = _age;
name = _name;
major = _major;
}
}
Firstly, as mentioned by #Cid in comment to question all fields of Passenger are private (by default when modifier is not specified). You should either mark then as public (to access them outside declaring class) or better create public properties:
class Passenger
{
public int Age { get; set; } // public auto property
public string Name { get; set; } // public auto property
public string Major { get; set; } // public auto property
public Passenger(int age, string name, string major)
{
Age = age;
Name = name;
Major = major;
}
}
Secondly, you need to compare Age (proeprty, not whole object) of the passenger:
if (passengers[i].Age > oldest)
{
oldest = passengers[i].Age;
}
Also, you could use LINQ to find the oldest passenger:
var oldest = passengers.Max(item => item.Age);
Finally, to return the oldest passenger:
public Passenger Oldest()
{
// if no passengers -> return null
if (!passengers?.Any() ?? true)
{
return null;
}
var maxAge = passengers.Max(item => item.Age);
return passengers.First(item => item.Age == maxAge);
}
Also, as mentioned by #DmitryBychenko the method can be shortened to:
public Passenger Oldest()
{
// if no passengers -> return null
if (!passengers?.Any() ?? true)
{
return null;
}
return passengers.Aggregate((s, a) => s.Age > a.Age ? s : a);
}
or without LINQ:
public Passenger Oldest()
{
// if no passengers -> return null
if (passengers == null || passengers.Length == 0)
{
return null;
}
var maxAge = passengers[0].Age;
var oldestPassengerIndex = 0;
for (var i = 1; i < passengers.Length; i++)
{
if (passengers[i].Age > maxAge)
{
oldest = passengers[i].Age;
oldestPassengerIndex = i;
}
}
return passengers[oldestPassengerIndex];
}
Slightly more efficient version of Roman's answer, especially if the oldest passenger appears last in the list:
public Passenger Oldest()
{
if (passengers.Length == 1) return passengers[0];
var oldest = passengers[0];
for (int i = 1; i < passengers.Length; i++)
{
if (passengers[i].Age > oldest.Age) oldest = passengers[i];
}
return oldest;
}
This only ever requires a single iteration.
Using Linq is easy
var oldest=passengers.Max(x=>x.Age):
Otherwise
Passenger oldest=new Passenger(0,"","");
foreach (Passenger p in Passengers){
if (p.age>oldest.age) oldest=p;
}
I have a custom class named as City and this class has an Equals method. The SequenceEqual method works good when comparing arrays with assigned variables. The problem occurs when comparing two arrays that contains the elements formatted new City(). It results as false.
City class:
interface IGene : IEquatable<IGene>
{
string Name { get; set; }
int Index { get; set; }
}
class City : IGene
{
string name;
int index;
public City(string name, int index)
{
this.name = name;
this.index = index;
}
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public int Index
{
get
{
return index;
}
set
{
index = value;
}
}
public bool Equals(IGene other)
{
if (other == null && this == null)
return true;
if((other is City))
{
City c = other as City;
return c.Name == this.Name && c.Index == this.Index;
}
return false;
}
}
In the Test method below, the first comparing result arrayCompare1 is true and the second result arrayCompare2 is false. Both compare result must be true but there is an anormal stuation. How can I fix this problem?
Test code:
public void Test()
{
City c1 = new City("A", 1);
City c2 = new City("B", 2);
City[] arr1 = new City[] { c1, c2 };
City[] arr2 = new City[] { c1, c2 };
City[] arr3 = new City[] { new City("A", 1), new City("B", 2) };
City[] arr4 = new City[] { new City("A", 1), new City("B", 2) };
bool arrayCompare1 = arr1.SequenceEqual(arr2);
bool arrayCompare2 = arr3.SequenceEqual(arr4);
MessageBox.Show(arrayCompare1 + " " + arrayCompare2);
}
You need to override the Object.Equals somehow like this:
public override bool Equals(object other)
{
if (other is IGene)
return Equals((IGene)other);
return base.Equals(other);
}
You need to override bool Equals(object obj). Simplest addition to your code:
public override bool Equals(object obj)
{
return Equals(obj as IGene);
}
I want to create objects with 5 properties and each properties has 2 attributes. After that, I compare the objects if they are same, they will be grouped in same category.
Here is the code:
Item.cs
public class Item
{
public Item()
{
}
public SortProperty SortPropA { get; set; }
public SortProperty SortPropB { get; set; }
public SortProperty SortPropC { get; set; }
public SortProperty SortPropD { get; set; }
public SortProperty SortPropE { get; set; }
public string Name { get; set; }
public string Desc { get; set; }
}
SortProperty.cs
public class SortProperty : IEquatable<SortProperty>
{
public string PartName { get; set; }
public string GroupabilityID { get; set; }
public SortProperty()
{
}
public override int GetHashCode()
{
int hash = 19;
hash = hash * 31 + (GroupabilityID == null ? 0 : GroupabilityID.GetHashCode());
hash = hash * 31 + (PartName == null ? 0 : PartName.GetHashCode());
return hash;
}
public bool Equals(SortProperty obj)
{
return (obj == null) ?
false : (GroupabilityID == obj.GroupabilityID) || (PartName == obj.PartName);
}
public override bool Equals(Object obj)
{
SortProperty itemobj = obj as SortProperty;
return itemobj == null ? false : Equals(itemobj);
}
}
Program.cs (main class to test the coding)
class Program
{
static void Main(string[] args)
{
Item objA = new Item();
Item objB = new Item();
// ------ Object A
objA.Name = "Card1";
objA.Desc = "Product Test A";
//Property A
objA.SortPropA = new SortProperty();
objA.SortPropA.PartName = "Plastic A";
objA.SortPropA.GroupabilityID = "A1";
//Property B
objA.SortPropB = new SortProperty();
objA.SortPropB.PartName = "Color Green";
objA.SortPropB.GroupabilityID = "B2";
//Property C
objA.SortPropC = new SortProperty();
objA.SortPropC.PartName = "Visa";
objA.SortPropC.GroupabilityID = "C1";
// ------ Object B
objB.Name = "Card2";
objB.Desc = "Product Test B";
//Property A
objB.SortPropA = new SortProperty();
objB.SortPropA.PartName = "Plastic B";
objB.SortPropA.GroupabilityID = "A2";
//Property B
objB.SortPropB = new SortProperty();
objB.SortPropB.PartName = "Color Lime";
objB.SortPropB.GroupabilityID = "B1";
//Property C
objB.SortPropC = new SortProperty();
objB.SortPropC.PartName = "Visa";
objB.SortPropC.GroupabilityID = "C1";
bool isEqual = objA.Equals(objB);
if (isEqual == true)
Console.WriteLine("Is same");
else
Console.WriteLine("Is different");
Console.ReadKey();
}
}
The result should return true because there is a same property between objA and objB (SortPropc) but it return false.
I believe I have miss some logic part and I have sitting on chair for 4 hours but couldn't fix it. Can anyone please solve it?
The result should return true because there is a same property between objA and objB (SortPropc) but it return false.
You have just not implemented it. Read your code again and try to find the piece where you actually compare two Item instances. There's is none.
You should implement an Equals and GetHashCode method on your Item class, something like this:
public override bool Equals(Object obj)
{
var o = (Item)obj;
// Note: not error checking :-)
return SortPropA.Equals(o.SortPropA) ||
SortPropB.Equals(o.SortPropB) ||
SortPropC.Equals(o.SortPropC) ||
SortPropD.Equals(o.SortPropD) ||
SortPropE.Equals(o.SortPropE);
}
or create a class that implements IEqualityComparer<Item> that handles this requirement.
i have an list of persons with basecode and an array of locations. i need to eliminate the persons in the list having different basecode with same locations and keep persons with differend locations.
i tried using IEqualityComparer, and group by in linq, but i didn't succeed.
can you guys please advice me how to do it ?
this is my class structure
public class Person
{
public string Name { get; set; }
public List<Location> Locations { get; set; }
}
public class Location
{
public string Name { get; set; }
public string BaseCode { get; set; }
}
data example
Person 1
Name : John
Locations :
[0] Name : India , BaseCode : "AA12"
[1] Name : USA ,BaseCode : "AA14"
Person 2
Name : John
Locations :
[0] Name : India, BaseCode : "AA13"
[1] Name : USA ,BaseCode : "AA14"
Person 3
Name : John
Locations :
[0] Name : India, BaseCode : "AA16"
[1] Name : UK , BaseCode : "AA17"
I want to filter Person 2 from my list and keep person 1 and person 3. Please advice
Disclaimer: this solution doesn't specifically handle the same BaseCode with different/same locations; you didn't mention anything about this in your requirements.
IEqualityComparer<T> Route
The important parts here are the IEqualityComparer<T> implementations for both Person and Location:
class Program
{
static void Main(string[] args)
{
var p1 = new Person {Name ="John", BaseCode="AA12", Locations = new List<Location>
{
new Location { Name = "India" },
new Location { Name = "USA" }
}};
var p2 = new Person {Name ="John", BaseCode="AA13", Locations = new List<Location>
{
new Location { Name = "India" },
new Location { Name = "USA" }
}};
var p3 = new Person {Name ="John", BaseCode="AA14", Locations = new List<Location>
{
new Location { Name = "India" },
new Location { Name = "UK" }
}};
var persons = new List<Person> { p1, p2, p3 };
// Will not return p2.
var distinctPersons = persons.Distinct(new PersonComparer()).ToList();
Console.ReadLine();
}
}
public class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x == null || y == null)
return false;
bool samePerson = x.Name == y.Name;
bool sameLocations = !x.Locations
.Except(y.Locations, new LocationComparer())
.Any();
return samePerson && sameLocations;
}
public int GetHashCode(Person obj)
{
return obj.Name.GetHashCode();
}
}
public class LocationComparer : IEqualityComparer<Location>
{
public bool Equals(Location x, Location y)
{
if (x == null || y == null)
return false;
return x.Name == y.Name;
}
public int GetHashCode(Location obj)
{
return obj.Name.GetHashCode();
}
}
The PersonComparer uses the linq Except extension supplying the LocationComparer to produce a list of differences between two lists of locations.
The PersonComparer then feeds into the linq Distinct method.
IEquatable<T> Route
If you need to work with BaseCode being different counting towards being a "match", I don't think this route would work because of GetHashCode not giving you an opportunity to distinguish values.
An alternative solution is to implement IEquatable<T> on the classes themselves and also override GetHashCode, Distinct and Except will then honour this implementation:
public class Person : IEquatable<Person>
{
public string Name { get; set; }
public string BaseCode { get; set; }
public List<Location> Locations { get; set; }
public bool Equals(Person other)
{
if (other == null)
return false;
bool samePerson = Name == other.Name;
// This is simpler because of IEquatable<Location>
bool sameLocations = !Locations.Except(other.Locations).Any();
return samePerson && sameLocations;
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
public class Location : IEquatable<Location>
{
public string Name { get; set; }
public bool Equals(Location other)
{
if (other == null)
return false;
return Name == other.Name;
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
Which results in a simpler call:
var distinctPersons = persons.Distinct().ToList();
I'd be tempted to write something like the following. I've not checked that the y.Locations.Equals() works, but it should be simple to replace it for something that does the same job.
List<Person> personList = new List<Person>();
List<Person> deduplicatedPersonList = new List<Person>();
personList.ForEach(x =>
{
Person existingPerson = personList.Find(y =>
{
if (y.Locations.Equals(x.Locations))
return false;
return true;
});
if (existingPerson == null)
deduplicatedPersonList.Add(x);
});
You can make use of IEquatable Interface, and override Equal and GetHashCode method like this:
EDIT AFTER CHANGE IN QUESTION:
public class Location : IEquatable<Location>
{
public string Name { get; set; }
public string BaseCode { get; set;
public bool Equals(Location other)
{
if (Object.ReferenceEquals(other, null)) return false;
if (Object.ReferenceEquals(this, other)) return true;
return BaseCode.Equals(other.BaseCode);
}
public override int GetHashCode()
{
return BaseCode.GetHashCode();
}
}
So, now you can make use of Distinct on the List of Person, and it will only return distinct name and BaseCode.
var distinctListPerson = PersonList.Distinct().ToList();
You can read about this interface from MSDN
Adam's solutions are more "proper" way of handling it. But if you want to do it with LINQ, then something like this shoud also do it (note that the code expects locations to be ordered and takes strings as identifiers):
persons
.GroupBy(x => x.Name)
.SelectMany(x => x)
.GroupBy(y => string.Concat(y.Locations.Select(z => z.Name)))
.SelectMany(x => x
.GroupBy(y => string.Concat(y.Locations.Select(z => z.BaseCode)))
.Select(x => x.First());
I want to remove duplicates in a list using following code, but it does not work. Anyone could enlighten me? Thanks.
public sealed class Pairing
{
public int Index { get; private set; }
public int Length { get; private set; }
public int Offset { get; private set; }
public Pairing(int index, int length, int offset)
{
Index = index;
Length = length;
Offset = offset;
}
}
class MyComparer : IEqualityComparer<Pairing>
{
public bool Equals(Pairing x, Pairing y)
{
return ((x.Index == y.Index) && (x.Length == y.Length) && (x.Offset == y.Offset));
}
public int GetHashCode(Pairing obj)
{
return obj.GetHashCode();
}
}
class Program
{
static void Main(string[] args)
{
List<Pairing> ps = new List<Pairing>();
ps.Add(new Pairing(2, 4, 14));
ps.Add(new Pairing(1, 2, 4));
ps.Add(new Pairing(2, 4, 14));
var unique = ps.Distinct(new MyComparer());
foreach (Pairing p in unique)
{
Console.WriteLine("{0}\t{1}\t{2}", p.Index, p.Length, p.Offset);
}
Console.ReadLine();
}
}
According to the example on the IEnumerable.Distinct page you will need to implement GetHashCode() so that the equal objects return the same hashcode. If you do not override GetHashCode() in your object it is not guaranteed to return the same hashcode.
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(Product product)
{
//Check whether the object is null
if (Object.ReferenceEquals(product, null)) return 0;
//Get hash code for the Name field if it is not null.
int hashProductName = product.Name == null ? 0 : product.Name.GetHashCode();
//Get hash code for the Code field.
int hashProductCode = product.Code.GetHashCode();
//Calculate the hash code for the product.
return hashProductName ^ hashProductCode;
}
Defining GetHashCode to return a unique answer causes the Distinct to work as expected;
public int GetHashCode(Pairing obj)
{
if (obj==null) return 0;
var hc1 = obj.Index.GetHashCode();
var hc2 = obj.Length.GetHashCode();
var hc3 = obj.Offset.GetHashCode();
return hc1 ^ hc2 ^ hc3;
}