How to use IList.Contains() method to find an object - c#

I have a list of Persons inside a Company Class.
public class Company{
IList<Person> persons;
}
Then I have a List of companies,
IList<Company> companies;
Now I have a name (say "Lasantha"). If this name is a part of the name of any person in a company, I want to find that company. I tried using companies.Contains() method. I overrided the object.Equals method, inside the Person class as this,
public override bool Equals(object o)
{
var other = o as Person;
return this.Name.ToLower().Contains(other.Name.ToLower());
}
But this is not working. It's not calling this Equal method as well. Can someone help me please.
Thank you.

Overriding the equality comparison in this manner is wrong.
Equality should be transitive: if
"FooBar".Equals("Foo") == true
then it must also hold that
"Foo".Equals("FooBar") == true
However, in this case you are using Contains which will break the transitivity because "FooBar" contains "Foo", but "Foo" does not contain "FooBar". Apart from that, you should not override the Equals method on a class unless each and every last comparison between objects of that class will have the same semantics (which in this case seems highly dubious).
So, given that overriding Equals is not the solution, what should you do?
One convenient way is to use LINQ:
var companiesWithPeopleWithLasanthaInTheirName =
companies.Where(c => c.persons.Any(p => p.Name.Contains("Lasantha")));
However the above comparison is case-sensitive, so if you need it to not be you have to tweak it; there is an answer in this question: Case insensitive 'Contains(string)'

You can use Linq, something like
var temp = companies.Where(p => p.People.Any(q => q.Name.Contains("Lasantha")));
Here is the full example;
public class Example
{
private IList<Company> companies;
public Example()
{
Person p1 = new Person(){Name = "Lasantha"};
Person p2 = new Person(){Name = "Red Kid"};
Company comp = new Company();
comp.People = new List<Person>();
comp.People.Add(p1);
comp.People.Add(p2);
companies = new List<Company>();
companies.Add(comp);
var temp = companies.Where(p => p.People.Any(q => q.Name.Contains("Lasantha")));
}
}
public class Company
{
public IList<Person> People
{
get;
set;
}
}
public class Person
{
public string Name { get; set; }
}

You need to overload Equals so that it takes a Person as parameter. Otherwise it will default to reference comparison.
public override bool Equals(Person p)
{
//...
}
Then as msdn states, you may need to provide CompareTo (IComparable) as well.

