.NET IComparable: how to implement - c#

I have a collection of objects I need to order but not sure how.
There is a string property called, say Prop1, that I want to sort by. And I want to sort based on a List of strings which contain all possible values of Prop1.
List<string> precedence = new List<string>() { "firstPrecedence", "secondPrecedence" ....
How would I implement my CompareTo(object obj) method?
I'm trying with this but don't really know what i'm doing!
public int CompareTo(object obj)
{
List<string> precedence = new List<string>() { "firstPrecedence", "secondPrecedence", "thirdPrecedence" };
Filter filterOther = obj as Filter;
foreach (var item in precedence)
{
return String.Compare(filterOther.FilterValue, item);
}
return 0;
}

Well, if your precedence list is known at compile time and you can use it, then you can compare the indexes of the values you are sorting:
private static List<string> Precedence = new List<string>() { "item1", "item2", "item3" }; // etc
public int CompareTo(object obj)
{
Filter item = obj as Filter; // Assume not null.
int otherIndex = Precedence.IndexOf(item.FilterValue);
int thisIndex = Precedence.IndexOf(this.FilterValue); // Assume 'this' is a Filter
// This may need to be otherIndex.CompareTo(thisIndex) depending on the direction of sort you want.
return thisIndex.CompareTo(otherIndex);
}
If the FilterValue value is not in the list, IndexOf will return -1, which will still work in the sorting implementation here, but may sort at the top or bottom of the list... I can never remember which!
Note that the CompareTo method returns either 0, something less than 0, or something greater than 0. Usually, -1, 0, and 1.
Also, there is a generic IComparable<> which will allow you to achieve this in a more strongly-typed way:
public class Filter : IComparable<Filter>
{
}
And I'm sure some clever person will give you a solution in LINQ...

try this (assuming you have a List<Filter> )
filterObjectList.Sort((f1,f2) => precedence.IndexOf(f1.FilterValue).CompareTo(precedence.IndexOf(f2.FilterValue));

Using LINQ:
precedence.SelectMany(p => objs.Where(o => o.Prop1 == p));
OR
objs.Select(s => new { Index = precedence.IndexOf(s.Prop1), Obj = s })
.OrderBy(a => a.Index).Select(a => a.Obj);

Create a new class of your object that you want to sort:
public class MySortableObject: IComparable {
private string str;
public MySortableObject(string _str) {
this.str = _str;
}
int IComparable.CompareTo(object obj) {
MySortableObject comparedObj = (MySortableObject) obj;
// Implement here the code that will compare the current object (this) and the compared object (comparedObj)
// It must return -1 if this instance precedes comparedObj in the sort order
// It must return 1 if this instance follows comparedObj in the sort order
// It must return 0 if this instance occurs in the same position in the sort order as comparedObj
// Use for example String.CompareTo() method to implement this, or your own code (with if(), switch()... whatever you need)
}
}

Related

check if algorithm "saw" the class before [duplicate]

I am populating an array with instances of a class:
BankAccount[] a;
. . .
a = new BankAccount[]
{
new BankAccount("George Smith", 500m),
new BankAccount("Sid Zimmerman", 300m)
};
Once I populate this array, I would like to sort it by balance amounts. In order to do that, I would like to be able to check whether each element is sortable using IComparable.
I need to do this using interfaces. So far I have the following code:
public interface IComparable
{
decimal CompareTo(BankAccount obj);
}
But I'm not sure if this is the right solution. Any advice?
You should not define IComparable yourself. It is already defined. Rather, you need to implement IComparable on your BankAccount class.
Where you defined the class BankAccount, make sure it implements the IComparable interface. Then write BankAccount.CompareTo to compare the balance amounts of the two objects.
public class BankAccount : IComparable<BankAccount>
{
[...]
public int CompareTo(BankAccount that)
{
if (this.Balance < that.Balance) return -1;
if (this.Balance == that.Balance) return 0;
return 1;
}
}
Edit to show Jeffrey L Whitledge's solution from comments:
public class BankAccount : IComparable<BankAccount>
{
[...]
public int CompareTo(BankAccount that)
{
return this.Balance.CompareTo(that.Balance);
}
}
IComparable already exists in .NET with this definition of CompareTo
int CompareTo(Object obj)
You are not supposed to create the interface -- you are supposed to implement it.
public class BankAccount : IComparable {
int CompareTo(Object obj) {
// return Less than zero if this object
// is less than the object specified by the CompareTo method.
// return Zero if this object is equal to the object
// specified by the CompareTo method.
// return Greater than zero if this object is greater than
// the object specified by the CompareTo method.
}
}
Do you want to destructively sort the array? That is, do you want to actually change the order of the items in the array? Or do you just want a list of the items in a particular order, without destroying the original order?
I would suggest that it is almost always better to do the latter. Consider using LINQ for a non-destructive ordering. (And consider using a more meaningful variable name than "a".)
BankAccount[] bankAccounts = { whatever };
var sortedByBalance = from bankAccount in bankAccounts
orderby bankAccount.Balance
select bankAccount;
Display(sortedByBalance);
An alternative is to use LINQ and skip implementing IComparable altogether:
BankAccount[] sorted = a.OrderBy(ba => ba.Balance).ToArray();
There is already IComparable<T>, but you should ideally support both IComparable<T> and IComparable. Using the inbuilt Comparer<T>.Default is generally an easier option. Array.Sort, for example, will accept such a comparer.
If you only need to sort these BankAccounts, use LINQ like following
BankAccount[] a = new BankAccount[]
{
new BankAccount("George Smith", 500m),
new BankAccount("Sid Zimmerman", 300m)
};
a = a.OrderBy(bank => bank.Balance).ToArray();
If you need to compare multiple fields, you can get some help from the compiler by using the new tuple syntax:
public int CompareTo(BankAccount other) =>
(Name, Balance).CompareTo(
(other.Name, other.Balance));
This scales to any number of properties, and it will compare them one-by-one as you would expect, saving you from having to implement many if-statements.
Note that you can use this tuple syntax to implement other members as well, for example GetHashCode. Just construct the tuple and call GetHashCode on it.
This is an example to the multiple fields solution provided by #Daniel Lidström by using tuple:
public static void Main1()
{
BankAccount[] accounts = new BankAccount[]
{
new BankAccount()
{
Name = "Jack", Balance =150.08M
}, new BankAccount()
{
Name = "James",Balance =70.45M
}, new BankAccount()
{
Name = "Mary",Balance =200.01M
}, new BankAccount()
{
Name = "John",Balance =200.01M
}};
Array.Sort(accounts);
Array.ForEach(accounts, x => Console.WriteLine($"{x.Name} {x.Balance}"));
}
}
public class BankAccount : IComparable<BankAccount>
{
public string Name { get; set; }
public int Balance { get; set; }
public int CompareTo(BankAccount other) =>
(Balance,Name).CompareTo(
(other.Balance,other.Name ));
}
Try it

KellermanSoftware CompareNetObjects, comparing collections of different types

I am using CompareNetObjects for automated testing of my system. This has been useful in ensuring performance optimizations do not change the expected behavior of the system, and ensuring errors are not introduced.
When I have a collection that is declared in an abstract way, I expect to have the contents compared, not the collection itself; however, I do not see an option to enable this.
Class:
public class MustBeCorrect
{
public string Name { get; set; }
IEnumerable<string> Items { get; set; }
}
Test:
[Fact] void IsCorrect()
{
var obj1 = new MustBeCorrect
{
Name = "Kitty",
Items = new string[]
{
"Collar",
"Bell"
}
};
var obj2 = new MustBeCorrect
{
Name = "Kitty",
Items = new List<string>
{
"Collar",
"Bell"
}
};
comparer.Compare(obj1, obj2); // False!
}
The above two objects compare as not equal, even though the only difference between the two objects is one uses an array, and the other uses a list. In terms of my contract, however, these two seem as they should be considered equal to me.
How can I configure the comparison options to only compare the contents of collections, rather than the collection itself?
There does not exist a way to do what you want specifically for collections.
However, there is an option you can set called IgnoreObjectTypes in the ComparisonConfig. It is default to false, but if you set it to true, it should give you the behavior you desire for your collections.
Be aware, this will ignore object types for all comparisons.
This is the only way I found to make it work so far.
My solution consists in implementing a Custom comparer that ignores the generic argument of the collection when comparing IEnumerables.
However, the drawback of this approach is that you lose the granularity of the returned result.Differences: if two lists have different elements, it won't tell you exactly how and which elements are different.
If someone could identify a way to implement this in the Custom Comparer, that'd be great.
public class OnlyIEnumContents : BaseTypeComparer
{
public OnlyIEnumContents(RootComparer rootComparer) : base(rootComparer)
{
}
public OnlyIEnumContents() : this(RootComparerFactory.GetRootComparer())
{
}
public override bool IsTypeMatch(Type type1, Type type2)
{
if (typeof(IEnumerable).IsAssignableFrom(type1) && typeof(IEnumerable).IsAssignableFrom(type2))
return true;
return type1 == type2;
}
public override void CompareType(CompareParms parms)
{
var ienum1 = parms.Object1 as IEnumerable;
var ienum2 = parms.Object2 as IEnumerable;
if (ienum1 != null && ienum2 != null)
{
List<object> list1 = new List<object>();
List<object> list2 = new List<object>();
foreach (var item in ienum1)
list1.Add(item);
foreach (var item in ienum2)
list2.Add(item);
// Compare the lists.
// Because all the list elements were boxed in a System.Object,
// this only returns the differences between the single elements.
CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(list1, list2);
// This is where granularity is lost.
// The official documentation https://github.com/GregFinzer/Compare-Net-Objects/wiki/Custom-Comparers
// points that we should return `AddDifference(parms)`,
// however I don't know how to get `parms` from a given `result` object.
// Returning a single difference for the entire lists.
if (!result.AreEqual)
AddDifference(parms);
}
}
}
And then to use it:
CompareLogic compareLogic = new CompareLogic();
compareLogic.Config.CustomComparers.Add(new OnlyIEnumContents());
ComparisonResult result = compareLogic.Compare(obj1, obj2);

Filter a list of objects given a single object

I want a method that accepts a list of objects, and then the 'filter object' (which is the same type as the list of objects). I'm able to do it (inefficiently) on a small scale but also quite fixed - I'd like it to be a generic method so I can pass in the type so it can apply to anything.
Example:
public class Program {
public void Main(string[] args) {
var listOfObjects = new List<MyClass> {
new MyClass { ID = 1, Name = "Object 1" },
new MyClass { ID = 2, Name = "Object 2" },
new MyClass { ID = 3, Name = "Object 2" }
};
var filter = new MyClass { Name = "Object 2" };
// Should return only the first object in the list, since
// the filter's Name = "Object 2", all items in the list
// where the property equals "Object 2" will be filtered out
var filteredList = FilterObjects(listOfObjects, filter);
}
}
public class MyClass {
public int ID { get; set; }
public string Name { get; set; }
}
public class MyTest {
public List<MyClass> FilterObjects(List<MyClass> objects, MyClass filter) {
// first check if the filter is just an empty new instance
// if a user passes in an empty filter then they are not
// filtering anything, therefore
if (filter == new MyClass()) return objects;
var filteredList = new List<MyClass>();
foreach (var item in objects) {
// if this item passes the test for the filter
// (check if any of the properties are equal to the
// filter properties - if so, then this item is not
// is not a match, and we cannot add it to the list)
if (item.ID != filter.ID && item.Name != filter.Name)
filteredList.Add(item);
// What I want is a faster and more consolidated way of
// checking all the properties.
}
return filteredList;
}
}
EDIT: Is there any way to do this also using reflection?
EDIT2: Just wanted to clarify that my example here is just a simple template. I am working with an object that has 20+ properties and would love to not have to make a huge if statement if possible.
EDIT3: I should also mention that the filter object the user passes in can be incomplete, e.g. they can pass in a MyClass object without the ID (just the Name property) because when it reaches my Controller, that MyClass object will automagically fill in ID with a default value. I can check if it is a default value by creating a new instance of MyClass - new MyClass() and for every property, if it equals what the default value would be then ignore that property for filtering because the user didn't want that property filtered. But think of this concept on a larger scale where I have 20+ properties and a user wants to filter out all objects but ONLY wants to use 3 of those properties to filter from. The other 17+ properties will not have an equality check.
It sounds like what you want are generic statements.
It isn't super straightforward but something like this should work:
public static IEnumerable<T> Filter<T>(this IEnumerable<T> results, Filter filter)
{
var types = results.GetType().GetProperties();
foreach (var filter in filter.Filters)
{
Type type = results.GetType();
filter.ColumnName = filter.ColumnName.Replace(" ", "");
var pred = BuildPredicate<T>(filter.ColumnName, filter.FilterValue);
if (filter.ColumnName != null && filter.FilterValue != null)
{
results = results.Where(w =>
{
return w.GetType().GetProperty(filter.ColumnName).GetValue(w, null).ToString().ToLowerInvariant().Contains(filter.FilterValue.ToLowerInvariant());
});
}
}
return results;
}
The filter object looks something like:
public class Filter
{
public string ColumnName {get; set; }
public string Value { get; set; }
//Other properties for Asc / Desc and more
}
And then on any List like List or List you would essentially do:
var results = MyList.Filter(new Filter() { ColumnName = "LastName"; Value = "Smith" });
which gets translated to a Function that if typed manually would look like:
var results = MyList.Where(w => w.LastName == "Smith");
This example however is rough, no type checking for starters.
I would go with a custom IsMatch method:
static bool IsMatch (MyClass toTest, MyClass filter)
{
if (filter.Prop1 != null // or whatever value means "skip this property"
&& filter.Prop1 == toTest.Prop1)
return true;
if (filter.Prop2 != null & filter.Prop2 == toTest.Prop2)
return true;
...
return false;
}
and then let Linq do the lookup for you:
List<MyClass> filtered = listOfObjects.Where(x => !IsMatch(x, filter)).ToList();
A unit test checking (with reflection) that this method is always up to date, checking all properties now and in future can be helpful in not introducing bugs when you add properties to the class
Why not just use methods already part of the System.Generic.Collections library.
var filteredList= new List<MyClass>(listOfObjects);
filteredList.RemoveWhere(n => n.Name == "Object 2");
If you want to use another class as your filter:
MyClass filter = new MyClass() {Name = "Object 2", Id=2 };
var filteredList= new List<MyClass>(listOfObjects);
filteredList.RemoveWhere(n => (n.Name == filter.Name || n.Id == filter.Id)); // you can modify predicate based on whatever you wish to compare

Linq query List<List<class>> Where property of class C#

Could someone suggest a way for me to select one of the lists within a parent list where one of it's elements has a certain property value?
public class HierarchyLevel
{
public string Abbreviation;
public string Name;
public string Value;
public Type LevelType;
public List<HierarchyLevel> Children = new List<HierarchyLevel>();
}
public static List<List<HierarchyLevel>> ElementaryTypes = new List<List<HierarchyLevel>>();
I am actually trying to get the List that has the LevelType field of a specific type.
You wrote:
I am actually trying to get the List that has the LevelType field of a specific type.
What do you want if you've got several HierarchyLevels with this LevelType? And what do you want if there are no HierarchyLevels at all with this LevelType?
Let's assume that you want all HierarchyLevels with this LevelType. If later on you only want the first, or the one with a certain Abbreviation (or whatever), you could always use .FirstOrDefault, or .Where. and do a ToList in the end.
Implementing it as an extension function. See Extension Methods Demystified
public static IEnumerable<HierarchyLevel> GetHierarchyLevelsWithLevelType(
this IEnumerable<HierarchyLevel> hierarchyLevels,
LevelType desiredLevelType)
{
foreach (var hierarchyLevel in hierarchyLevels)
{
if (hierarchyLevel.LevelType == desiredLevelType)
{ // found one!
yield return hierarchyLevel;
}
// using recursion: check all the Children
IEnumerable<HierarchyLevel> childrenWithDesiredLevelType = hierarchyLevel.Children
.GetHierarchyLevelsWithLevelType(desiredLevelType);
foreach(var childWithDesiredLevelType in childrenWithDesiredLevelType)
{
yield return childWithDesiredLevelType;
}
}
}
Because of the recursion all Grandchildren and their Children etc will be returned
usage:
var allSpecialHierarchies = myHierarchies.GetHierarchyLevelsWithLevelType(LevelType.Special);
// get the first:
var firstSpecialHierarchy = allSpecialHierarchies.FirstOrDefault();
// get the first three named "Shakespeare:
var threeShakesPeares = allSpecialHierarchies
.Where(hierarchyLevel => hierarchyLevel.Name == "Shakespeare")
.Take(3)
For better usage you should provide a version that has a parameter IQualityComparer<LevelType>. Let the function above call that one.
And a nice challenge: to be fully LINQ compatible, create a version with a predicate that returns a type T and an equality comparer for this type T, so that you can have all HierarchyLevels with a certain Name, or Abbreviation.
You can Solve your with the help of recursion consider example below :- I have taken sample type of String you can use any of your Type
List<List<HierarchyLevel>> sample = new List<List<HierarchyLevel>>();
Type yourType = typeOf(string);
List<HierarchyLevel> filtered = sample.Where(x => ContainsElement(x, yourType));
public void bool ContainsElement(List<HierarchyLevel> list,Type yourType)
{
if(list.Any(x => x.LevelType == yourType) //check if current node has same level type
return true;
else if(list.Childern.Count > 0) //check if current node has children if yes then call ContainsElement again
return list.Children.Any(x => ContainsElement(x,yourType));
else
return false; //else return false in last
}
Thanks to user743414 for pointing out how simple this was :)
By using a dictionary instead, I could reference to the specific list. (This is also a faster option.)
Dictionary<Type,List<HierarchyLevel>> HierarchicalData;
I can now use it with a key of 'Type':
private void UpdateGeneralData(object Entity, Dictionary<Type,List<HierarchyLevel>> TypeData)
{
CBType.Items.Clear();
foreach (var item in TypeData[Entity.GetType()])
{
CBType.Items.Add(item);
}
}
Something like this (?):
List<HierarchyLevel> var = hLevel.Select(h => h.Children.Where(c => c.Param = "desired param")).ToList();

How to sort object property in list by first two characters then alpha

I have a list of objects. The objects are insurance policies. Well, the first part of every policy number is alpha charater, then all numeric. I need to sort all policies with policy number starting with CA first in the list, then by alpha numeric after that. For example, if I have three policies, AB10001, CA20001, CA20003, the order should be all "CA" first, then sort rest like below:enter code here
CA20001
CA20003
AB10001
What is throwing me off, as I understand regular sorts, is how to get all CA first since they do not apply to alpha order. I think possibly, pull all of the CA prefixed policy names into a new list, then order those by numeric. Then sort the leftovers in a second list. Then append the second list to the "CA" sorted list if that makes sense. There must be a cleaner way though using lambda.
You can achieve that with something like.
var list = new List<string> { "AB10001", "CA20003", "CA20001" };
var ordered = list.OrderByDescending(s => s.StartsWith("CA")).ThenBy(s => s);
foreach(var o in ordered) Console.WriteLine(o);
which outputs
CA20001
CA20003
AB10001
Basically you first order everything by whether or not it begins with "CA" then by the actual string value. You have to use descending because false is considered to be less than true.
You can try the following:
const string CAHeader = "CA";
IEnumerable<string> sorted =
list.OrderBy(s => s.StartsWith(CAHeader) ? s.Substring(CAHeader.Length) : s);
Implement IComparer and put custom logic in Compare.
Here's an IComparer implementation. It's about 1200% more lines of code than juharr's solution. But it could be handy in certain scenarios that I haven't thought of yet.
The comparer:
public class PolicyComparer : IComparer<Policy>
{
public static PolicyComparer Instance { get; } = new PolicyComparer();
public int Compare(Policy x, Policy y)
{
return x.PolicyNumber.StartsWith("CA") ^ y.PolicyNumber.StartsWith("CA")
? (x.PolicyNumber.StartsWith("CA") ? -1 : 1)
: string.Compare(x.PolicyNumber, y.PolicyNumber, StringComparison.Ordinal);
}
}
An extension for sorting:
public static class PolicyExtensions
{
public static IEnumerable<Policy> Sort(this IEnumerable<Policy> unsortedPolicies)
{
var sorted = new List<Policy>(unsortedPolicies);
sorted.Sort(PolicyComparer.Instance);
return sorted;
}
}
The Policy class I used for testing:
public class Policy
{
public string PolicyNumber { get; set; }
}
A unit test:
[TestMethod]
public void PoliciesAreSortedByCAfirst()
{
var unsorted = new Policy[]
{
new Policy {PolicyNumber = "AB10001"},
new Policy {PolicyNumber = "CA20003"},
new Policy {PolicyNumber = "CA20001"}
};
var sorted = unsorted.Sort();
var expectedOrder = new string[] {"CA20001", "CA20003", "AB10001"};
Assert.IsTrue(expectedOrder.SequenceEqual(sorted.Select(policy=>policy.PolicyNumber)));
}
I was looking at this wrong. What needs to be sortable isn't the policy. It's the policy number. It's actually not a bad idea to declare the policy number as a separate class rather than using a string. The reason is that you can "hide" its implementation. Maybe at some point a policy "number" becomes a combination of two fields.
That makes even more sense because the policy number has its own logic that has nothing to do with Policy. It has a custom sort order. It probably has its own rules for validation. If it's just a string attached to a Policy then Policy has to own all that. And it's not unrealistic that you might want to put a policy number on something that isn't itself a policy, like a claim against a policy. (This is the sort of ID that probably shows up everywhere.)
Here's a solution from that angle:
public class PolicyNumber : IComparable
{
private readonly string _number;
public PolicyNumber(string number)
{
_number = number;
}
public int CompareTo(object obj)
{
var other = obj as PolicyNumber;
if (other == null) return 0;
return _number.StartsWith("CA") ^ other._number.StartsWith("CA")
? (_number.StartsWith("CA") ? -1 : 1)
: string.Compare(_number, other._number, StringComparison.Ordinal);
}
public override string ToString()
{
return _number;
}
public override bool Equals(object obj)
{
var other = obj as PolicyNumber;
return other != null && string.Equals(_number, other._number);
}
protected bool Equals(PolicyNumber other)
{
return string.Equals(_number, other._number);
}
public override int GetHashCode()
{
return (_number != null ? _number.GetHashCode() : 0);
}
}
And the unit test:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void PoliciesAreSortedByCAfirst()
{
var unsorted = new Policy[]
{
new Policy{Number = new PolicyNumber("AB10001")},
new Policy{Number = new PolicyNumber("CA20003")},
new Policy{Number = new PolicyNumber("CA20001")}
};
var sorted = unsorted.OrderBy(policy => policy.Number);
var expectedOrder = new Policy[]
{
unsorted[2], unsorted[1], unsorted[0]
};
Assert.IsTrue(expectedOrder.SequenceEqual(sorted));
}
}
Notice how that allows PolicyNumber to "own" its own logic. Classes that use it just sort it without knowing or caring how it is sorted. And you can still compare it for equality just like a string.
The unit test shows how easy that is to use. Instead of asking something to sort it for you, you just sort it. If it's a Policy with a policy number you can sort it by the policy number or by something else.

Categories

Resources