Home > front end >  C# Sort a Dictionary by The Numerical value of string (Key)
C# Sort a Dictionary by The Numerical value of string (Key)

Time:06-28

interesting question here. I'm trying to sort a Dictionary of strings by numerical value. It works in a List but not in a Dictionary, what am I doing wrong here?

var v = 0;

Dictionary<string, string> s = new Dictionary<string, string>();
s.Add("a", "a");
s.Add("100", "1");
s.Add("2", "2");
s.Add("10", "10");
List<string> g = new List<string> { "1", "10", "a", "1000" };

var problem = s.OrderBy(x => int.TryParse(x.Key,out v));
// outputs original order
var works = from c in g
             orderby int.TryParse(c,out v)
             select c;
//outputs sorted order a,1,10,1000

CodePudding user response:

You have two distinct, massive problems, that you need to understand.

  1. You are not sorting by v. You are sorting by whatever is returned by the comparer passed into the OrderBy call, which in your sample is a bool. So, you are doing this:
var g = new List<string> { "1", "10", "a", "1000" };
var works = from c in g
            orderby int.TryParse(c,out v)
            select c;
// It doesn't work, because int.TryParse outputs [true, true, false, true]
// which is used as a sorting key, which puts the 'a' as the first element,
// because false is sorted before true.
// try with this input:
var g = new List<string> { "10", "1", "a", "1000" };
// and you'll see that the output is ["a", "10", "1", "1000"]

So, your sorting function should be int.TryParse(c,out v) ? v : -1. The -1 could be something different if you need a sorting also on the invalid keys, but that can be addressed by a ThenBy after the OrderBy.

BUT, you still have a major bug:

  1. You are always updating the single variable v. If the value is calculated and stored in a shared variable it could easily lead to problems with late evaluation, so you should remove var v from the external scope and use
// the addition of 'var' in the out declaration enables the usage of a separate, new v for each item in the source,
// thus removing the possibility of collisions.
s.OrderBy(x => int.TryParse(x.Key, out var v) ? v : -1);

CodePudding user response:

You can try something like this:

var problem = s
  .OrderBy(pair => int.TryParse(pair.Key, out var parsed) 
     ? parsed          // Take Key value as it is
     : long.MinValue); // Treat pair as the topmost

Here we try to parse Key from the key-value pair and in case we fail, we put the pair on the top (I've used long.MinValue in order to mix with possible int.MinValue key). You may want to add .ThenBy:

var problem = s
  .OrderBy(pair => int.TryParse(pair.Key, out var parsed) 
     ? parsed          // Take Key value as it is
     : long.MinValue)  // Treat pair as the topmost
  .ThenBy(pair => pair.Key);

if you want to sort non-number keys as well as numbers, e.g.

  Dictionary<string, string> demo = new() {
    {   "a",  "a" },
    { "100",  "1" },
    {   "2",  "2" },
    {   "b",  "a" },
    {  "10", "10" },
    {   "c",  "a" },
  };
 
  var result = demo
    .OrderBy(pair => int.TryParse(pair.Key, out var parsed) 
       ? parsed 
       : long.MinValue)
    .ThenBy(pair => pair.Key); 

  Console.Write($"{string.Join(Environment.NewLine, result)}");

Output:

[a, a]
[b, a]
[c, a]
[2, 2]
[10, 10]
[100, 1]
  •  Tags:  
  • c#
  • Related