I don't think you need to override Equals when you can get this as
you should be using where to filter the companies and then use Any on the Person list to get the ones matching your name criteria
companies.Where(c => c.persons.Any(p => p.Name.Contains("Value here"));

You're searching for
a list of characters ("Lasantha")
inside a list of Persons
inside a list of Companies
Well, other contributors already described how to do it correctly

Related

How to update the property to true or false based on comparing two list

I have two list
class obj1
{
public string country{ get; set; }
public string region{ get; set; }
}
class obj2
{
public string country{ get; set; }
public string region { get; set; }
public string XYZ { get; set; }
public bool ToBeChanged{ get; set; }
}
first list looks like:
List<obj1> alist = new List<obj1>();
alist.Add("US", "NC");
alist.Add("US", "SC");
alist.Add("US", "NY");
second list (List<obj2> alist2) may make 1000 of entries with many combination of country and region.
I need to update the property "ToBeChanged" to "True" if second (alist2) list properties (country and region) matches to first(alist1) and false in otherwise.
Please help.
Thanks,
Vaibhav
Two points from the comments, and my thoughts:
Some aren't sure exactly what your matching criteria is. But to me it seems fairly clear that you're matching on 'country' and 'region'. Nevertheless, in the future, state this explicitly.
You got one comment criticizing your choice of variable names. That criticism is fully justified. Code is far easier to maintain when you have little hints as to what it's doing, and variable names are crucial for that.
Now, regarding my particular solution:
In the code below, I've renamed some of your objects to make them clear in their purpose. I'd like to rename 'obj2', but I'll leave that to you because I'm not exactly sure what you're intending to do with it, and I definitely don't know what 'XYZ' is for. Here are the renamed classes, with some added constructors to aid in list construction.
class RegionInfo {
public RegionInfo(string country, string region) {
this.country = country;
this.region = region;
}
public string country{ get; set; }
public string region{ get; set; }
}
class obj2 {
public obj2 (string country, string region, string XYZ) {
this.country = country;
this.region = region;
this.XYZ = XYZ;
}
public string country{ get; set; }
public string region { get; set; }
public string XYZ { get; set; }
public bool ToBeChanged{ get; set; }
}
I'm using a LINQ Join to match the two lists, outputting only the 'obj2' side of the join, and then looping the result to toggle the 'ToBeChanged' value.
var regionInfos = new List<RegionInfo>() {
new RegionInfo("US", "NC"),
new RegionInfo("US", "SC"),
new RegionInfo("US", "NY")
};
var obj2s = new List<obj2> {
new obj2("US", "NC", "What am I for?"),
new obj2("US", "SC", "Like, am I supposed to be the new value?"),
new obj2("CA", "OT", "XYZ doesn't have a stated purpose")
};
var obj2sToChange = obj2s
.Join(
regionInfos,
o2 => new { o2.country, o2.region },
reg => new { reg.country, reg.region },
(o2,reg) => o2
);
foreach (var obj2 in obj2sToChange)
obj2.ToBeChanged = true;
obj2s.Dump(); // using Linqpad, but you do what works to display
This results in:
country
region
XYZ
ToBeChanged
US
NC
What am I for?
True
US
SC
Like, am I supposed to be the new value?
True
CA
OT
XYZ doesn't have a stated purpose
False
First of all, with LINQ you can never change the source. You can only extract data from the source. After that you can use the extracted data to update the source.
I need to update the property "ToBeChanged" to "True" if second (alist2) list properties (country and region) matches to first(alist1) and false in otherwise.
This is not a proper requirement. alist1 is a sequence of obj1 objects. I think, that you want the property ToBeChanged of a certain obj2 to be true if any of the obj1 items in alist1 has a [country, region] combination that matches the [country, region] combination of the obj2 concerned.
requirement Get all obj2 in alist2, that have a [country, region] combination that matches any of the [country, region] combinations of the obj1 objects in alist1.
You probably thought about using Where for this. Something like "Where [country, region] combination in the other list". Whenever you need to find out if an item is in another list, consider to use one of the overloads of Enumerable.Contains
The problem is, that the [Country, Region] combination in every obj2 can be converted to an object of class obj1, but if you want to check if they are equal, you will have a compare by reference, while you want a compare by value.
There are two solutions for this:
create an EqualityComparer that compares obj1 by Value
create [Country, Region] as anonymous type. Anonymous types always compare by value.
The latter is the most easy, so we'll do that one first.
Use anonymous types for comparison
First convert alist into anonymous type containing [Country, Region] combinations:
var eligibleCountryRegionCombinations = alist.Select(obj1 => new
{
Country = obj1.Country,
Region = obj1.Region,
});
Note that I don't use ToList at the end: the enumerable is created, but the sequence has not been enumerated yet. In LINQ terms this is called lazy or deferred execution.
IEnumerable<obj2> obj2sThatNeedToBeChanged = alist2.Select(obj2 => new
{
CountryRegionCombination = new
{
Country = obj2.Country,
Region = obj2.Region,
},
Original = obj2,
})
.Where(item => eligibleCountryRegionCombinations.Contains(
item.CountryRegionCombination))
.Select(item => item.Original);
CountryRegionCombination is an anonymous type of the same type as the anonymous items in eligibleCountryRegionCombinations. Therefore you can use Contains. Because the items are anonymous type, the equality comparison is comparison by value.
The final select will remove the anonymous type, and keep only the Original.
Note that the query is still not enumerated.
foreach (var obj2 in obj2sThatNeedToBeChanged.ToList())
{
obj2.ToBeChanged = true;
}
It can be dangerous to change the source that you are enumerating. In this case it is not a problem, because the field that you change is not used to create the enumeration. Still I think it is safer, because of possible future changes, to do a ToList before you start changing the source.
Create an equality comparer
One of the overload of Enumerable.Contains has a parameter comparer. This expects an IEqualityComparer<obj1>
class Obj1Comparer : EqualityComparer<obj1>
{
public static IEqualityComparer<obj1> ByValue {get;} = new Obj1Comparer();
private static IEqualityComparer<string> CountryComparer => StringComparer.OrdinalIgnoreCase;
private static IEqualityComparer<string> RegionComparer => StringComparer.OrdinalIgnoreCase;
public override bool Equals (obj1 x, obj1 y)
{
if (x == null) return y == null; // true if both null, false if x null, but y not null
if (y == null) return false; // because x not null
// optimization:
if (Object.ReferenceEquals(x, y)) return true;
if (x.GetType() != y.GetType()) return false;
return CountryComparer.Equals(x.Country, y.Country)
&& RegionComparer.Equals(x.Region, y.Region);
}
To make it easy to change equality of countries, I created a separate comparer for countries and for regions. So if later you want to compare case sensitive, or if you change Country from string to a foreign key to a table of countries, then changes will be minimal.
You also need to override GetHashCode. If x equals y, then GetHashCode should rerturn the same value. Not the other way round: if x and y different they may return the same hash code. However, code will be more efficient if you have more different Hash codes.
public override int GetHashCode (obj1 x)
{
if (x == null) return 87966354; // just a number
return CountryComparer.GetHashCode(x.Country)
^ RegionComparer.GetHashCode(x.Region);
}
Which HashCode you return depends on how often this will be called, for instance in dictionaries, comparers like Contains, etc.
How "different" are the Countries and Regions? A different Country will probably also mean a different region. So maybe it is efficient enough if you only calculate the Hash code for the Country. If a Country has many, many regions, then it will probably be better to calculate the hash code for regions as well If a Region is only in one Country (OberAmmerGau is probably only in Germany), or in only a few Regions (how many regions "New Amsterdam" will there be?), then you won't have to check the Country at all.
Because we have an equality comparer, we don't need to convert alist to an anonymous type, we can specify that Contains should compare by value.
IEqualityComparer<obj1> comparer = Obj1Comparer.ByValue;
IEnumerable<obj2> obj2sThatNeedToBeChanged = alist2.Select(obj2 => new
{
Obj1 = new Obj1
{
Country = obj2.Country,
Region = obj2.Region,
},
Original = obj2,
})
.Where(item => alist.Contains(item.CountryRegionCombination, comparer))
.Select(item => item.Original);
Fast method: Extension method
The fastest method, and maybe also the most simple one, is to create an extension method.
private static IEqualityComparer<string> CountryComparer => StringComparer.OrdinalIgnoreCase;
private static IEqualityComparer<string> RegionComparer => StringComparer.OrdinalIgnoreCase;
public static IEnumerable<Obj2> WhereSameLocation(
this IEnumerable<Obj2> source,
IEnumerable<Obj1> obj1Items)
{
// TODO: what to do if source == null?
foreach (Obj2 obj2 in source)
{
// check if there is any obj1 with same [Country, Region]
if (obj1Items
.Where(obj1 => CountryComparer.Equals(obj2.Country, obj1.Country)
&& RegionComparer.Equals(obj2.Region, obj1.Region))
.Any())
{
yield return obj2;
}
}
}
Usage:
IEnumerable<Obj1> alist = ...
IEnumerable<Obj2> alist2 = ...
IEnumerable<obj2> obj2sThatNeedToBeChanged = alist2.WhereSameLocation(alist);

Add List<> To Another List<> by filtering

I have one model class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
When I adding two list with this:
List<Person> people1 = new List<Person> {
new Person() { Id = 1, Name = "Name1" },
new Person() { Id = 2, Name = "Name2" },
new Person() { Id = 3, Name = "Name3" },
};
List<Person> people2 = new List<Person> {
new Person() { Id = 1, Name = "Name1" },
new Person() { Id = 4, Name = "Name4" },
new Person() { Id = 5, Name = "Name5" },
};
people1.AddRange(people2);
If person in people2 has the same id in person in people1, I don't want it added. How can I do that?
You can use LINQ for this fairly easily but inefficiently:
people1.AddRange(people2.Where(p2 => !people1.Any(p1 => p1.Id == p2.Id)));
Or you could create a set of IDs first:
HashSet<int> people1Ids = new HashSet<int>(people1.Select(p1 => p1.Id));
people1.AddRange(people2.Where(p2 => !people1Ids.Contains(p2.id));
The first approach is obviously simpler, but if your lists get large, it could become slow, because for every element in people2, it'll look through every element in people1.
The second approach will be significantly faster if people1 is large. If it's people2 that's large instead, you won't get much benefit. For example, if people1 only contains a couple of people, then looking in a hash set for an ID won't be much faster than looking through the list.
You could take an entirely different approach though. If you make your Person type implement IEquatable<Person> based on ID - or create an IEqualityComparer<Person> that does so, and if you don't so much need the existing list to be modified, so much as you need "the union of the two lists", and if you don't care about the order, and if all the entries in each list are unique or you don't mind duplicates being removed, you could just use:
// Specify the comparer as another argument if you need to.
// You could call ToList on the result if you need a list.
var union = people1.Union(people2);
(That's a lot of conditions for that solution, but they may well all be valid.)
You can use the Union operator for this with a custom IEqualityComparer. This will create a new list which is a combination of the other 2. Implementing a customer IEqualityComparer gives you control over what constitutes the same record.
var allPeople = people1.Union(people2, new PersonComparer());
public class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
// ommited null checks etc
return x.Id == y.Id;
}
public int GetHashCode(Person obj)
{
// ommited null checks etc
return obj.Id.GetHashCode()
}
}
Use this:
people1.AddRange(people2.Except(people1));
But you first need to Override Equal and GetHashCode in Person class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
if (!(obj is Person))
return false;
Person p = (Person)obj;
return (p.Id == Id && p.Name == Name);
}
public override int GetHashCode()
{
return String.Format("{0}|{1}", Id, Name).GetHashCode();
}
}
Or you can use a a custom equality comparer and then use Distinct(new DistinctItemComparer()):
public class DistinctItemComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.Id == y.Id &&
x.Name == y.Name;
}
public int GetHashCode(Person obj)
{
return obj.Id.GetHashCode() ^
obj.Name.GetHashCode();
}
}
Then use it like this:
people1.AddRange(people2.Except(people1, new DistinctItemComparer()));
If you just need to distinct based on Id you can excluse the Name from this two methods.
Based on this, The second approach seems better, as the Microsoft already suggested to Do not overload operator equals on reference types.
Have you thought about using a Dictionary instead?
You can use the Id as a Key and it won't allow duplicate Keys to exist?
The following code is a way I've used before:
var dictionary = people1.ToDictionary(x => x.Id, x => x);
foreach(var person in people2)
{
if(!dictionary.ContainsKey(item.Id))
{
dictionary.Add(item.Id, item);
}
}
There may be a better way of doing it but this has worked for me.
This way when you add an item to the dictionary it won't let you add something with the same Id.
Also check out HashSets as they do a similar thing.
I think good solution here is to use LINQ. Its quite simple and short code to write :
people1.AddRange(people2.Where(x => !people1.Any(y => x.Id == y.Id)));

