How to .GroupBy() by Id and by list property? - c#

I have these classes:
public class AlertEvaluation
{
public string AlertId { get; set; }
public ICollection<EvaluatedTag> EvaluatedTags { get; set; }
public string TransactionId { get; set; }
public EvaluationStatus EvaluationStatus { get; set; }
public DateTime EvaluationDate { get; set; }
}
public class EvaluatedTag
{
public string Id { get; set; }
public string Name { get; set; }
}
And I would like to get a list of alert evaluations grouped by AlertId, and by EvaluatedTags, meaning that I would like to compare and group evaluations that not only have the same AlertId, but to also have the same list of EvaluatedTags. (And also get the last evaluation in time)
I tried this:
var evaluationsGroupedAndOrdered = evaluations.GroupBy(x => new { x.AlertSettingId, x.EvaluatedLabels })
.Select(x => x.OrderByDescending(z => z.EvaluationDate ).FirstOrDefault()).ToList();
But of course, the comparing of list properties like that did not work.
I read something about adding an equality comparer in GroupBy, which would mean comparing the lists inside the objects right? But I'm not sure of how to implement it in the right way.
I tried (based on GroupBy on complex object (e.g. List<T>)) :
public class AlertEvaluationComparer : IEqualityComparer<AlertEvaluation>
{
public bool Equals(AlertEvaluation x, AlertEvaluation y)
{
return x.AlertId == y.AlertId && x.EvaluatedTags.OrderBy(val => val.Name).SequenceEqual(y.EvaluatedTags.OrderBy(val => val.Name));
}
public int GetHashCode(AlertSettingEvaluation x)
{
return x.AlertId.GetHashCode() ^ x.EvaluatedTags.Aggregate(0, (a, y) => a ^ y.GetHashCode());
}
}
But did not work either.. Maybe because my list EvaluatedTags is not a list of strings but of individual objects.
Does anybody have a nice solution for this?

A typical way to compare two lists is to use the System.Linq exension method, SequenceEquals. This method returns true if both lists contain the same items, in the same order.
In order to make this work with an IEnumerable<EvaluatedTag>, we need to have a way to compare instances of the EvaluatedTag class for equality (determining if two items are the same) and for sorting (since the lists need to have their items in the same order).
To do this, we can override Equals and GetHashCode and implement IComparable<EvaluatedTag> (and might as well do IEquatable<EvaluatedTag> for completeness):
public class EvaluatedTag : IEquatable<EvaluatedTag>, IComparable<EvaluatedTag>
{
public string Id { get; set; }
public string Name { get; set; }
public int CompareTo(EvaluatedTag other)
{
if (other == null) return -1;
var result = string.CompareOrdinal(Id, other.Id);
return result == 0 ? string.CompareOrdinal(Name, other.Name) : result;
}
public bool Equals(EvaluatedTag other)
{
return other != null &&
string.Equals(other.Id, Id) &&
string.Equals(other.Name, Name);
}
public override bool Equals(object obj)
{
return Equals(obj as EvaluatedTag);
}
public override int GetHashCode()
{
return Id.GetHashCode() * 17 +
Name.GetHashCode() * 17;
}
}
Now we can use this in the custom comparer you have in your question, for sorting and comparing the EvaluatedTags:
public class AlertEvaluationComparer : IEqualityComparer<AlertEvaluation>
{
// Return true if the AlertIds are equal, and the EvaluatedTags
// contain the same items (call OrderBy to ensure they're in
// the same order before calling SequenceEqual).
public bool Equals(AlertEvaluation x, AlertEvaluation y)
{
if (x == null) return y == null;
if (y == null) return false;
if (!string.Equals(x.AlertId, y.AlertId)) return false;
if (x.EvaluatedTags == null) return y.EvaluatedTags == null;
if (y.EvaluatedTags == null) return false;
return x.EvaluatedTags.OrderBy(et => et)
.SequenceEqual(y.EvaluatedTags.OrderBy(et => et));
}
// Use the same properties in GetHashCode that were used in Equals
public int GetHashCode(AlertEvaluation obj)
{
return obj.AlertId?.GetHashCode() ?? 0 * 17 +
obj.EvaluatedTags?.Sum(et => et.GetHashCode() * 17) ?? 0;
}
}
And finally we can pass your AlertEvaluationComparer to the GroupBy method to group our items:
var evaluationsGroupedAndOrdered = evaluations
.GroupBy(ae => ae, new AlertEvaluationComparer())
.OrderBy(group => group.Key.EvaluationDate)
.ToList();

