How to check for nested dictionary object equality? - c#

I have the following object:
public class Row
{
public int Id {get; set;}
public string Name {get; set;}
public Dictionary<Column, Cell> Cells {get; set;} = new Dictionary<Column, Cell>();
}
public class Cell
{
// ...
public string Value {get; set;}
}
Having a List of Rows, how can I remove all duplicates (by comparing cell's values)?
I tried adding the following:
public class RowEqualitycomparer : IEqualityComparer<Row>
{
public bool Equals(Row a, Row b)
{
return a.Cells.Equals(b.Cells);
}
public int GetHashCode(Row obj)
{
return obj.GetHashCode();
}
}

You can check cells in RowEqualitycomparer like this:
public bool Equals(Row? a, Row? b)
{
if (a is null || b is null) return false;
foreach (var cell in a.Cells)
{
var findItem = b.Cells[cell.Key]; // cell.Key is name
if (findItem is null) return false; // conflict in any column
if (findItem.Value != cell.Value.Value) return false; // c.Value is Cell
}
return true;
}

Related

Comparing record types with LINQ in C#

I am trying to implement record types to make comparing less complex. But I ran into issues comparing records to each other. I expect records to be equal when properties are equal to each other but this isn't the case in my code..
I have a record class called MyAlarm with some properties. It inherits from the interface IAlarm which is empty.
public record MyAlarm : IAlarm
{
public int Prop1 { get; init; }
public int Prop2 { get; init; }
}
A class called VisibleAlarm has the IAlarm as property plus some extra data.
public record VisibleAlarm
{
public IAlarm InternalAlarm { get; init; }
public string Message { get; set; }
}
In a class called AlarmService the VisibleAlarms are listed and updated when needed. This service has a List of visible alarms.
Further in this alarm service the list is checked if an alarm already exists depending on the IAlarm property. I have tried two methods:
public class AlarmService
{
private List<VisibleAlarm> _alarms = new();
// ... Other code that handles alarm stuff
private VisibleAlarm GetExistingAlarm1(IAlarm alarm)
{
return _alarms.FirstOrDefault(a => a.InternalAlarm == alarm);
}
private VisibleAlarm GetExistingAlarm2(IAlarm alarm)
{
foreach (var existingAlarm in _alarms)
{
if (existingAlarm.InternalAlarm == alarm)
{
return existingAlarm;
}
}
}
}
When debugging the code I see that the record properties of the existingAlarm.InternalAlarm and the given alarm match but still the GetExistingAlarm methods return both false. Am i missing something or must I implement a IEqualityComparer because of interface IAlarm?
Cheers
In your line of code:
return _alarms.FirstOrDefault(a => a.InternalAlarm == alarm);
the a.InternalAlarm == alarm is calling operator== rather than object.Equals() to do the comparison.
You can fix this by changing the code to:
return _alarms.FirstOrDefault(a => a.InternalAlarm.Equals(alarm));
The following program demonstrates the difference:
using System;
static class Program
{
public static void Main()
{
MyAlarm ma1 = new MyAlarm { Prop1 = 1, Prop2 = 2 };
MyAlarm ma2 = new MyAlarm { Prop1 = 1, Prop2 = 2 };
VisibleAlarm va1 = new VisibleAlarm { InternalAlarm = ma1, Message = "message" };
VisibleAlarm va2 = new VisibleAlarm { InternalAlarm = ma2, Message = "message" };
Console.WriteLine(ma1 == ma2); // True
Console.WriteLine(va1 == va2); // True
Console.WriteLine(va1.InternalAlarm == va2.InternalAlarm); // False
Console.WriteLine(va1.InternalAlarm.Equals(va2.InternalAlarm)); // True
}
public interface IAlarm
{
int Prop1 { get; }
int Prop2 { get; }
}
public record MyAlarm : IAlarm
{
public int Prop1 { get; init; }
public int Prop2 { get; init; }
}
public record VisibleAlarm
{
public IAlarm InternalAlarm { get; init; }
public string Message { get; set; }
}
}
Output:
True
True
False
True
The reason this isn't working for you is because InternalAlarm is of type IAlarm and not MyAlarm. If you change the type to MyAlarm, you'll get the comparison as true rather than false.
This is because IAlarm does not define an operator== (note: such an operator would be static).

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

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.

Recalculate the hash of the keys in Dictionary

I'm working on an approach to have a collection that efficiently can search based on more than one property. The sample code of the approach:
class SampleCollection
{
Dictionary<Sample, Sample> _dictItems;
public SampleCollection()
{
_dictItems = new Dictionary<Sample, Sample>(new SampleEqualityComparer());
}
public Sample FindById(int id)
{
return _dictItems[new Sample(id, string.Empty)];
}
public Sample FindByName(string name)
{
return _dictItems[new Sample(-1, name)];
}
}
class Sample
{
public Sample(int id, string name)
{
Id = id;
Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
public string ALotOfOtherProperties { get; set; }
}
class SampleEqualityComparer : IEqualityComparer<Sample>
{
public bool Equals(Sample x, Sample y)
{
if (x.Id >= 0 && y.Id >= 0)
{
return x.Id == y.Id;
}
return x.Name.Equals(y.Name, StringComparison.CurrentCultureIgnoreCase);
}
public int GetHashCode(Sample obj)
{
//try with only name now
return obj.Name.GetHashCode();
//return 0;
}
}
This approach works perfectly fine as long as the Name property is not modified. Understandably, the Hash value no longer matches the original item in the Dictionary when the Name is modified.
Is it possible to force the Dictionary to recalculate the hash of its keys or any other workaround if it is not possible directly.?
It is really a performance hit when using a custom class as a key. The handling can take a 10 times longer.
I suggest that you have a dictionary for name and one for id.
I recommend that you set your Name and Id setter to private like:public string Name { get; private set; }
class SampleCollection
{
public SampleCollection()
{
NameLookup = new Dictionary<string, List<Sample>>();
IdLookup = new Dictionary<int, Sample>();
}
private Dictionary<string, List<Sample>> NameLookup;
private Dictionary<int, Sample> IdLookup;
public void Add(Sample sample)
{
IdLookup.Add(sample.Id, Sample);
List<Sample> list;
if (!NameLookup.TryGetValue(sample.Name, out list))
NameLookup.Add(sample.Name, list = new List<Sample>());
list.Add(Sample);
}
public Sample FindById(int id)
{
Sample result;
IdLookup.TryGetValue(id, out result);
return result;
}
public IEnumerable<Sample> FindByName(string name)
{
List<Sample> list;
if (NameLookup.TryGetValue(name, out list))
foreach(var sample in list)
yield return sample;
}
}

c# petapoco object to datagridview, change column order

I have a bussiness object like this:
[PetaPoco.TableName("cars")]
[PetaPoco.PrimaryKey("id")]
public class Cars : CarObject
{
[DefaultValue(null)]
[DisplayName("Column Name")]
public string Color { get; set; }
[DefaultValue(null)]
public string Engine { get; set; }
[DefaultValue(null)]
public string BHP { get; set; }
[DefaultValue(null)]
public string Year { get; set; }
}
And I display in DataGridView like this:
List<Cars> ret = db.Query<Cars>("select * from cars").ToList();
if(ret != null)
dgv.DataSource = ret; // .OrderBy(o => o.Year).ToList();
However, seems that DGV put columns like in object (design time) order. I could alter this by using a loop with DGV DisplayIndex property but there is a simple solution, some attribute decorate?
Thanks in advance,
PS. I tried also using System.ComponentModel.DataAnnotations but seems that for WinForms isn't working. The DGV isn't able to bind that attributes.
[Display(Name = "Custom Name", Order = 2)]
Any hack? Thanks very much.
Thanks to Tim Van Wassenhove's elegant solution, I managed to do exactly what I wanted.
http://www.timvw.be/2007/02/04/control-the-order-of-properties-in-your-class/
It requires a modified BindingList<> class (I modified a bit more)
[AttributeUsage(AttributeTargets.Property)]
public class PropertyOrderAttribute : Attribute
{
private int order;
public PropertyOrderAttribute(int order)
{
this.order = order;
}
public int Order
{
get { return this.order; }
}
}
class PropertyOrderBindingList<T> : BindingList<T>, ITypedList
{
public PropertyOrderBindingList(List<T> list)
: base(list)
{
//
}
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
PropertyDescriptorCollection typePropertiesCollection = TypeDescriptor.GetProperties(typeof(T));
return typePropertiesCollection.Sort(new PropertyDescriptorComparer());
}
public string GetListName(PropertyDescriptor[] listAccessors)
{
return string.Format("A list with Properties for {0}", typeof(T).Name);
}
}
class PropertyDescriptorComparer : IComparer
{
public int Compare(object x, object y)
{
if (x == y) return 0;
if (x == null) return 1;
if (y == null) return -1;
PropertyDescriptor propertyDescriptorX = x as PropertyDescriptor;
PropertyDescriptor propertyDescriptorY = y as PropertyDescriptor;
PropertyOrderAttribute propertyOrderAttributeX = propertyDescriptorX.Attributes[typeof(PropertyOrderAttribute)] as PropertyOrderAttribute;
PropertyOrderAttribute propertyOrderAttributeY = propertyDescriptorY.Attributes[typeof(PropertyOrderAttribute)] as PropertyOrderAttribute;
if (propertyOrderAttributeX == propertyOrderAttributeY) return 0;
if (propertyOrderAttributeX == null) return 1;
if (propertyOrderAttributeY == null) return -1;
return propertyOrderAttributeX.Order.CompareTo(propertyOrderAttributeY.Order);
}
}
Now, just decorate the poco object attributes with order:
[DefaultValue(null)]
[DisplayName("Column Name")]
[PropertyOrder(3)]
public string Color { get; set; }
[DefaultValue(null)]
[PropertyOrder(1)]
public string Engine { get; set; }
[DefaultValue(null)]
[PropertyOrder(0)]
public string BHP { get; set; }
[DefaultValue(null)]
[PropertyOrder(2)]
public string Year { get; set; }
And query like this
List<Cars> ret2 = db.Query<Cars>("select * from cars").ToList();
PropertyOrderBindingList<Cars> ret = new PropertyOrderBindingList<Cars>(ret2);
The properties will be in custom order.

How to check for common objects between 2 generic lists

I have 2 lists that I need to check for common objects that are being passed to a generic wrapper.
The first list (selList) is a typed entity list. The ID field in this list is different, based on what the base type for the list being created.
The second list (masterList) is an anonymous IList that I know has 2 properties {ID, DESC} - ID (could be int or string), and description (string). I can get the value of the ID property in this list.
I would like to return an extension of the master list that has a boolean field indicating whether the item in the master list is contained in the selList.
I'm thinking that I'm somewhere along the lines of the Visitor pattern.
public class SelectionCriteria<T> : where T : class
{
public IList<T> EligibleList { get; private set; }
public IList LookupList { get; private set; }
}
LookupList = new List<object>
{
new { ID = "fid", DESC = "Record 1"},
new { ID = "Record2", DESC = "Record 2"},
new { ID = "Record3", DESC = "Record 3"},
new { ID = "Record4", DESC = "Record 4"},
};
EligibleList = new List<AssetClass>
{
new AssetClass { FEE_ID = "fid", ASSET_CLASS = "A" },
};
I should get the following results:
LookupList[0] == true
LookupList[1] == false
LookupList[2] == false
LookupList[3] == false
Is there a better way to solve this problem?
var results = LookupList.Select(l => EligibleList.Any(e => e.FEE_ID==l.ID))
.ToList();
Using this as a definition for SelectionCriteria<T>
public class SelectionCriteria<T>
where T : class
{
public IList<T> EligibleList { get; private set; }
public IList LookupList { get; private set; }
public SelectionCriteria(IList lookupList, IList<T> eligibleList)
{
LookupList = lookupList;
EligibleList = eligibleList;
}
public bool this[int index]
{
get
{
var element = LookupList[index];
foreach (var item in EligibleList)
{
if (item.Equals(element))
{
return true;
}
}
return false;
}
}
}
And this as a definition for AssetClass
public class AssetClass : IEquatable<AssetClass>
{
public string FEE_ID { get; set; }
public string ASSET_CLASS { get; set; }
public bool Equals(AssetClass other)
{
return !ReferenceEquals(other, null) && other.FEE_ID == FEE_ID && other.ASSET_CLASS == ASSET_CLASS;
}
//Check to see if obj is a value-equal instance of AssetClass, if it's not, proceed
// to doing some reflection checks to determine value-equality
public override bool Equals(object obj)
{
return Equals(obj as AssetClass) || PerformReflectionEqualityCheck(obj);
}
//Here's where we inspect whatever other thing we're comparing against
private bool PerformReflectionEqualityCheck(object o)
{
//If the other thing is null, there's nothing more to do, it's not equal
if (ReferenceEquals(o, null))
{
return false;
}
//Get the type of whatever we got passed
var oType = o.GetType();
//Find the ID property on it
var oID = oType.GetProperty("ID");
//Get the value of the property
var oIDValue = oID.GetValue(o, null);
//If the property type is string (so that it matches the type of FEE_ID on this class
// and the value of the strings are equal, then we're value-equal, otherwise, we're not
return oID.PropertyType == typeof (string) && FEE_ID == (string) oIDValue;
}
}
You can get elements that are found in the list of eligible items that exist in the list of lookup items like so:
for (var i = 0; i < assetClassSelectionCriteria.LookupList.Count; ++i)
{
Console.WriteLine("LookupList[{0}] == {1}", i, assetClassSelectionCriteria[i]);
}
You could also use the following for PerformReflectionEqualityCheck in AssetClass if you don't like seeing the reflection goodness
private bool PerformReflectionEqualityCheck(object o)
{
if (ReferenceEquals(o, null))
{
return false;
}
dynamic d = o;
try
{
return FEE_ID == (string) d.ID;
}
catch
{
return false;
}
}
If by "extension of the master list" you meant an extension method, then, instead of declaring an indexer on SelectionCriteria<T> to get the results, you could do something like this:
public static class SelectionCriteriaExtensions
{
public static bool IsLookupItemEligible<T>(this SelectionCriteria<T> set, int index)
where T : class
{
var element = set.LookupList[index];
foreach (var item in set.EligibleList)
{
if (item.Equals(element))
{
return true;
}
}
return false;
}
}
and call it like this:
assetClassSelectionCriteria.IsLookupItemEligible(0);

Categories

Resources