Can I use LINQ to check if objects in a list have a unique ID?

say I have a list containing objects like this one:
public class Person
{
private string _name;
private string _id;
private int _age;
public Person
{
}
// Accessors
}
public class ManipulatePerson
{
Person person = new Person();
List<Person> personList = new List<Person>;
// Assign values
private void PopulateList();
{
// Loop
personList.Add(person);
// Check if every Person has a unique ID
}
}
and I wanted to check that each Person had a unique ID. I would like to return a boolean true/false depending on whether or not the IDs are unique. Is this something I can achieve with LINQ?
Note that you can even leverage directly an HashSet<>:
var hs = new HashSet<string>();
bool areAllPeopleUnique = personList.All(x => hs.Add(x.Id));
(and is the code that I normally use)
It has the advantage that on the best case (presence of some duplicates) it will stop before analyzing all the personList collection.
I would use Distinct and then check against the counts for example:
bool bAreAllPeopleUnique = (personList.Distinct(p => p.ID).Count == personList.Count);
However as #Ian commented you will need to add a property to the Person class so that you can access the Id like so:
public string ID
{
get { return _id; }
}
A 'nicer' way to implement this would be to add a method like so:
private bool AreAllPeopleUnique(IEnumerable<Person> people)
{
return (personList.Distinct(p => p.ID).Count == personList.Count);
}
NOTE: The method takes in an IEnumerable not a list so that any class implementing that interface can use the method.
One of best ways to do so is overriding Equals and GetHashCode, and implementing IEquatable<T>:
public class Person : IEquatable<Person>
{
public string Id { get; set; }
public override bool Equals(object some) => Equals(some as Person);
public override bool GetHashCode() => Id != null ? Id.GetHashCode() : 0;
public bool Equals(Person person) => person != null && person.UniqueId == UniqueId;
}
Now you can use HashSet<T> to store unique objects and it will be impossible that you store duplicates. And, in addition, if you try to add a duplicated item, Add will return false.
NOTE: My IEquatable<T>, and Equals/GetHashCode overrides are very basic, but this sample implementation should give you a good hint on how to elegantly handle your scenario.
You can check this Q&A to get an idea on how to implement GetHashCode What is the best algorithm for an overridden System.Object.GetHashCode?
Maybe this other Q&A might be interesitng for you: Why is it important to override GetHashCode when Equals method is overridden?
You can use GroupBy for getting unique items:
var result = personList.GroupBy(p=> p.Id)
.Select(grp => grp.First())
.ToList();

