My problem is that I always want to order a collection of objects in a certain fashion.
For example:
class foo{
public string name {get;set;}
public DateTime date {get;set;}
public int counter {get;set;}
}
...
IEnumerable<foo> dosomething(foo[] bar){
return bar.OrderBy(a=>a.name).ThenBy(a=>a.date).ThenBy(a=>a.counter);
}
The issue I have is its quite longwinded tacking-on the sort order all the time. A neat solution appears to just create a class that implements IComparer<foo>, meaning I can do:
IEnumerable<foo> dosomething(foo[] bar){
return bar.OrderBy(a=>a, new fooIComparer())
}
.
The problem is, the order method this implements is as follows
...
public int Compare(foo x, foo y){ }
Meaning it compares on a very granular basis.
The currently implementation (which will probably work, although im writing pseudocode)
public int Compare(foo x, foo y){
if (x==y)
return 0;
var order = new []{x,y}.OrderBy(a=>a.name).ThenBy(a=>a.date).ThenBy(a=>a.counter);
return (order[0] == x) ? -1 : -1;//if x is first in array it is less than y, else it is greater
}
This is not exactly efficient, can another offer a neater solution? Ideally without a Compare(x,y) method altogether?
Option 1 - The Comparer
As you're ordering by multiple conditions, you'll to check them individually within each case; for example, if x.name and y.name are equal, then you would check x.date and y.date, and so on.
public class FooComparer : IComparer<Foo>
{
public int Compare(Foo x, Foo y)
{
// nasty null checks!
if (x == null || y == null)
{
return x == y ? 0
: x == null ? -1
: 1;
}
// if the names are different, compare by name
if (!string.Equals(x.Name, y.Name))
{
return string.Compare(x.Name, y.Name);
}
// if the dates are different, compare by date
if (!DateTime.Equals(x.Date, y.Date))
{
return DateTime.Compare(x.Date, y.Date);
}
// finally compare by the counter
return x.Counter.CompareTo(y.Counter);
}
}
Option 2 - The extension method
An alternative, not so appealing approach, could be an extension method. Sadly as the TKey for each ThenBy can be different, we lose the power of generics, but can safely replace it with the type object in this case.
public static IOrderedEnumerable<T> OrderByThen<T>(this IEnumerable<T> source, Func<T, object> selector, params Func<T, object>[] thenBySelectors)
{
IOrderedEnumerable<T> ordered = source.OrderBy(selector);
foreach (Func<T, object> thenBy in thenBySelectors)
{
ordered = ordered.ThenBy(thenBy);
}
return ordered;
}
You have to implement IComparable<foo> and compare all properties:
class foo: IComparable<foo>, IComparer<foo>
{
public string name { get; set; }
public DateTime date { get; set; }
public int counter { get; set; }
public int Compare(foo x, foo y)
{
if (x == null || y == null) return int.MinValue;
if (x.name != y.name)
return StringComparer.CurrentCulture.Compare(x.name, y.name);
else if (x.date != y.date)
return x.date.CompareTo(y.date);
else if (x.counter != y.counter)
return x.counter.CompareTo(y.counter);
else
return 0;
}
public int CompareTo(foo other)
{
return Compare(this, other);
}
}
Then you can use OrderBy in this way:
var ordered = foos.OrderBy(f => f).ToList();
what's wrong with an extension method?
Why wont you simply compare your values:
int Compare(foo x, foo y)
{
if (x== null && y == null)
return 0;
else if (x == null)
return -1;
else if (y == null)
return 1;
var nameComparision = string.Compare(x.name,y.name);
if (nameComparision != 0)
return nameComparision;
var dateComparision = x.date.CompareTo(y.date);
if (dateComparision != 0)
return dateComparision;
var counterComparision = x.counter.CompareTo(y.counter);
return counterComparision;
}
Related
I want to sort the list of classes and interfaces. So my logic is
if the class is not implemented interface it's higher than the interface, otherwise, it's lower
I am using the IComparer interface to sort my list. My model looks like this:
My Comparer class (if returns 1 it means y>x, 0 is x==y -1 is x>y):
public class SortedTypeComparer : IComparer<Type>
{
/// <summary>
/// Compares types
/// </summary>
public int Compare(Type x, Type y)
{
public int Compare(Type x, Type y)
{
if (y.IsAssignableFrom(x))
{
return 1;
}
else if (x.IsAssignableFrom(y))
{
return -1;
}
else if (!y.IsAssignableFrom(x) && !x.IsAssignableFrom(y) && x.IsInterface && y.IsClass)
{
return 1;
}
else if (!y.IsAssignableFrom(x) && !x.IsAssignableFrom(y) && x.IsClass && y.IsInterface)
{
return -1;
}
else
{
return 0;
}
}
}
}
I am expecting when I sort the list it should be:
IAnimal
IRunnable
Animal
ICat
Cat
Or:
IRunnable
IAnimal
Animal
ICat
Cat
Because IRunnable and IAnimal are 'equal'. Here is my usage:
var list = new List<Type>();
list.Add(typeof(IAnimal));
list.Add(typeof(IRunnable));
list.Add(typeof(ICat));
list.Add(typeof(Animal));
list.Add(typeof(Cat));
list.Sort(new SortedTypeComparer());
In this case, it is working as expected. But when I change the order of adding to list for example(put IRunnable to the end):
var list = new List<Type>();
list.Add(typeof(IAnimal));
list.Add(typeof(ICat));
list.Add(typeof(Animal));
list.Add(typeof(Cat));
list.Add(typeof(IRunnable));
list.Sort(new SortedTypeComparer());
The order is
IAnimal
Animal
ICat
IRunnable
Cat
It is not my expectation because of IRunnable>Animal. It seems when it compare Animal and ICat Animal is higher, then when it compares ICat and IRunnable it's saying "ICat == IRunnable, so Animal should be > IRunnable". How can I write the logic in the Compare method to sort my list as expected?
I do not think this is possible with a IComparer. From CompareTo
For objects A, B, and C, the following must be true:
If A.CompareTo(B) returns zero and B.CompareTo(C) returns zero, then A.CompareTo(C) is required to return zero.
So if A inherits from C and B does not inherit anything, then according to your rules compareTo should return:
A.CompareTo(B) -> 0
B.CompareTo(C) -> 0
A.CompareTo(C) -> 1
This violates the requirements of CompareTo.
An alternative would be to build an Directed acyclic graph of the hierarchy. Then you should be able to use Topological sorting to sort the graph.
Maybe answer of #JonasH more correct, but I did by fixing My comparer class. I added the following condition in my method and it is correct for most scenarios:
public class SortedTypeComparer : IComparer<Type>
{
public List<Type> AllTypes { get; set; }
public SortedTypeComparer(List<Type> types)
{
AllTypes = types;
}
/// <summary>
/// Compares types
/// </summary>
public int Compare(Type x, Type y)
{
var result = CompareIsHigherOrLower(x, y);
if (result == 0)
{
var subEntitiesOfX = AllTypes.Where(a => x.IsAssignableFrom(a) && a != x);
foreach (var subTypeOfX in subEntitiesOfX)
{
result = CompareIsHigherOrLower(subTypeOfX, y);
if (result == -1)
{
return -1;//It means SubEntity of X is higher then Y and X should be > Y
}
}
var subEntitiesOfY = AllTypes.Where(a => y.IsAssignableFrom(a) && a != y);
foreach (var subType in subEntitiesOfY)
{
result = CompareIsHigherOrLower(subType, x);
if (result == -1)
{
return 1;//It means SubEntity of Y is higher then X and Y should be > X
}
}
}
return result;
}
int CompareIsHigherOrLower(Type x, Type y)
{
if (y.IsAssignableFrom(x))
{
return 1;
}
else if (x.IsAssignableFrom(y))
{
return -1;
}
else if (!y.IsAssignableFrom(x) && !x.IsAssignableFrom(y) && x.IsInterface && y.IsClass)
{
return 1;
}
else if (!y.IsAssignableFrom(x) && !x.IsAssignableFrom(y) && x.IsClass && y.IsInterface)
{
return -1;
}
else
{
return 0;
}
}
}
I have a list of long type array.
List<ulong[]> TestList = new List<ulong[]>();
and list has following items.
{1,2,3,4,5,6},
{2,3,4,5,6,7},
{3,4,5,6,7,8},
{1,2,3,4,5,6}
and expected distinct result is
{1,2,3,4,5,6},
{2,3,4,5,6,7},
{3,4,5,6,7,8}
So I try as following, but useless.
TestList = TestList.Distinct().ToList();
Am I need something special comparer for getting distinct list?
Distinct() uses the default equality check, which for arrays is reference equality. It does not check the contents of the array for equality.
If you want to do that, you'll need the overload of Distinct() that takes an IEqualityComparer<T>. This allows you to customize the behaviour to determine if two items are equal or not.
For comparing arrays, IStructuralEquatable and friends already do the heavy lifting. You can wrap it simply, like so:
sealed class StructuralComparer<T> : IEqualityComparer<T>
{
public static IEqualityComparer<T> Instance { get; } = new StructuralComparer<T>();
public bool Equals(T x, T y)
=> StructuralComparisons.StructuralEqualityComparer.Equals(x, y);
public int GetHashCode(T obj)
=> StructuralComparisons.StructuralEqualityComparer.GetHashCode(obj);
}
Then, use it in the Distinct() call like this:
TestList = TestList.Distinct(StructuralComparer<ulong[]>.Instance).ToList();
You need to provide an equality comparer, default implementation does not know how to compare arrays of long (it uses reference equality):
class LongArrayComparer : EqualityComparer<long[]>
{
public override bool Equals(long[] a1, long[] a2)
{
if (a1 == null && a2 == null)
return true;
else if (a1 == null || a2 == null)
return false;
return a1.SequenceEqual(a2);
}
public override int GetHashCode(long[] arr)
{
long hCode = arr.Aggregate(0, (acc, it) => acc ^ it);
return hCode.GetHashCode();
}
}
Then use it:
TestList = TestList.Distinct(new LongArrayComparer()).ToList();
List<ulong[]> TestList = new List<ulong[]>() {
new ulong[]{ 1,2,3,4,5,6},
new ulong[]{ 2,3,4,5,6,7},
new ulong[]{ 3,4,5,6,7,8},
new ulong[]{ 1,2,3,4,5,6}
};
var result = TestList.GroupBy(x => String.Join(",", x))
.Select(x => x.First().ToArray())
.ToList();
You can implement an IEqualityComparer
public class IntArrayComparer : IEqualityComparer<string[]>
{
public bool Equals(int[] x, int[] y)
{
var shared = x.Intersect(y);
return x.Length == y.Length && shared.Count() == x.Length;;
}
public int GetHashCode(int[] obj)
{
int hashCode=obj.Length;
for(int i=0;i<obj.Length;++i)
{
hashCode=unchecked(hashCode*314159 +obj[i]);
}
return hashCode;
}
}
Then can implement it:
TestList = TestList.Distinct(new IntArrayComparer()).ToList();
I've got a List<Card>, and I want to sort these cards
So, I'm looking for a method to sort them with different criterias, like their ID, their Name ...
public class Card : IComparer
{
public string ID;
public string Name;
public int CompareId(object firstCard, object secondCard)
{
Card c1 = (Card)firstCard;
Card c2 = (Card)secondCard;
return c1.Id.CompareTo(c2.Id);
}
}
But then, visual studio sent me an error :
'Card' does not implement interface member 'IComparer<Card>.Compare(Card, Card)'
You, probably, want to have your class Comparable not a Comparator
public class Card : IComparable<Card>
{
public string ID;
public string Name;
public int CompareTo(Card other)
{
if (null == other)
return 1;
// string.Compare is safe when Id is null
return string.Compare(this.Id, other.Id);
}
}
then
List<Card> myList = ...
myList.Sort();
Edit: If you want to have several criteria to choose from, you have to implement several Comparers as separated classes, e.g.
public sealed class CardByIdComparer : IComparer<Card>
{
public int Compare(Card x, Card y)
{
if (object.ReferenceEquals(x, y))
return 0;
else if (null == x)
return -1;
else if (null == y)
return 1;
else
return string.Compare(x.Id, y.Id);
}
}
and when sorting provide the required:
List<Card> myList = ...
myList.Sort(new CardByIdComparer());
Edit 2: (inspired by spender's library). If you want to combine several comparers into one (i.e. use comparer1, on tie - comparer2 etc.)
public sealed class ComparerCombined<T> : IComparer<T> {
private IComparer<T>[] m_Comparers;
public ComparerCombined(params IComparer<T>[] comparers) {
if (null == comparers)
throw new ArgumentNullException(nameof(comparers));
m_Comparers = comparers
.Select(item => item == null ? Comparer<T>.Default : item)
.Where(item => item != null)
.Distinct()
.ToArray();
}
public int Compare(T x, T y) {
if (object.ReferenceEquals(x, y))
return 0;
else if (null == x)
return -1;
else if (null == y)
return 1;
foreach (var comparer in m_Comparers) {
int result = comparer.Compare(x, y);
if (result != 0)
return result;
}
return 0;
}
}
usage:
myList.Sort(new ComparerCombined(
new CardByIdComparer(), // Sort By Id
new CardByNameComparer() // On tie (equal Id's) sort by name
));
The easiest way You can use Linq:
List<Card> objSortedList = objListObject.OrderBy(o=>o.ID).ToList();
or
List<Card> objSortedList = objListObject.OrderByDescending(o=>o.ID).ToList();
Good examples for demonstrate the concept of
List<T>.Sort(IComparer <T>) method check the link please.
IComparer<T> in this example compare method used for strings IComparer<T>
but you can use this for ID(int) too.
using System;
using System.Collections.Generic;
class GFG : IComparer<string>
{
public int Compare(string x, string y)
{
if (x == null || y == null)
{
return 0;
}
// "CompareTo()" method
return x.CompareTo(y);
}
}
public class geek
{
public static void Main()
{
List<string> list1 = new List<string>();
// list elements
list1.Add("C++");
list1.Add("Java");
list1.Add("C");
list1.Add("Python");
list1.Add("HTML");
list1.Add("CSS");
list1.Add("Scala");
list1.Add("Ruby");
list1.Add("Perl");
int range = 4;
GFG gg = new GFG();
Console.WriteLine("\nSort a range with comparer:");
// sort the list within a
// range of index 1 to 4
// where range = 4
list1.Sort(1, range, gg);
Console.WriteLine("\nBinarySearch and Insert Dart");
// Binary Search and storing
// index value to "index"
int index = list1.BinarySearch(0, range,
"Dart", gg);
if (index < 0)
{
list1.Insert(~index, "Dart");
range++;
}
}
}
You need to implement IComparer
public int Compare(Card card1, Card card2)
{
if (card1.ID > card2.ID)
return 1; //move card1 up
if (card2.ID < card1.ID)
return -1; //move card2 up
return 0; //do nothing
}
I want to write a custom comparer for a SortedDictionary, where keys are sorted based on their type. Is this possible?
public class StateBase
{
// This is a base class I have to inherit from
}
SortedDictionary<StateBase, int> _stateDictionary =
new SortedDictionary<StateBase, int>(new StateComparer());
class StateComparer : IComparer<StateBase>
{
public int Compare(StateBase a, StateBase b)
{
// I'd like to sort these based on their type
// I don't particularly care what order they are in, I just want them
// to be sorted.
}
}
Sure, why not? Note that we must be talking about reference-types for this to apply, so something like:
public class TypeComparer<T> : IComparer<T>, IEqualityComparer<T> where T : class
{
public static readonly TypeComparer<T> Singleton= new TypeComparer<T>();
private TypeComparer(){}
bool IEqualityComparer<T>.Equals(T x, T y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
Type xType = x.GetType(), yType = y.GetType();
return xType == yType && EqualityComparer<T>.Default.Equals(x, y);
}
int IEqualityComparer<T>.GetHashCode(T x)
{
if (x == null) return 0;
return -17*x.GetType().GetHashCode() + x.GetHashCode();
}
int IComparer<T>.Compare(T x, T y)
{
if(x==null) return y == null ? 0 : -1;
if (y == null) return 1;
Type xType = x.GetType(), yType = y.GetType();
int delta = xType == yType ? 0 : string.Compare(
xType.FullName, yType.FullName);
if (delta == 0) delta = Comparer<T>.Default.Compare(x, y);
return delta;
}
}
You can. If your comparer implements IComparer<T>, it can be passed to a new SortedDictionary instance by the corresponding constructor overload.
The Compare method then somehow decides what item is greater or lower. It is the place where you can implement your compare-by-type logic.
Here is an example to compare Type instances based on their name:
public class TypeComparer : IComparer<Type>
{
public int Compare(Type x, Type y)
{
if(x != null && y != null)
return x.FullName.CompareTo(y.FullName);
else if(x != null)
return x.FullName.CompareTo(null);
else if(y != null)
return y.FullName.CompareTo(null);
else
return 0;
}
}
I'm having troubles with the Except() method.
Instead of returning the difference, it returns the original set.
I've tried implementing the IEquatable and IEqualityComparer in the Account class.
I've also tried creating a separate IEqualityComparer class for Account.
When the Except() method is called from main, it doesn't seem to call my custom Equals() method, but when I tried Count(), it did call the custom GetHashCode() method!
I'm sure I made a trivial mistake somewhere and I hope a fresh pair of eyes can help me.
main:
IEnumerable<Account> everyPartnerID =
from partner in dataContext.Partners
select new Account { IDPartner = partner.ID, Name = partner.Name };
IEnumerable<Account> hasAccountPartnerID =
from partner in dataContext.Partners
from account in dataContext.Accounts
where
!partner.ID.Equals(Guid.Empty) &&
account.IDPartner.Equals(partner.ID) &&
account.Username.Equals("Special")
select new Account { IDPartner = partner.ID, Name = partner.Name };
IEnumerable<Account> noAccountPartnerID =
everyPartnerID.Except(
hasAccountPartnerID,
new LambdaComparer<Account>((x, y) => x.IDPartner.Equals(y.IDPartner)));
Account:
public class Account : IEquatable<Account>
{
public Guid IDPartner{ get; set; }
public string Name{ get; set; }
/* #region IEquatable<Account> Members
public bool Equals(Account other)
{
return this.IDPartner.Equals(other.IDPartner);
}
#endregion*/
}
LambdaComparer:
public class LambdaComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _lambdaComparer;
private readonly Func<T, int> _lambdaHash;
public LambdaComparer(Func<T, T, bool> lambdaComparer) :
this(lambdaComparer, o => o.GetHashCode())
{
}
public LambdaComparer(Func<T, T, bool> lambdaComparer, Func<T, int> lambdaHash)
{
if (lambdaComparer == null)
throw new ArgumentNullException("lambdaComparer");
if (lambdaHash == null)
throw new ArgumentNullException("lambdaHash");
_lambdaComparer = lambdaComparer;
_lambdaHash = lambdaHash;
}
public bool Equals(T x, T y)
{
return _lambdaComparer(x, y);
}
public int GetHashCode(T obj)
{
return _lambdaHash(obj);
}
}
Basically your LambdaComparer class is broken when you pass in just a single function, because it uses the "identity hash code" provider if you don't provide anything else. The hash code is used by Except, and that's what's causing the problem.
Three options here:
Implement your own ExceptBy method and then preferably contribute it to MoreLINQ which contains that sort of thing.
Use a different implementation of IEqualityComparer<T>. I have a ProjectionEqualityComparer class you can use in MiscUtil - or you can use the code as posted in another question.
Pass a lambda expression into your LambdaComparer code to use for the hash:
new LambdaComparer<Account>((x, y) => x.IDPartner.Equals(y.IDPartner)),
x => x.IDPartner.GetHashCode());
You could also quickly fix your LambdaComparer to work when only the equality parameters are supplied like this:
public LambdaComparer(Func<T, T, bool> lambdaComparer) :
this(lambdaComparer, o => 1)
{
}
Look here, how to use and implementing IEqualityComparer in way with linq.Except and beyond.
https://www.dreamincode.net/forums/topic/352582-linq-by-example-3-methods-using-iequalitycomparer/
public class Department {
public string Code { get; set; }
public string Name { get; set; }
}
public class DepartmentComparer : IEqualityComparer {
// equal if their Codes are equal
public bool Equals(Department x, Department y) {
// reference the same objects?
if (Object.ReferenceEquals(x, y)) return true;
// is either null?
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.Code == y.Code;
}
public int GetHashCode(Department dept) {
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
// if null default to 0
if (Object.ReferenceEquals(dept, null)) return 0;
return dept.Code.GetHashCode();
}
}
IEnumerable<Department> deptExcept = departments.Except(departments2,
new DepartmentComparer());
foreach (Department dept in deptExcept) {
Console.WriteLine("{0} {1}", dept.Code, dept.Name);
}
// departments not in departments2: AC, Accounts.
IMO, this answer above is the simplest solution compared to other solutions for this problem. I tweaked it such that I use the same logic for the Object class's Equals() and GetHasCode(). The benefit is that this solution is completely transparent to the client linq expression.
public class Ericsson4GCell
{
public string CellName { get; set; }
public string OtherDependantProperty { get; set; }
public override bool Equals(Object y)
{
var rhsCell = y as Ericsson4GCell;
// reference the same objects?
if (Object.ReferenceEquals(this, rhsCell)) return true;
// is either null?
if (Object.ReferenceEquals(this, null) || Object.ReferenceEquals(rhsCell, null))
return false;
return this.CellName == rhsCell.CellName;
}
public override int GetHashCode()
{
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
// if null default to 0
if (Object.ReferenceEquals(this, null)) return 0;
return this.CellName.GetHashCode();
}
}