How to Sort a Dictionary by key string length - c#

I have a Sorted Dictionary that I want to Sort By Key Length.
Dictionary is defined as:
private SortedDictionary<String, String> _replacementDictionary;
and initialised as:
_replacementDictionary = new SortedDictionary<string, string>(new LengthComparer());
I thought I could use a Custom Sort like this:
class LengthComparer : IComparer<String>
{
public int Compare(string x, string y)
{
return x.Length.CompareTo(y.Length);
}
}
But this doesn't work for keys that are the same length. i.e. If I add keys "abc" and "xyz", only one of these shows when I enumerate the dictionary

If you mean you want to sort by length, then the key itself as a string, try:
class LengthComparer : IComparer<String>
{
public int Compare(string x,string y)
{
int lengthComparison=x.Length.CompareTo(y.Length)
if(lengthComparison==0)
{
return x.CompareTo(y);
}
else
{
return lengthComparison;
}
}
}
What this code does is this: It makes a comparison based on length. If the two strings are tied for length, then it resolves the tie by comparing the strings themselves, not their length. You need to resolve this tie because of the reason #Adriano gave in his comment.

List<KeyValuePair<string, string>> list = _replacementDictionary.ToList();
list.Sort((firstPair, nextPair) =>
{
return firstPair.Key.Length.CompareTo(nextPair.Key.Length);
}
);
_replacementDictionary = list.ToDictionary(pair => pair.Key, pair => pair.Value);
You haven't specified that you wanted to continue down the route of IComparer so I hope this code is still suitable. You can wrap it in an extension if needs be.
Note: I can't try and compile it at the moment so may need cleaning up a bit.

Here is what I use.
using System.Collections.Generic;
public class KeyLengthSortedDictionary : SortedDictionary<string, string>
{
public int Compare(string x, string y)
{
if (x == null) throw new ArgumentNullException(nameof(x));
if (y == null) throw new ArgumentNullException(nameof(y));
var lengthComparison = x.Length.CompareTo(y.Length);
return lengthComparison == 0 ? string.Compare(x, y, StringComparison.Ordinal) : lengthComparison;
}
public KeyLengthSortedDictionary() : base(new StringLengthComparer()) { }
}
Here is a demo : https://dotnetfiddle.net/plmFLL

Related

Get key in dictionary with optional tuple parameter

