IComparer not working as expected - c#

My program's function is to count the occurrences of unique words in a document, and then display them in sorted order. I first loop through all the words and enter them into a dictionary, and increment the value in the dictionary for how many times they have been encountered. I then convert the dictionary to a list and call the .Sort method with an IComparer as a parameter. Shown in this code here:
List<KeyValuePair<string,long>> wordList = wordCount.ToList();
IComparer<KeyValuePair<string,long>> comparison = new comparator();
wordList.Sort(comparison);
And the IComparer class I am using
public class comparator : IComparer<KeyValuePair<string, long>>
{
public int Compare(KeyValuePair<string, long> x, KeyValuePair<string, long> y)
{
if (x.Value > y.Value)
return 1;
else
return 0;
}
}
However, when I am finished with the sorting, the list is not ordered by the value of the KeyValuePair like as I hoped it would be. What am I doing wrong here?

You're missing the case when y.Value is greater than x.Value in your comparer implementation:
public class comparator : IComparer<KeyValuePair<string, long>>
{
public int Compare(KeyValuePair<string, long> x, KeyValuePair<string, long> y)
{
if (x.Value > y.Value)
{
return 1;
}
else if (x.Value < y.Value)
{
return -1;
}
else
return 0;
}
}
or
public class comparator : IComparer<KeyValuePair<string, long>>
{
public int Compare(KeyValuePair<string, long> x, KeyValuePair<string, long> y)
{
return x.Value.CompareTo(y.Value);
}
}
You could also use LINQ OrderBy instead of Sort. It's easier to use because it takes a lambda expression, but it will create a new collection, instead of sorting the provided one.
var sorted = wordList.OrderByDescending(x => x.Value).ToList();
You could do all your processing in one query (assuming words is a collection of strings with all the words):
var sortedWithCount = words.GroupBy(x => x)
.OrderByDescending(g => g.Count)
.ToList(g => new { Word = g.Key, Count = g.Count });

Actually, you should return 1, 0 and -1 as a result fo the Compare method. But in your case, you could just use the CompareTo method from long type, for sample:
public class Comparator : IComparer<KeyValuePair<string, long>>
{
public int Compare(KeyValuePair<string, long> x, KeyValuePair<string, long> y)
{
return x.Value.CompareTo(y.Value);
}
}
As a good pratice, rename your class to Comparator and not comparator. Keep the clean code on!

Related

How to get a distinct result for list of array?

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();

How to create a sorting index in C#?

I have an array of values and I want to create a sorting index, i.e. an auxiliary array of integers that lists the element in sorted order by indirect addressing.
In other words,
I <= J -> Value[Index[I]] <= Value[Index[J]]
How can I define a comparator for the Sort method to achieve that ? The array of values must remain unchanged.
The easiest way to build such an index I see is to use LINQ:
var Index = Enumerable.Range(0, Value.Length).OrderBy(i => Value[i]).ToArray();
or if you insist on using Array.Sort, then you can use the overloads that accept Comparison<T> delegate:
var Index = new int[Value.Length];
for (int i = 0; i < Index.Length; i++)
Index[i] = i;
Array.Sort(Index, (a, b) => Comparer<Value_Type>.Default.Compare(Value[a], Value[b]));
where the Value_Type is the type of the Value array elements.
Another option (IMO the best) is to create a reusable generic comparer like this:
public static class Comparers
{
public static IComparer<int> CreateIndexComparer<T>(this IReadOnlyList<T> source, IComparer<T> comparer = null)
{
return new ListIndexComparer<T>(source, comparer);
}
private sealed class ListIndexComparer<T> : Comparer<int>
{
readonly IReadOnlyList<T> list;
readonly IComparer<T> comparer;
public ListIndexComparer(IReadOnlyList<T> list, IComparer<T> comparer = null)
{
this.list = list;
this.comparer = comparer ?? Comparer<T>.Default;
}
public override int Compare(int x, int y)
{
return x != y ? comparer.Compare(list[x], list[y]) : 0;
}
}
}
and use it with the Array.Sort overloads that accept IComparer<T>:
Array.Sort(Index, Value.CreateIndexComparer());

How to implement a comparer on a dictionary value

I have:
Dictionary<int, MyClass> ItemList = new Dictionary<int, MyClass>();
Where MyClass is something like:
public class MyClass
{
public int BaseItemID;
public string Description;
public MyClass()
{
}
public class Comparer : IEqualityComparer<MyClass>
{
public bool Equals(MyClass x, MyClass y)
{
if (ReferenceEquals(x, y))
return true;
else if (x == null || y == null)
return false;
return x.BaseItemID == y.BaseItemID;
}
public int GetHashCode(MyClass obj)
{
unchecked
{
int hash = 17;
hash = hash * 23 + obj.BaseItemID.GetHashCode();
return hash;
}
}
}
}
I need to pass this comparer to the the contains on the dictionary but am struggling. I see the dictionary takes something implementing IEqualityComparer in the constructor but:
Dictionary<int, MyClass> ItemList = new Dictionary<int, MyClass>(new MyClass.Comparer());
doesn't seem to work and raises an error on compile time.
I assume I need to implement some KeyValuePair types somewhere but I'm not sure where.
If I was doing this on a normal List<MyClass> then List.Contains(Obj, new MyClass.Comparer()) would work but not in this case.
If am not mistaken Dictionary contructor overload requires IEqualityComparer
public Dictionary(IEqualityComparer<TKey> comparer);
in your code you pass "IEqualityComparer of TValue", you can compare only with keys in dictionary not with values