Here's a go at it, getting away from Linq a bit to make it easier to build the groups one at a time while leveraging sorting:
// Build groups by using a combination of AlertId and EvaluatedTags hashcode as group key
var groupMap = new Dictionary<string, SortedSet<AlertEvaluation>>();
foreach (var item in evals)
{
var combinedKey = item.AlertId + EvaluatedTag.GetCollectionHashCode(item.EvaluatedTags);
if (groupMap.TryGetValue(combinedKey, out SortedSet<AlertEvaluation>? groupItems))
{
// Add to existing group
groupItems.Add(item);
}
else
{
// Create new group
groupMap.Add(combinedKey, new SortedSet<AlertEvaluation> { item });
}
}
// Get a list of groupings already sorted ascending by EvaluationDate
List<SortedSet<AlertEvaluation>>? groups = groupMap.Values.ToList();
This assumes that the classes implement IComparable and Equals/GetHashCode to facilitate sorting:
public class AlertEvaluation : IComparable<AlertEvaluation>
{
public string AlertId { get; set; }
public ICollection<EvaluatedTag> EvaluatedTags { get; set; }
public string TransactionId { get; set; }
public EvaluationStatus EvaluationStatus { get; set; }
public DateTime EvaluationDate { get; set; }
// Used by SortedSet
public int CompareTo(AlertEvaluation? other)
{
if (other is null)
{
return 1;
}
return EvaluationDate.CompareTo(other.EvaluationDate);
}
}
public class EvaluatedTag : IEquatable<EvaluatedTag?>
{
public string Id { get; set; }
public string Name { get; set; }
public bool Equals(EvaluatedTag? other) => other != null && Id == other.Id && Name == other.Name;
public override int GetHashCode() => HashCode.Combine(Id, Name);
// Helper to get a hash of item collection
public static int GetCollectionHashCode(ICollection<EvaluatedTag> items)
{
var code = new HashCode();
foreach (var item in items.OrderBy(i => i.Id))
{
code.Add(item);
}
return code.ToHashCode();
}
}
By the way, I'm using the fancy new HashCode class in .NET Core to override hash codes.

Related

Improve comparing between two collections - LINQ? C#

Edit:
_existingRatings and _targetRatings are both collections from a database, where Rating and RatingType are both key/values.
I've got two RatingDto collections that I need to compare each other against: _existingRates and _targetRates. I'll be simply doing a comparison on the RatingTypeId, then need to check if the same one in the _targetRates has an empty Rating string.
As it's Monday morning, my brain is still asleep and I'm sure there's a better and simpler way to do this with LINQ. This is what I'm currently doing:
class RatingDto
{
public int RatingTypeId { get; set; }
public string RatingType { get; set; }
public string Rating { get; set; }
}
foreach (var existing in _existingRatings)
{
foreach(var target in _targetRatings)
{
if(existing.RatingTypeId == target.RatingTypeId)
{
if(target.Rating == string.Empty)
{
_targetHasMissingRatings = true;
}
}
}
}
This should be okay as the maximum amount is around 7 in each collection, but I'm sure there's a better and cleaner way with LINQ.
That's should what you are looking for :
(i love to use string.IsNullOrEmpty rather than cmp string.Empty)
_targetHasMissingRatings = _existingRatings.Any(er => string.IsNullOrEmpty(_targetRatings.FirstOrDefault(tr => er.RatingTypeId == tr.RatingTypeId)?.Rating));
Use IComparable so you can use OrderBy on the entire class or simply compare two instances of the class.
List<RatingDto> ordered = _existingRatings.OrderBy(x => x).ToList();
See code below :
class RatingDto : IComparable<RatingDto>
{
public int RatingTypeId { get; set; }
public string RatingType { get; set; }
public string Rating { get; set; }
public int CompareTo(RatingDto other)
{
if (this.RatingTypeId != other.RatingTypeId)
{
return this.RatingTypeId.CompareTo(other.RatingTypeId);
}
else
{
return this.RatingType.CompareTo(other.RatingType);
}
}
}
Code would look like this :
List<RatingDto> _existingRatings = new List<RatingDto>();
List<RatingDto> _targetRatings = new List<RatingDto>();
Boolean _targetHasMissingRatings = false;
foreach (var existing in _existingRatings)
{
foreach (var target in _targetRatings)
{
if (existing == target)
{
_targetHasMissingRatings = true;
break;
}
}
if (_targetHasMissingRatings == true) break;
}

C# Comparison of objects [duplicate]

