Can iEqualityComparer be used to keep one object over another - c#

I am using linq-to-entities to populate a list of objects, I am then using .Distinct(new objectComparer()) to get distinct records. I want to ensure that certain objects are retained in the list over other objects.
Object:
public class Student
{
public int NSN { get; set; }
//removed properties not relevant
public int EnrolmentStatus { get; set; }
}
Comparer:
public class StudentComparer : IEqualityComparer<Student>
{
public bool Equals(Student x, Student y)
{
//Check whether the properties are equal.
return x.NSN == y.NSN;
}
public int GetHashCode(Student obj)
{
return obj.NSN.GetHashCode();
}
}
Query:
var students = (from t1 in Entities.Table1
join t2 in Entities.Table2
on t1.someID equals t2.someID
where (new int?[] { 3, 6, 10 }).Contains(t2.EnrolmentStatus)
select new Student
{
NSN = t1.NSN,
EnrolmentStatus = t2.EnrolmentStatus
}).ToList().Distinct(new StudentComparer());
This code returns distinct NSN values, however I would like to ensure that objects with EnrolmentStatus of 3 or 6 are preferred over EnrolmentStatus 10. Is there any way to do this with an iEqualityComparer or should I use a different approach?

https://code.google.com/p/morelinq/source/browse/MoreLinq/DistinctBy.cs
.DistinctBy(x => Student.NSN)

Related

How To group by a list of list and merge it into single list without repetition in C#?

I have a class named studentdetails and a property named students which is the list of studentdetails.
public class studentdetails
{
public int SubjectId {get ; set; }
public int studentId { get; set; }
public int ClassId { get; set; }
}
List<studentdetails> students = new List<studentdetails>()
{
new studentdetails() { studentId = 1, SubjectId = 1, ClassId = 1 },
new studentdetails() { studentId = 2, SubjectId = 2, ClassId = 1 },
new studentdetails() { studentId = 3, SubjectId = 1, ClassId = 2 },
new studentdetails() { studentId = 1, SubjectId = 3, ClassId = 2 },
new studentdetails() { studentId = 1, SubjectId = 3, ClassId = 1 }
};
I have to create a list of class which contains list of student(which contains a list of sbject as property ) as a property without repeating class,student and subject if its already exist.
example:
public class Class
{
public int ClassId { get; set; }
public Lits<student> students { get; set; }
}
public class student
{
public int StudentId { get; set; }
public Lits<Subject> subjects { get; set; }
}
public class Subject
{
public int SubjectId { get; set; }
}
For Example:
With refer to above studentdetails
class(1) -student(1) -subject(1)
-subject(3)
-student(2) -subject(2)
class(2) -student(3) -subject(1)
-student(1) -subject(3)
The next approach can be used to solve the problem:
List<Class> classes = students
// This GroupBy creates groups by ClassId:
// (ClassId) -> (List of Students).
.GroupBy(s => s.ClassId)
.Select(c => new Class
{
ClassId = c.Key,
Students = c
// This GroupBy for given Class creates groups by StudentId:
// (StudentId) -> (List of Subjects).
.GroupBy(s => s.StudentId)
.Select(s => new Student
{
StudentId = s.Key,
Subjects = s
// This GroupBy for given Class and Student removes
// duplicate values of SubjectId. If you can guarantee
// that for given Class and Student will not be duplicate
// values of SubjectId then you can remove this GroupBy.
// If you remove this GroupBy then you need to change
// expression inside Select to the following:
// new Subject { SubjectId = t.SubjectId }.
.GroupBy(t => t.SubjectId)
.Select(t => new Subject { SubjectId = t.Key })
.ToList()
}).ToList()
}).ToList();
Here is complete sample that shows this approach.
#Thaks asked in the comment:
If there is class name, student name and aсtivity name along with ids.
Then how can I map accordingly to the instance?
If you need additionally to map properties other then id then you should use the next overload of GroupBy method: GroupBy(keySelector, comparer). Using this method we can use instances of StudentDetails class as keys and specify comparer for them.
At first we should create comparer, a class that implements interface IEqualityComparer. In our sample we can use a single comparer class to perform all three GroupBy operations, because all our GroupBy operations are performed using int Id property. Typically each GroupBy operation uses its own comparer class because most of the time different GroupBy operations are performed using different keys (different data types, different number of groupping properties). Here is how we can implement our comparer:
// For demo I simplified implementation of the Equals and GetHashCode
// methods by excluding null checks. In the documentation of
// IEqualityComparer you can find implementation with null checks.
public class ComparerById<T> : IEqualityComparer<T>
{
private readonly Func<T, int> _keySelector;
public ComparerById(Func<T, int> keySelector) => _keySelector = keySelector;
public bool Equals(T x, T y) => _keySelector(x) == _keySelector(y);
public int GetHashCode(T obj) => _keySelector(obj);
}
And then using this comparer we can perform required GroupBy:
List<Class> classes = students
// Now key of each group has type StudentDetails, therefore later we
// will be able to use properties of StudentDetails such as ClassName.
// Here to compare keys of type StudentDetails we use comparer:
// new ComparerById<StudentDetails>(s => s.ClassId);
// It means that we create groups by ClassId.
.GroupBy(s => s, new ComparerById<StudentDetails>(s => s.ClassId))
.Select(c => new Class
{
ClassId = c.Key.ClassId,
ClassName = c.Key.ClassName,
Students = c
// Here we create groups by StudentId.
.GroupBy(s => s, new ComparerById<StudentDetails>(s => s.StudentId))
.Select(s => new Student
{
StudentId = s.Key.StudentId,
StudentName = s.Key.StudentName,
Subjects = s
// Here we create groups by SubjectId.
.GroupBy(t => t, new ComparerById<StudentDetails>(t => t.SubjectId))
.Select(t => new Subject {SubjectId = t.Key.SubjectId, SubjectName = t.Key.SubjectName})
.ToList()
}).ToList()
}).ToList();
Here is complete sample that shows this approach.