Want to return a list of members

I have the following objects
public class Club
{
private List<Person> _Members = new List<Person>();
public int Id { get; set; }
public string Name { get; set; }
public List<Person> Members
{
get { return _Members; }
set { _Members = value; }
}
}
Now I have a List<Club> named clubsList and I want to generate a List<Person> that should contain a list of unique members.
For example, John Smith may be a member of Club A and Club B so he'll be in the Members list for both clubs. However, I want John Smith in my list only once.
I'm trying to generate this list using LINQ and I'd appreciate some help with it.
var uniqueMembers = clubsList.Select(x => x.Members).ToList()...???
SelectMany and Disctinct is a way to go:
clubsList
.SelectMany(_ => _.Members)
.Distinct()
.ToList();
but to force distinct to work properly, you may want an overload with equality comparer, because by default Distinct compares references for reference types, and I don't know, how Club.Members are being populated
(I assume, that you don't want to override Equals and GetHashCode for Person).

Abstract an IEqualityComparer implementation or override the default comparer to use Distinct method

I'm trying to find a distinct List<Author> given a List<BlogPost> where each BlogPost has an Author property. I've found the Distinct() extension method in generics and I'm trying to use it. First, let me explain my loop and where I want to use it, then I'll explain my classes and where I'm having trouble.
Trying to use distinct here
public List<Author> GetAuthors() {
List<BlogPost> posts = GetBlogPosts();
var authors = new List<Author>();
foreach (var bp in posts) {
authors.Add(bp.Author);
}
return authors.Distinct().ToList();
}
Based on what I've read on MSDN, Distinct() either uses the default comparer or a passed in comparer. I was hoping (I obviosuly don't know if this is doable) to write a comparer in one spot and be able to use it for all of my classes since they all compare by the exact same equality operation (which compares the GUID property of each class).
All of my classes inherit from the BasePage class:
public class BasePage : System.Web.UI.Page, IBaseTemplate, IEquatable<IBaseTemplate>, IEqualityComparer<IBaseTemplate>
public class Author : BasePage
public class BlogPost : BasePage
My equals method implemented in BasePage compares the GUID property which is unique to each. When I call Distinct() on an Author it doesn't seem to work. Is there any way I can wrap up the comparer in one place and always be able to use it rather than having to write something like class AuhorComparer : IEqualityComparer<Auhor> since I'd then need to write the same thing for each class, every time I want to use Distinct(). Or can I override the default comparer somehow so I don't have to pass anything to Distinct()?
The Distinct operation is probably not the best solution here because you end up building a potentially very big list with duplicates only to then immediately shrink it to distinct elements. It's probably better to just start with a HashSet<Author> to avoid building up the large list.
public List<Author> GetAuthors() {
HashSet<Author> authorSet = new HashSet<Author>();
foreach (var author in GetBlogPosts().Select(x => x.Author)) {
authorSet.Add(author);
}
return authorSet.ToList();
}
If you do want to use Distinct then the best route is to implement IEquatable on the Author type. When not given an explicit IEqualityComparer the Distinct and other LINQ methods will eventually default into using the IEquatable implementation on the type. Usually through EqualityComprare<T>.Default
Overriden Equals should work for you. One thing that might be going wrong is that GetHashCode is not overridden alongside Equals, which the framework guidelines dictate should happen.
The code only shows the main idea, which, I hope, will be useful.
public class Repository
{
public List<Author> GetAuthors()
{
var authors = new List<Author>
{
new Author{Name = "Author 1"},
new Author{Name = "Author 2"},
new Author{Name = "Author 1"}
};
return authors.Distinct(new CustomComparer<Author>()).ToList();
}
public List<BlogPost> GetBlogPosts()
{
var blogPosts = new List<BlogPost>
{
new BlogPost {Text = "Text 1"},
new BlogPost {Text = "Text 2"},
new BlogPost {Text = "Text 1"}
};
return blogPosts.Distinct(new CustomComparer<BlogPost>()).ToList();
}
}
//This comparer is required only one.
public class CustomComparer<T> : IEqualityComparer<T> where T : class
{
public bool Equals(T x, T y)
{
if (y == null && x == null)
{
return true;
}
if (y == null || x == null)
{
return false;
}
if (x is Author && y is Author)
{
return ((Author)(object)x).Name == ((Author)(object)y).Name;
}
if (x is BlogPost && y is BlogPost)
{
return ((BlogPost)(object)x).Text == ((BlogPost)(object)y).Text;
}
//for next class add comparing logic here
return false;
}
public int GetHashCode(T obj)
{
return 0; // actual generating hash code should be here
}
}
public class Author
{
public string Name { get; set; }
}
public class BlogPost
{
public string Text { get; set; }
}

Categories

Resources