This question already has answers here:
What's the best strategy for Equals and GetHashCode?
(7 answers)
Closed 9 years ago.
I have never really done this before so i was hoping that someone could show me the correct what of implementing a override of Except() and GetHashCode() for my class.
I'm trying to modify the class so that i can use the LINQ Except() method.
public class RecommendationDTO{public Guid RecommendationId { get; set; }
public Guid ProfileId { get; set; }
public Guid ReferenceId { get; set; }
public int TypeId { get; set; }
public IList<TagDTO> Tags { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public bool IsActive { get; set; }
public object ReferencedObject { get; set; }
public bool IsSystemRecommendation { get; set; }
public int VisibilityScore { get; set; }
public RecommendationDTO()
{
}
public RecommendationDTO(Guid recommendationid,
Guid profileid,
Guid referenceid,
int typeid,
IList<TagDTO> tags,
DateTime createdon,
DateTime modifiedon,
bool isactive,
object referencedobject)
{
RecommendationId = recommendationid;
ProfileId = profileid;
ReferenceId = referenceid;
TypeId = typeid;
Tags = tags;
CreatedOn = createdon;
ModifiedOn = modifiedon;
ReferencedObject = referencedobject;
IsActive = isactive;
}
public override bool Equals(System.Object obj)
{
// If parameter is null return false.
if (obj == null)
{
return false;
}
// If parameter cannot be cast to Point return false.
RecommendationDTO p = obj as RecommendationDTO;
if ((System.Object)p == null)
{
return false;
}
// Return true if the fields match:
return (ReferenceId == p.ReferenceId);// && (y == p.y);
}
public bool Equals(RecommendationDTO p)
{
// If parameter is null return false:
if ((object)p == null)
{
return false;
}
// Return true if the fields match:
return (ReferenceId == p.ReferenceId);// && (y == p.y);
}
//public override int GetHashCode()
//{
// return ReferenceId;// ^ y;
//}}
I have taken a look at http://msdn.microsoft.com/en-us/library/ms173147.aspx but i was hoping someone could show me within my own example.
Any help would be appreciated.
Thank you
You can override Equals() and GetHashCode() on your class like this:
public override bool Equals(object obj)
{
var item = obj as RecommendationDTO;
if (item == null)
{
return false;
}
return this.RecommendationId.Equals(item.RecommendationId);
}
public override int GetHashCode()
{
return this.RecommendationId.GetHashCode();
}
public override bool Equals(System.Object obj)
{
// Check if the object is a RecommendationDTO.
// The initial null check is unnecessary as the cast will result in null
// if obj is null to start with.
var recommendationDTO = obj as RecommendationDTO;
if (recommendationDTO == null)
{
// If it is null then it is not equal to this instance.
return false;
}
// Instances are considered equal if the ReferenceId matches.
return this.ReferenceId == recommendationDTO.ReferenceId;
}
public override int GetHashCode()
{
// Returning the hashcode of the Guid used for the reference id will be
// sufficient and would only cause a problem if RecommendationDTO objects
// were stored in a non-generic hash set along side other guid instances
// which is very unlikely!
return this.ReferenceId.GetHashCode();
}
Be careful when using a primary key as your test for equality in overriding Equals() because it only works AFTER the object has been persisted. Prior to that your objects don't have primary keys yet and the IDs of the ones in memory are all zero.
I use base.Equals() if either of the object IDs is zero but there probably is a more robust way.

Distinct with encapsulated equality

I have a collection of objects where I want to find distinct values based on several properties.
I could do this:
var distinct = myValues.GroupBy(p => new { A = p.P1, B = p.P2 });
But I want to encapsulate the equality sementics. Something like this:
public interface IKey<T>
{
bool KeyEquals(T other);
}
public class MyClass : IKey<MyClass>
{
public string P1 { get; set; }
public string P2 { get; set; }
public bool KeyEquals(MyClass other)
{
if(object.ReferenceEquals(this, other)
return true;
if(other == null)
return false;
return this.P1 == other.P1 && this.P2 == other.P2;
}
}
Is there an O(N) way to get distinct values using my KeyEquals function?
If you can't change MyClass, you can implement an IEqualityComparer:
class MyClassComparer : IEqualityComparer<MyClass>
{
public bool Equals(MyClass m1, MyClass m2)
{
return m1.KeyEquals(m2);
}
public int GetHashCode(MyClass m)
{
return (m.P1.GetHashCode() *23 ) + (m.P2.GetHashCode() * 17);
}
}
And pass it to GroupBy
var distinct = myValues.GroupBy(p => p, new MyClassComparer());

Finding the distinct values of a dictionary

I have a GroupSummary class that has some properties like this in it:
public class GroupsSummary
{
public bool FooMethod()
{
////
}
public bool UsedRow { get; set; }
public string GroupTin { get; set; }
public string PayToZip_4 { get; set; }
public string PayToName { get; set; }
public string PayToStr1 { get; set; }
public string PayToStr2 { get; set; }
public string PayToCity { get; set; }
public string PayToState { get; set; }
public bool UrgentCare_YN { get; set; }
}
Then I have a Dictionary like <string, List<GroupsSummary>
For each of these dictionary items I want to find all the distinct addresses but the properties of this class that define a distinct address for me are
PayToStr1,PayToStr2,PayToCity,PayToState
I know as far as I can say something like mydictionartItem.select(t => t).Distinct().ToList() but I think that will compare all the properties of this class which is wrong. So how should I solve this?
var newDict = dict.ToDictionary(
x=>x.Key,
v=>v.Value.GroupBy(x=>new{x.PayToStr1, x.PayToStr2, x.PayToCity, x.PayToState})
.Select(x=>x.First())
.ToList());
implement IEquatable<T> interface on the GroupsSummary Class. More information can be found here
IEquatable
defines a method Equals. Remember to overload the GetHashCode method as well
Write your own IEqualityComparer, like so:
public class GroupsSummaryComparer : IEqualityComparer<GroupsSummary>
{
public bool Equals(GroupsSummary x, GroupsSummary y)
{
if (object.ReferenceEquals(x, y))
return true;
else if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null))
return false;
return x.PayToStr1 == y.PayToStr1 && x.PayToStr2 == y.PayToStr2 && x.PayToCity == y.PayToCity && x.PayToState == y.PayToState;
}
public int GetHashCode(GroupsSummary obj)
{
if (obj == null)
return 0;
int code;
if (obj.PayToStr1 != null)
code ^= obj.PayToStr1.GetHashCode();
if (obj.PayToStr2 != null)
code ^= obj.PayToStr2.GetHashCode();
if (obj.PayToCity != null)
code ^= obj.PayToCity.GetHashCode();
if (obj.PayToState != null)
code ^= obj.PayToState.GetHashCode();
return code;
}
}
Then you can pass it to Distinct
This may be safer than implementing IEquatable<GroupsSummary> directly on the class, since, in other situations, you may want to test them for full equality.
The easiest way would be to implement an IEqualityComparer<GroupsSummary>
Then you can say something like
HashSet<GroupSummary> unique = new HashSet<GroupsSummary>(
myDict.Values ,
new MyGroupsSummaryEqualityComparer()
) ;

How to Distinct List<OrderDetails> based on one values from OrderDetails

I have order details:
public class OrderDetails
{
public int OrderId { get; set; }
// [DataMember]
public int ProductId { get; set; }
}
Now if I do a distinct like below , it returns everything:
List<OrderDetails> orderDetails = new List<OrderDetails>();
---------------------------------
return orderDetails.Distinct();
But if I do distinct like :
List<OrderDetails> orderDetails = new List<OrderDetails>();
---------------------------------
return orderDetails.Select(x => x.OrderId).Distinct();
Then I get only Order Ids.
How can I get the Distinct Orderdetails(both OrderID and ProductId) based on OrderId
You can use the overload of Distinct which takes IEqualityComparer<TSource> as comparer.
You can define it as:
public class OrderDetailsEqualityComparer : IEqualityComparer<OrderDetails>
{
public bool Equals(OrderDetails x, OrderDetails y)
{
if (object.ReferenceEquals(x, y))
{
return true;
}
if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null))
{
return false;
}
return (x.OrderId == y.OrderId );
}
public int GetHashCode(Product obj)
{
return obj.OrderId.GetHashCode();
}
}
and use it as:
var osrderedOrderDetails =
orderDetails.Distinct(new OrderDetailsEqualityComparer());
You could create a method that takes an Order Id, and return the list as below...
public List<OrderDetails> GetDistinctOrderDetails(int orderId)
{
List<OrderDetails> orderDetails = GetAllOrders();
return orderDetails.Where(x => x.OrderId == orderId).Distinct();
}
return orderdetails.Where(a=>a.OrderID == _inputvalue).ToArray();
"toarray" can be any of the collection types what ever you need
unless your function returns Ienumerable<> then you don't need it
using distinct() is not a good idea as it will drop rows
Try below code:
Distinct with OrderId
return orderDetails.GroupBy(x => x.OrderId).Select(g => g.First()).ToList();
Distinct with OrderId and ProductId
return orderDetails.GroupBy(x => new {x.OrderId, x.ProductId}).Select(g => g.First()).ToList();
Implement IEquatable interface:
public class OrderDetails : IEquatable<OrderDetails>
{
public int OrderId { get; set; }
public int ProductId { get; set; }
public bool Equals(OrderDetails anotherOrder)
{
return this.OrderId.Equals(anotherOrder.OrderId);
}
public override int GetHashCode()
{
return this.OrderId;
}
}
Now, try orderDetails.Distinct();

Categories

Resources