I have a dictionary that looks like this Dictionary<Tuple<int, int, bool>, string>
Let's say I have an entry like (1, 2, true) = "Word"
I want to be able to index the dictionary by only doing (1, null, true) and I want it to return the entry I mentioned before since the first and third values in the tuple are the same (ignoring the second since it's null)
Is there a way to do this? If not what is a suggestion for a new structure that would accomplish this? Thanks
I'd suggest you to create a struct to use instead of your Tuple, overriding GethashCode() (implement Equals too, to be consistent) :
public struct MyStruct
{
public int? FirstNumber { get; set; }
public int? SecondNumber { get; set; }
public bool TheBoolean { get; set; }
public override bool Equals(object obj)
{
return this.Equals((MyStruct)obj);
}
public bool Equals(MyStruct obj)
{
return (!obj.FirstNumber.HasValue || !this.FirstNumber.HasValue ||
obj.FirstNumber.Value == this.FirstNumber.Value)
&& (!obj.SecondNumber.HasValue || !this.SecondNumber.HasValue ||
obj.SecondNumber.Value == this.SecondNumber.Value)
&& obj.TheBoolean == this.TheBoolean;
}
public override int GetHashCode()
{
return (!this.FirstNumber.HasValue ? 0 : this.FirstNumber.GetHashCode())
^ (!this.SecondNumber.HasValue ? 0 : this.SecondNumber.GetHashCode())
^ this.TheBoolean.GetHashCode();
}
}
public static void Test()
{
var obj1 = new MyStruct
{
FirstNumber = 1,
SecondNumber = 2,
TheBoolean = true
};
var obj2 = new MyStruct
{
FirstNumber = 1,
SecondNumber = null,
TheBoolean = true
};
var dico = new Dictionary<MyStruct, string>
{
{ obj1, "Word" }
};
var result = dico[obj2];
Console.WriteLine(result);
}
The idea behind a Dictionary<TKey, TValue> is that you really need to have identical match between your TKey, TValuepairs. So, according to your structure, you could try something like this:
Dictionary<tuple<int, bool>, string>
where, in the dictionary creation step, you take in two ints, and combine them into a new, unitque value to add to your dictionary. Here is an example:
public static void AddToDictionary(
this Dictionary<Tuple<int, bool>, string> dictionary,
Tuple<int, int, bool> oldKey,
string value)
{
var key = new Tuple<int, bool>(oldKey.Item1 + oldKey.Item2, oldKey.Item3);
dictionary[key] = value;
}
Bear in mind that the key values must be UNIQUE. Otherwise, you will overwrite older entries.
Another structure that you can have is a Dictionary of Dictionaries. Since you are only interested in the first int, and since the second int might be null, you can have this:
Dictionary<int, Dictionary<Tuple<int?, bool>, string>>
This way, you can get something like this:
var keyTuple = new Tuple<int, int?, bool>(1, null, true);
myDictionary[keyTuple .Item1][new Tuple<int?, bool>(keyTuple.Item2, keyTuple.Item3)]
= "string value";
But, again, it starts to get verbose... a lot.

Sort objects by string propery, empty string last

I have an array of objects which all contain string property. I want to sort objects by string property alphabetically in a way that objects with empty string property come at the end of the list. Currently I have this:
switches = switches.OrderBy(n => n.GetCurrentUser()).ToArray();
The problem is that it puts empty strings at the top of the list. How do I put objects with strings with value (sorted alphabetically) at the top and objects with empty strings at the bottom?
You can use:
switches = switches
.Select(n => new { TheObject = n, User = n.GetCurrentUser() })
.OrderBy(x => String.IsNullOrEmpty(x.User) ? 1 : 0)
.ThenBy(x => x.User)
.Select(x => x.TheObject)
.ToArray();
This will first build two groups, the one with empty user and others. OrderBy will move them to the end because 1 is more than 0. If you want them at the top use OrderByDescending.
Then i use ThenBy to sort alphabetically which will only matter for the non-empty users.
You can also use inline Comparer creation:
switches.OrderBy(n => n.GetCurrentUser(),
Comparer<string>.Create((a, b) =>
string.IsNullOrEmpty(a) && !string.IsNullOrEmpty(b)? 1
: !string.IsNullOrEmpty(a) && string.IsNullOrEmpty(b) ? -1
: string.Compare(a, b)));
OrderBy has an overload that accepts an IComparer<>T. This allows you to define your own sorting rules. You can start with the generic Comparer class and override the Compare method, eg :
public class EmptyLastComparer: Comparer<string>
{
public override int Compare(string x, string y)
{
if (String.IsNullOrWhiteSpace(x) && !String.IsNullOrWhiteSpace(y))
{
return 1;
}
else if (String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
{
return 0;
}
else if (!String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
{
return -1;
}
else
{
return x.CompareTo(y);
}
}
}
To use it, creatre a new EmptyLastComparer() instance and pass it to OrderBy :
var myStrings = new[] { "c", "A","a", "A","b", " "," ",null };
var ordered=myStrings.OrderBy(x => x, new EmptyLastComparer());
String comparison is more complex than just comparing two strings. String.Compare has overloads that allow case-insensitive comparisons, using specific cultures etc. The custom comparer could accepts a StringComparison parameter in its constructor to allow something similar, eg :
public class EmptyLastComparer : Comparer<string>
{
private readonly StringComparison _comparison;
public EmptyLastComparer(StringComparison comparison=StringComparison.CurrentCulture)
{
_comparison = comparison;
}
public override int Compare(string x, string y)
{
if (String.IsNullOrWhiteSpace(x) && !String.IsNullOrWhiteSpace(y))
{
return 1;
}
else if (String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
{
return 0;
}
else if (!String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
{
return -1;
}
else
{
return String.Compare(x,y, _comparison);
}
}
}
Perhaps even add some predefined comparers, just like StringComparer does :
public static EmptyLastComparer CurrentCulture =>
new EmptyLastComparer();
public static EmptyLastComparer CurrentCultureIgnoreCase =>
new EmptyLastComparer(StringComparison.CurrentCultureIgnoreCase);
public static EmptyLastComparer InvariantCulture =>
new EmptyLastComparer(StringComparison.InvariantCulture);
public static EmptyLastComparer InvariantCultureIgnoreCase =>
new EmptyLastComparer(StringComparison.InvariantCultureIgnoreCase);
public static EmptyLastComparer Ordinal =>
new EmptyLastComparer(StringComparison.Ordinal);
public static EmptyLastComparer OrdinalIgnoreCase =>
new EmptyLastComparer(StringComparison.OrdinalIgnoreCase);
And use them the same way, without allocating a new comparer each time :
var ordered=myStrings.OrderBy(x => x, EmptyLastComparer.InvariantCultureIgnoreCase);

How to override general comparison for two strings in C#

I've a question like the link below but much more complicated:
Ignoring accented letters in string comparison
I have a dictionary and some values inside like:
{[Ministère de l'économie, 139]}
{[Ministère des finances, 114]}
and for inserting new elements into the dictionary, as the sames codes, I wrote this:
if (!dict.ContainsKey(str))
{ dict.Add(str, Convert.ToDouble(number)); }
But when I want to check the existence of this value :
{[Ministère de l'economie, 139]} it returns it doesn't exist.
How can I implement this response to my code?
string.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace);
On the other way, as the title of my question, how can I overwrite the general (or the main) string comparison method in my application?
So, combining with the linked answer for removal of accents and diacritics, you can make an IEqualityComparer<string> to supply to your dictionary:
public class IgnoreAccentsAndDiacriticsComparer:IEqualityComparer<string>
{
public bool Equals(string left, string right)
{
if(left == null && right == null){ return true; }
if(left == null || right == null){ return false; }
return string.Equals(RemoveDiacritics(left), RemoveDiacritics(right));
}
public int GetHashCode(string txt)
{
return RemoveDiacritics(txt).GetHashCode();
}
static string RemoveDiacritics(string text)
{
string formD = text.Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
foreach (char ch in formD)
{
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(ch);
if (uc != UnicodeCategory.NonSpacingMark)
{
sb.Append(ch);
}
}
return sb.ToString().Normalize(NormalizationForm.FormC);
}
}
Now, construct your dictionary using an instance of this comparer:
var myDic = new Dictionary<string, int>(new IgnoreAccentsAndDiacriticsComparer())
and try adding something accented:
myDic["Aimée"] = 1;
...and read it back out, without the accent:
Console.WriteLine(myDic["Aimee"]); //ŵôõ!
Starting from .NET 4.6 there are some interesting overloads of CompareInfo, so that the first solution suggested in https://stackoverflow.com/a/368850/613130 is usable in an IEqualityComparer<string>:
public class StringComparerIgnoreDiacritics : IEqualityComparer<string>
{
public static readonly StringComparerIgnoreDiacritics CurrentCulture = new StringComparerIgnoreDiacritics(CultureInfo.CurrentCulture.CompareInfo);
public readonly CompareInfo CompareInfo;
public StringComparerIgnoreDiacritics(CompareInfo compareInfo)
{
CompareInfo = compareInfo;
}
#region IEqualityComparer<string> Members
public bool Equals(string x, string y)
{
return CompareInfo.Compare(x, y, CompareOptions.IgnoreNonSpace) == 0;
}
public int GetHashCode(string obj)
{
return CompareInfo.GetHashCode(obj, CompareOptions.IgnoreNonSpace);
}
#endregion
}
and then
var myDic = new Dictionary<string, int>(var myDic = new Dictionary<string, int>(new IgnoreAccentsAndDiacriticsComparer()).CurrentCulture);

Sort List with duplicate keys by custom comparer

I have the List inputColl of type MyValue with many duplicate keys:
myValue1.Id=100;
myValue2.Id=100;
...etc
And I have custom comparer to compare two MyValue elements by Id:
inputColl.Sort(myValueComparer);
What am I doing wrong?
Comparer:
public class MyValueComparerById : IComparer<MyValue>
{
public int Compare(MyValue x, MyValue y)
{
if (x.Id == y.Id)
return 0;
else if (x.Id > y.Id)
return -1;
else if (x.Id < y.Id)
return 1;
return 0;
}
}
Unless your equality comparer is not implemented badly, your solution should work.
But I suggest an easier approach, using linq:
inputCol = inputCol.OrderBy(o => o.Id).ToList();
You already have an int comparer so is better to use it instead of rewriting same logic:
public class MyValueComparerById : IComparer<MyValue>
{
public int Compare(MyValue x, MyValue y)
{
return x.Id.CompareTo(y.Id);
}
}
** Update edit **
For further improvements maybe you might want to consider additional comparison in case of Id equality:
public class MyValueComparerById : IComparer<MyValue>
{
public int Compare(MyValue x, MyValue y)
{
var firstResult = x.Id.CompareTo(y.Id);
if (firstResult == 0)
{
// I'm assuming that MyValue has an additional string property named 'SomeName'
return x.SomeName.CompareTo(y.SomeName);
}
return firstResult;
}
}

How to sort with 2 or more sortexpression using comparer

I must use a self-coded comparer to sort a list of objects.
myObjectList.Sort(new MyComparer(_sortExpression, _sortDirection));
How can I append a 2nd expression and direction to sort by if values on 1st expression are equal?
I mean e.g.: Sort by last name and then if equal by first name. But I must use my own comparer.
myObjectList.Sort(
new MyComparer(new string[]{_exp1, _exp2}, new string[]{_dir1, _dir2}));
Thanks in advance!
Lord Vader
You could define an extension method that allows to chain comparers as follows:
public static class ComparerExtensions
{
public static IComparer<T> ThenBy<T>(this IComparer<T> comparer1,
IComparer<T> comparer2)
{
return new ChainedComparer<T>(comparer1, comparer2);
}
private class ChainedComparer<T> : IComparer<T>
{
private readonly IComparer<T> comparer1;
private readonly IComparer<T> comparer2;
public ChainedComparer(IComparer<T> comparer1,
IComparer<T> comparer2)
{
this.comparer1 = comparer1;
this.comparer2 = comparer2;
}
public int Compare(T x, T y)
{
int result = this.comparer1.Compare(x, y);
if (result == 0) result = this.comparer2.Compare(x, y);
return result;
}
}
}
Usage:
var comparer = new MyComparer(_sortExpression1, _sortDirection1).ThenBy(
new MyComparer(_sortExpression2, _sortDirection2));
myObjectList.Sort(comparer);
This sorts the list by the first comparer and, if two items are equal, then by the second comparer.
Alternative solution:
class ChainComparer<T> : IComparer<T>
{
private readonly IEnumerable<IComparer<T>> comparers;
public ChainComparer(params IComparer<T>[] comparers)
{
this.comparers = comparers;
}
public int Compare(T x, T y)
{
int result = 0;
foreach (var comparer in this.comparers)
{
result = comparer.Compare(x, y);
if (result != 0) break;
}
return result;
}
}
Usage:
myObjectList.Sort(new ChainComparer<MyClass>(
new MyComparer(_sortExpression1, _sortDirection1),
new MyComparer(_sortExpression2, _sortDirection2)));
You could just concatenate the two strings together and do a normal sort on that value. It would give you the same result. Or if you can use link you could do:
myObjectList.OrderBy(p => p.LastName).ThenBy(p => p.FirstName);
There are also OrderByDescending and ThenByDescending methods as well.

Categories