Given two lists of different objects, how to combine values from each into a new list based on a common Id, efficiently?

What I am trying to do is combine two lists of different types into a new type, on the Id property of each. Both lists have different properties that I need in the new list.
This snippet is working already, but I am not happy with the performance. Assuming both lists are the same length, and in whatever order they need to be, is it possible to do this more efficiently?
class A
{
public int Id { get; set; }
public string stuffA { get; set; }
//other properties that we aren't using
}
class B
{
public int Id { get; set; }
public string stuffB { get; set; }
//other properties we aren't using
}
class C
{
public int Id { get; set; }
public string stuffA { get; set; }
public string stuffB { get; set; }
}
public List<C> getList(List<A> listA, List<B> listB)
{
var listC = new List<C>();
foreach(var a in listA)
{
var b = listB.Where(x => x.Id == a.Id);
listC.Add(new C{ Id = a.Id, stuffA = a.stuffA, stuffB = b.stuffB});
}
return listC;
}
I've looked into the Enumerable.Zip method, which pairs up two lists in the order provided, but I can't seem to use that with objects. The only examples I can make work are with primitive types.
I've seen this question: How merge two lists of different objects? but this doesn't create a new type, only an anonymous type containing the old lists (I believe).
Any ideas?
Your looking for a simple Linq join, the order of items doesn't matter. For example:
public List<C> getList(List<A> listA, List<B> listB)
{
var listC = from a in listA
join b in listB on a.Id equals b.Id
select new C
{
Id = a.Id,
stuffA = a.stuffA,
stuffB = b.stuffB
};
return listC.ToList();
}
If you can ensure that both lists are in the same order with the same number of elements, you can loop through the lists and join based on index. This eliminates any searches required:
public List<C> getList(List<A> listA, List<B> listB)
{
var listC = new List<C>();
for(var i = 0; i < listA.Count; i++)
{
listC.Add(new C
{
Id = listA[i].Id,
stuffA = listA[i].stuffA,
stuffB = listB[i].stuffB
};
}
return listC;
}
This sounds a bit like a homework question but anyway... the trivial solution has a complexity of O(n^2). if efficiency is your goal, you need to use a structure that allows for fast searching like a hashtable, then your solution will be O(n). so add the elements of the first array to a hash table, then loop thru the second array and compose the result.

Linq: find elements with common properties but different types

I have two list with objects:
class Class1
{
public int Id { get; set; }
public string Name { get; set; }
public Guid UniqueIdentifier { get; set; }
public bool IsValid { get; set; }
}
class Class2
{
public int Identifier { get; set; }
public string Producer{ get; set; }
public Guid Guid { get; set; }
public int SupplierId { get; set; }
}
Is there a way to use linq to get the elements of type Class1 from the list that have the same Id (identifier) and Guid with the elements of type Class2 from the second list?
Here is one way to do it:
var result = list1
.Where(x => list2.Any(y => x.Id == y.Identifier && x.UniqueIdentifier == y.Guid))
.ToList();
Please note that this version is not optimized for large lists. If your lists are small, this is fine. If you have large lists, you need a solution that involves things like HashSets. Here is an example:
var list2HashSet = CreateHashset(list2.Select(x => new {x.Identifier, x.Guid}));
var result = list1
.Where(x => list2HashSet.Contains(new {Identifier = x.Id, Guid = x.UniqueIdentifier}))
.ToList();
Where CreateHashset is defined like this:
public static HashSet<T> CreateHashset<T>(IEnumerable<T> collection)
{
return new HashSet<T>(collection);
}
I had to create this simple method because the compiler is complaining that it cannot resolve the correct constructor overload. I am not really sure why.
You could try something like this:
var result = from item1 in list1
join item2 in list2 on
new { G = item1.UniqueIdentifier, Id = item1.Id }
equals new { G = item2.Guid, Id = item2.Identifier }
select new { item1, item2 };
foreach(var item in result)
{
Console.WriteLine($"Producer: {item.item2.Producer} with product: {item.item1.Name}");
}
Let's say you have the two lists
List<Class1> List1;
List<Class2> List2;
You can select all items of List1 containing the Id and Guid of the second list with
List1.Where(C1 => List2.Any(C2 => C1.Id == C2.Identifier && C1.UniqueIdentifier == C2.Guid));
Note that Guid is a class. If you don't want to check if C1.UniqueIdentifier and C2.Guid are exactly the same objects, you should implement IsEqual and use it like
List1.Where(C1 => List2.Any(C2 => C1.Id == C2.Identifier && C1.UniqueIdentifier.Equals(C2.Guid)));
If it suffice for you that the ids or the guids match, see the answer from Jeroen van Langen. Otherwise, I see two options:
Add a where clause afterwards, i.e.,
var result = from item1 in list1
join item2 in list2 on item1.Id equals item2.Identifier
where item1.UniqueIdentifier = item2.Guid
select new { item1, item2 };
Create a tuple class and join on the tuples of Guid and Id. You cannot reuse the .NET 4 tuple types (Tuple<,>), but you could reuse the C# 7 tuple types as they correctly implement equality.
Both versions should also be fine with large lists. Basically, the whole thing should scale as long as you use join.

Union two list by property

I would like to merge two list without duplicates. It should distinct only by one property.
I have a class:
public class Test
{
public int Id { get; set; }
public string Prop { get; set; }
public string Type { get; set; }
}
I have two lists which I would like to merge without duplicates by Type. So, firstly I want to take everything from list 1 and then from list 2 when Type doesn't exist in list 1.
I've tried union.
You've to use IEqualityComparer. See at : https://msdn.microsoft.com/en-us/library/ms132151%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
class Compare : IEqualityComparer<Test>
{
public bool Equals(Test x, Test y)
{
return x.Id == y.Id;
}
public int GetHashCode(Test codeh)
{
return codeh.Id.GetHashCode();
}
}
Then use
var union = list1.Union(list2).Distinct(new Compare()).ToList();
Using .NET 6 or higher, you can use the UnionBy() method which will merge 2 lists excluding duplicates by a property :
var mergedLists = firstList.UnionBy(secondList, u => u.property).ToList();
For more informations : Microsoft doc

Check if a list of objects contains another list (only have a property) in C# Linq

I have two Lists:
One list from database that contains objects -> SystemList
public class MyObject
{
public short Id { get; set; }
public string Name { get; set; }
}
One list from the user that contains ids from objects -> UserList
List<short> UserList;
I want to write in linq a query that check if all the items from the UserList are in the SystemList
I solved this temporally using this:
bool hasAllItems = true;
foreach (var g in UserList) {
hasAllItems = hasAllItems && SystemList.Any(a => a.Id == g);
}
I know this can be improved.
You can use Linq only:
bool hasAllItems = UserList.All(s => SystemList.Any(mo => mo.Id == s));

Categories

Resources