Sort a List<T> by enum where enum is out of order

I have a List of messages.
Each message has a type.
public enum MessageType
{
Foo = 0,
Bar = 1,
Boo = 2,
Doo = 3
}
The enum names are arbitrary and cannot be changed.
I need to return the list sorted as: Boo, Bar, Foo, Doo
My current solution is to create a tempList, add the values in the order I want, return the new list.
List<Message> tempList = new List<Message>();
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Boo));
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Bar));
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Foo));
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Doo));
messageList = tempList;
How can I do this with an IComparer?
An alternative to using IComparer would be to build an ordering dictionary.
var orderMap = new Dictionary<MessageType, int>() {
{ MessageType.Boo, 0 },
{ MessageType.Bar, 1 },
{ MessageType.Foo, 2 },
{ MessageType.Doo, 3 }
};
var orderedList = messageList.OrderBy(m => orderMap[m.MessageType]);
So, let's write our own comparer:
public class MyMessageComparer : IComparer<MessageType> {
protected IList<MessageType> orderedTypes {get; set;}
public MyMessageComparer() {
// you can reorder it's all as you want
orderedTypes = new List<MessageType>() {
MessageType.Boo,
MessageType.Bar,
MessageType.Foo,
MessageType.Doo,
};
}
public int Compare(MessageType x, MessageType y) {
var xIndex = orderedTypes.IndexOf(x);
var yIndex = orderedTypes.IndexOf(y);
return xIndex.CompareTo(yIndex);
}
};
How to use:
messages.OrderBy(m => m.MessageType, new MyMessageComparer())
There is a easier way: just create ordereTypes list and use another overload of OrderBy:
var orderedTypes = new List<MessageType>() {
MessageType.Boo,
MessageType.Bar,
MessageType.Foo,
MessageType.Doo,
};
messages.OrderBy(m => orderedTypes.IndexOf(m.MessageType)).ToList();
Hm.. Let's try to take advantages from writing our own IComparer. Idea: write it like our last example but in some other semantic. Like this:
messages.OrderBy(
m => m.MessageType,
new EnumComparer<MessageType>() {
MessageType.Boo,
MessageType.Foo }
);
Or this:
messages.OrderBy(m => m.MessageType, EnumComparer<MessageType>());
Okay, so what we need. Our own comparer:
Must accept enum as generic type (how to solve)
Must be usable with collection initializer syntax (how to)
Must sort by default order, when we have no enum values in our comparer (or some enum values aren't in our comparer)
So, here is the code:
public class EnumComparer<TEnum>: IComparer<TEnum>, IEnumerable<TEnum> where TEnum: struct, IConvertible {
protected static IList<TEnum> TypicalValues { get; set; }
protected IList<TEnum> _reorderedValues;
protected IList<TEnum> ReorderedValues {
get { return _reorderedValues.Any() ? _reorderedValues : TypicalValues; }
set { _reorderedValues = value; }
}
static EnumComparer() {
if (!typeof(TEnum).IsEnum)
{
throw new ArgumentException("T must be an enumerated type");
}
TypicalValues = new List<TEnum>();
foreach (TEnum value in Enum.GetValues(typeof(TEnum))) {
TypicalValues.Add(value);
};
}
public EnumComparer(IList<TEnum> reorderedValues = null) {
if (_reorderedValues == null ) {
_reorderedValues = new List<TEnum>();
return;
}
_reorderedValues = reorderedValues;
}
public void Add(TEnum value) {
if (_reorderedValues.Contains(value))
return;
_reorderedValues.Add(value);
}
public int Compare(TEnum x, TEnum y) {
var xIndex = ReorderedValues.IndexOf(x);
var yIndex = ReorderedValues.IndexOf(y);
// no such enums in our order list:
// so this enum values must be in the end
// and must be ordered between themselves by default
if (xIndex == -1) {
if (yIndex == -1) {
xIndex = TypicalValues.IndexOf(x);
yIndex = TypicalValues.IndexOf(y);
return xIndex.CompareTo(yIndex);
}
return -1;
}
if (yIndex == -1) {
return -1; //
}
return xIndex.CompareTo(yIndex);
}
public void Clear() {
_reorderedValues = new List<TEnum>();
}
private IEnumerable<TEnum> GetEnumerable() {
return Enumerable.Concat(
ReorderedValues,
TypicalValues.Where(v => !ReorderedValues.Contains(v))
);
}
public IEnumerator<TEnum> GetEnumerator() {
return GetEnumerable().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerable().GetEnumerator();
}
}
So, well, let's make sorting more faster. We need to override default OrderBy method for our enums:
public static class LinqEnumExtensions
{
public static IEnumerable<TSource> OrderBy<TSource, TEnum>(this IEnumerable<TSource> source, Func<TSource, TEnum> selector, EnumComparer<TEnum> enumComparer) where TEnum : struct, IConvertible
{
foreach (var enumValue in enumComparer)
{
foreach (var sourceElement in source.Where(item => selector(item).Equals(enumValue)))
{
yield return sourceElement;
}
}
}
}
Yeah, that's lazy. You can google how yield works. Well, let's test speed. Simple benchmark: http://pastebin.com/P8qaU20Y. Result for n = 1000000;
Enumerable orderBy, elementAt: 00:00:04.5485845
Own orderBy, elementAt: 00:00:00.0040010
Enumerable orderBy, full sort: 00:00:04.6685977
Own orderBy, full sort: 00:00:00.4540575
We see, that our own orderBy by is more lazy that standart order by (yeah, it doesn't need to sort everything). And faster even for fullsort.
Problems in this code: it doesn't support ThenBy(). If you need this, you can write your own linq extension that returns IOrderedEnumerable There are a blog post series by Jon Skeet which goes into LINQ to Objects in some depth, providing a complete alternative implementation. The basis of IOrderedEnumerable is covered in part 26a and 26b, with more details and optimization in 26c and 26d.
Instead of using an IComparer, you could also use a SelectMany approach, which should have better performance for large message lists, if you have a fixed number of message types.
var messageTypeOrder = new [] {
MessageType.Boo,
MessageType.Bar,
MessageType.Foo,
MessageType.Doo,
};
List<Message> tempList = messageTypeOrder
.SelectMany(type => messageList.Where(m => m.MessageType == type))
.ToList();
You may avoid writing a completely new type just to implement IComparable. Use the Comparer class instead:
IComparer<Message> comparer = Comparer.Create<Message>((message) =>
{
// lambda that compares things
});
tempList.Sort(comparer);
You can build a mapping dictionary dynamically from the Enum values with LINQ like this:
var mappingDIctionary = new List<string>((string[])Enum.GetNames(typeof(Hexside)))
.OrderBy(label => label )
.Select((i,n) => new {Index=i, Label=n}).ToList();
Now any new values added to the Enum n future will automatically get properly mapped.
Also, if someone decides to renumber, refactor, or reorder the enumeration, everything is handled automatically.
Update:
As pointed out below, Alphabetical ordering was not called for; rather a semi- alphabetical ordering, so essentially random. Although not an answer to this particular question, this technique might be useful to future visitors, so I will leave it standing.
No need to have the mapping. This should give you the list and order based on the enum. You don't have to modify anything even when you change the enum's order or and new items...
var result = (from x in tempList
join y in Enum.GetValues(typeof(MessageType)).Cast<MessageType>()
on x equals y
orderby y
select y).ToList();
If you are about to get this working with Entity Framework (EF), you would have to spread out your enum in your OrderBy as such:
messageList.OrderBy(m =>
m.MessageType == MessageType.Boo ? 0 :
m.MessageType == MessageType.Bar ? 1 :
m.MessageType == MessageType.Foo ? 2 :
m.MessageType == MessageType.Doo ? 3 : 4
);
This creates a sub select with CASE WHEN, then ORDER BY on that temporary column.

List.Sort with lambda expression

I'm trying to sort part of a list with a lambda expression, but I get an error when trying to do so:
List<int> list = new List<int>();
list.Add(1);
list.Add(3);
list.Add(2);
list.Add(4);
// works fine
list.Sort((i1, i2) => i1.CompareTo(i2) );
// "Cannot convert lambda expression to type 'System.Collections.Generic.IComparer<int>' because it is not a delegate type"
list.Sort(1, 2, (i1, i2) => i1.CompareTo(i2) );
foreach (int i in list)
Console.WriteLine(i);
At a guess this is because there's no System.Comparison overload for the sort that takes a range. Is this omitted for any particular reason?
Is there an easy way of getting a suitable IComparer from the lambda expression (like a class I can just use to go list.Sort(1, 2, new CompareyThing<int>((...) => ...)) or something)?
You can use the Comparer.Create method, although this appears to be new in .Net 4.5
list.Sort(1, 2, Comparer<int>.Create((i1, i2) => i1.CompareTo(i2)));
You can always create your own comparer:
public class FuncComparer<T> : IComparer<T>
{
private readonly Func<T, T, int> func;
public FuncComparer(Func<T, T, int> comparerFunc)
{
this.func = comparerFunc;
}
public int Compare(T x, T y)
{
return this.func(x, y);
}
}
Then your code would be:
list.Sort(1, 2, new FuncComparer<int>((i1, i2) => i1.CompareTo(i2)));
You could create a custom comparer if you're not using .Net 4.5:
class IntComparer : IComparer<int>
{
public int Compare(int x, int y)
{
return x.CompareTo(y);
}
}
list.Sort(1, 2, new IntComparer());

Categories

Resources