Linq Except considering only one property - c#

I have two lists of object.
List<object1> obj1 = new List<object1>();
List<object2> obj2 = new List<object2>();
I want to do this:
obj2 = obj2.Except(obj1).ToList();
However, by reading other questions similar to mine, I understand that this doesn't work unless I override Equals.
I do not want to do that, but both obj2 and obj1 have a string property that is sufficient to see whether they are equal. If obj2.StringProperty is equivalent to obj1.StringProperty then the two can be considered equal.
Is there any way I can use Except, but by using only the string property to compare?

The Except method requires that the two collection types involved have the same element type. In this case the element types are different (object1 and object2) hence Except isn't really an option. A better method to use here is Where
obj2 = obj2
.Where(x => !obj1.Any(y => y.StringProperty == x.StringProperty))
.ToList();

In .Net 6 you can use .ExceptBy() from System.Linq.
If your classes contain the following properties:
public class Object1
{
public string String { get; set; }
}
public class Object2
{
public string String { get; set; }
}
.ExceptBy() can be used like this to compare the two string properties:
var objects1 = new List<Object1>();
var objects2 = new List<Object2>();
// Populate lists
objects2 = objects2
.ExceptBy(
objects1.Select(obj1 => obj1.String)
obj2 => obj2.String) // selecting the String property of Object2 for comparison
.ToList();
Example fiddle here.
(Using .ExceptBy() has also been suggested by #mjwills in their comment, via MoreLinq.)

Related

Intersect doesn't work between Lists

I have a little strange problem. I use Visual Studio and I am developing a project with C#.
I have two custom classes "Attr" and "FD" and I use lists that includes their objects e.g.
List<Attr> attrList = new List<Attr>();
List<FD> fdList = new List<FD>();
So when I try to find the intersection of two lists the result is not what I expect. To make it more simple I tried to Intersect similar Objects and the result is wrong again. What is going wrong?
This is the fd. It is an object of class FD.
This is the ff which is also an object of FD class.
As you can see these object contains exactly the same values.
The method GetLeft() returns a list that contains objects of class Attr.
So when I try to find the intersection between those two lists (fd.GetLeft() and ff.GetLeft() ) the result is nothing (it should be a list that contains an Attr object "A").
What did I miss?
P.S. These screenshots are from the debugg mode in Visual Studio.
In order to use Intersect I suggest implementing IEqualityComparer<T>, something like this :
public class FD
{
public string Name { get; set; }
}
static void Main()
{
List<FD> fdList1 = new List<FD>();
fdList1.Add(new FD { Name = "a" });
List<FD> fdList2 = new List<FD>();
fdList2.Add(new FD { Name = "a" });
IEnumerable<FD> fd = fdList1.Intersect<FD>(fdList2, new ComparerFd()).ToList();
}
And the CamparerFd should be like this :
public class ComparerFd : IEqualityComparer<FD>
{
public bool Equals(FD x, FD y)
{
return x.Name == y.Name;
}
public int GetHashCode(FD obj)
{
if(obj == null) return 0;
return obj.Name.GetHashCode();//Or whatever way to get hash code
}
}
If you created your own class, and did not override the Equals-method in that class, the Intersect-method will only compare the references of the objects, and not the properties.
Take the following, really simple class:
class MyClass
{
int Value { get; set; }
public MyClass(int value)
{
this.Value = value;
}
}
Now, create two lists, with both containing one object. The properties of the objects are the same, but the instances are not:
var list1 = new List<MyClass>
{
new MyClass(5)
};
var list2 = new List<MyClass>
{
new MyClass(5)
};
So the following will happen:
list1[0].Equals(list2[0]); // false
list1.Intersect(list2); // No matches
If you want these methods to compare the properties of your MyClass-objects, implement IEqualityComparer<MyClass>, e.g. change the classes signature to:
class MyClass : IEqualityComparer<MyClass>
{
..
}
Alternatively, you can just override Equals and GetHashCode, as then these methods will be called as default IEqualityComparer.
See the this answer on how to properly override Equals and GetHashCode.

Compare items of two different type of lists c#

I want to compare two different lists with one property in common (Name). I have tried things like the .Contains() method, but that does not work because I am dealing with lists of two different objects. I have tried the solutions in questions like:
C# Compare Two Lists of Different Objects
C#, compare two different type of lists
compare different type of list in c#
But these solutions do not work because my lists expect a Property or Animal object and not a "string", this is where I get stuck.
I have two classes:
public class Animal
{
public string Name = string.Empty;
public string XChromosome = string.Empty;
public string YChromosome = string.Empty;
}
public class Properties
{
public string Name = string.Empty;
public string Prop1 = string.Empty;
public string Prop2 = string.Empty;
}
The two lists look like this:
List<Animal> = "name", "xposition", "yposition"
"name1", "xposition1", "yposition1" etc..
List<Properties> = "name", "prop1","prop2"
"name1", "prop3", "prop4" etc..
What I would like to do do is, compare these two lists and if the "Name" matches I would like to get the content of both lists belonging to this name. I also tried using a HashSet or a Dictionary, but this is not what I am looking for.
You can join two lists on Name property and get matches as anonymous object:
from a in animals
join p in properties on a.Name equals p.Name
select new {
a.Name,
a.XChromosome,
a.YChromosome,
p.Prop1,
p.Prop2
}
You can try it yourself in .NET Fiddle.
NOTE: If you want to get animal info no matter if there is match in properties, or you can have more than one match for given animal, then you need to use group join (check this fiddle for details):
from a in animals
join p in properties on a.Name equals p.Name into g
from p in g.DefaultIfEmpty()
select new {
a.Name,
a.XChromosome,
a.YChromosome,
Prop1 = p?.Prop1,
Prop2 = p?.Prop2
}
That wil return each pair of animal - property joined by name. If no matching property found, then Prop1 and Prop2 will have null values by default (though you can provide any default value you want).
Sergey Berezovsky's solution is good, deserves all the upvotes and to be the accepted answer, however, I would like to add an alternative as well. You could create a class called Named, like this:
class Named {
public string Name = string.Empty;
}
and inherit your classes from it, like this:
public class Animal : Named
{
public string XChromosome = string.Empty;
public string YChromosome = string.Empty;
}
public class Properties : Named
{
public string Prop1 = string.Empty;
public string Prop2 = string.Empty;
}
This will enable you to use List<Named> as type which will allow you to do your task easily either with LINQ or with a cycle. The benefit of this approach is that you will not duplicate the same member in both classes and if you are going to have more cases when you need to do something like this or you are going to have more similar members and/or methods, then you will not duplicate your code.

How to retrieve object properties with LINQ?

Let's say I have this object:
public class Foo
{
public string FirstProp {get;set;}
public string SecondProp {get;set;}
public string ThirdProp {get;set;}
}
Now I would like to retrieve only the FirstProp and the SecondProp from that object and concat all the property values into one string.
I have one solution in mind which would't be clean imo. Here it is:
var foo = new Foo("test1","test2","test3");
var propertyNames = new[] {"FirstProp", "SecondProp"};
var properties = foo.GetType().GetProperties().Where(x => propertyNames.Contains(x.Name));
//Then loop through each retrieved property and concat the string
So basically I am just looking for a cleaner solution where I wouldn't be dependent on an array of string.
var foo = new Foo(...);
var result = string.Join(",", new[]{ foo.FirstProp, foo.SecondProp });
Does this suffice? Or do you need Reflection for dynamic typing? If so, one can also supply MemberExpressions to dynamically get the values. Are you dealing with a collection of instances? Do you need this functionality extracted in a helper-method with a parameter for the needed properties?

Sort ArrayList with custom comparison

I am trying to sort an ArrayList using c#. When the ArrayList contains comparable objects, it is possible to sort with using list.Sort() but I need to sort an ArrayList which contains non-comparable objects. For example, let's say the object is Ring and it has an attribute property Price. Then I need to sort the ArrayList to the price order. If is is possible to select ascending or descending that will more helpful. Thank You!
Blockquote
arrAtdMon = **(ArrayList)**hashTb[unixMon];
if (arrAtdMon != null)
monCount = arrAtdMon.Count;
int[] arrayMax = { monCount, tueCount, wedCount, thuCount, friCount };
int maxValue = arrayMax.Max();
KidAttendance valMon = null;
string monTagName = string.Empty;
Blockquote
above array list is to be sorted it self.
You can do this by implementing IComparer interface:-
public class Ring : IComparer
{
public decimal Price { get; set; }
public int Compare(object x, object y)
{
return ((Ring)x).Price.CompareTo(((Ring)y).Price);
}
}
Working Fiddle.
First, you really should be using the List<T> class, not ArrayList. Doing so wouldn't solve your problem, but it would make the code less fragile and more easy to maintain.
As for the specific question, you want to do something like this…
Assume:
class Ring { public decimal Price { get; set; } }
Then:
ArrayList list = ...; // Initialized as some collection of Ring instances
list.Sort(Comparer.Create((r1, r2) => r1.Price.CompareTo(r2.Price)));
This creates a new Comparer instance using the Comparison<T> of (r1, r2) => r1.Price.CompareTo(r2.Price). That is, for each pair of objects being compared, compare the price of the first with the price of the second.
Assuming that these objects share a base class or an interface with the price property you should be able to do something like this:
// Base class with price property, could also be an shared interface
public abstract class Product
{
public decimal Price{get;set;}
}
public class Ring : Product
{
}
public class Bag : Product
{
}
// Some test data
var myUnsortedArray = new Product[]{new Ring{Price = 1.2m}, new Bag{Price=2.5m}};
// Easy sort with LINQ
var sortedProducts = myUnsortedArray.OrderBy(p => p.Price).ToArray();
var sortedProductsDescending = myUnsortedArray.OrderByDescending(p => p.Price).ToArray();
UPDATE
I just realised that the question is about ArrayLists and have the changed solution below:
// Some test data
var myUnsortedArrayList = new ArrayList{new Ring{Price = 1.2m}, new Bag{Price=2.5m}};
// Easy sort with LINQ
var sortedProducts = myUnsortedArrayList.OfType<Product>().OrderBy(p => p.Price).ToArray();
var sortedProductsDescending = myUnsortedArrayList.OfType<Product>().OrderByDescending(p => p.Price).ToArray();
To sort an set of objects, the object needs to be Comparable and you can set up the comparison you'd like in the CompareTo() method:
IComparable information here

Exclude a collection from another by lambda

This is my type:
public class myType
{
public int Id { get; set; }
public string name { get; set; }
}
And there is 2 collection of this type:
List<myType> FristList= //fill ;
List<myType> Excludelist= //fill;
And I need to exclude Excludelist from FristList something like the following:
List<myType> targetList =
FirstList.Where(m=>m.Id not in (Excludelist.Select(t=>t.Id));
What is your suggestion about the exact lambda expression of the above query?
Three options. One without any changes:
var excludeIds = new HashSet<int>(excludeList.Select(x => x.Id));
var targetList = firstList.Where(x => !excludeIds.Contains(x.Id)).ToList();
Alternatively, either override Equals and GetHashCode and use:
var targetList = firstList.Except(excludeList).ToList();
Or write an IEqualityComparer<MyType> which compares by IDs, and use:
var targetList = firstList.Except(excludeList, comparer).ToList();
The second and third options are definitely nicer IMO, particularly if you need to do this sort of work in various places.

Categories