Proper .NET sorting doubles as strings are numerically

I have an application that displays a dataset that allows me to call custom .NET code and get stuck on the sorting problem. One column in my dataset contains both strings and numeric data, and I want to sort rows in alphabetical order and numbers numerically. All I can do is take the current value that the sorter is working with and return something.

If my list is {"-6", "10", "5"}, I want to create strings from those numbers that will sort them alphabetically. What I came up with makes them positive, and then filling them with zeros, for example:

public object Evaluate(object currentValue) { //add 'a' to beginning of non-numbers, 'b' to beginning of numbers so that numbers come second string sortOrder = ""; if(!currentValue.IsNumber) sortOrder = "a" + currentValue; else { sortOrder = "b" double number = Double.Parse(currentValue); //add Double.MaxValue to our number so that we 'hopefully' get rid of negative numbers, but don't go past Double.MaxValue number += (Double.MaxValue / 2) //pad with zeros so that 5 comes before 10 alphabetically: //"0000000005" //"0000000010" string paddedNumberString = padWithZeros(number.ToString()) //"b0000000005" //"b0000000010" sortOrder += paddedNumberString; } } 

Problem:
If I just return the number, they will be sorted in alphabetical order, and 10 will be sorted to 5, and I don’t even know what will happen to negative numbers.

Decision?:
One is hacked, which I thought was trying to convert from doubles (8 bytes) to unsigned longs (8 bytes). This will save us from negative numbers, since they will start from scratch. The problem of 10 to 5 years still remains. A pad with 0s or something is possible for this ...

It seems that this should be possible, but today I have a mute and can not.

data examples:
"Cat"
'4'
"5.4"
"Dog"
'-400'
"Ant-eater"
'12 .23.34.54 '
"I am the verdict"
'0'

which should be sorted by address: '12 .23.34.54 '
"Ant-eater"
"Cat"
"Dog"
"I am the verdict"
'-400'
'0'
'4'
'5,4'

+4
source share
4 answers

I suspect that after something you call the "Natural Sort Order". Attwood has a post on it: http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html

This article has some sample implementations.

+1
source

Not a very efficient, but simple comparison algorithm that first separates numbers from non-numbers, then sorts between them will work - see the code below. The drawback comes from the fact that we will make a string for double conversion several times, so you can pre-process numbers (i.e., save their double values ​​in List<double?> ), And then use them instead of always parsing them.

 public class StackOverflow_9231493 { public static void Test() { List<string> list = new List<string> { "cat", "4", "5.4", "dog", "-400", "aardvark", "12.23.34.54", "i am a sentence", "0" , }; list.Sort(new Comparison<string>(delegate(string s1, string s2) { double d1, d2; bool isNumber1, isNumber2; isNumber1 = double.TryParse(s1, out d1); isNumber2 = double.TryParse(s2, out d2); if (isNumber1 != isNumber2) { return isNumber2 ? -1 : 1; } else if (!isNumber1) { return s1.CompareTo(s2); } else { return Math.Sign(d1 - d2); } })); Console.WriteLine(string.Join("\n", list)); } } 

Comment Based Update :

If you only want to return something without using the comparison directly, you can use the same logic, but wrap the values ​​in a type that knows how to perform the comparison, as shown below.

 public class StackOverflow_9231493 { public class Wrapper : IComparable<Wrapper> { internal string value; private double? dbl; public Wrapper(string value) { if (value == null) throw new ArgumentNullException("value"); this.value = value; double temp; if (double.TryParse(value, out temp)) { dbl = temp; } } public int CompareTo(Wrapper other) { if (other == null) return -1; if (this.dbl.HasValue != other.dbl.HasValue) { return other.dbl.HasValue ? -1 : 1; } else if (!this.dbl.HasValue) { return this.value.CompareTo(other.value); } else { return Math.Sign(this.dbl.Value - other.dbl.Value); } } } public static void Test() { List<string> list = new List<string> { "cat", "4", "5.4", "dog", "-400", "aardvark", "12.23.34.54", "i am a sentence", "0" , }; List<Wrapper> list2 = list.Select(x => new Wrapper(x)).ToList(); list2.Sort(); Console.WriteLine(string.Join("\n", list2.Select(w => w.value))); } } 
+4
source

I have a solution for you, but this requires an arbitrary fixed maximum row size, but no other set information is required

First define a custom character set as follows:

 public class CustomChar { public static readonly int Base; public static readonly int BitsPerChar; public char Original { get; private set; } public int Target { get; private set; } private static readonly Dictionary<char, CustomChar> Translation; private static void DefineOrderedCharSet(string charset) { foreach (var t in charset) { new CustomChar(t); } } static CustomChar() { Translation = new Dictionary<char, CustomChar>(); DefineOrderedCharSet(",-.0123456789 aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"); BitsPerChar = (int)Math.Ceiling(Math.Log(Translation.Count, 2)); Base = (int) Math.Pow(2, BitsPerChar); } private CustomChar(char original) { Original = original; if(Translation.Count > 0) { Target = Translation.Max(x => x.Value.Target) + 1; } else { Target = 0; } Translation[original] = this; } public static CustomChar Parse(char original) { return Translation[original]; } } 

Then, define a construct to handle the conversion from string to System.Numeric.BigInteger as follows

 public class CustomString { public string String { get; private set; } public BigInteger Result { get; private set; } public const int MaxChars = 600000; public CustomString(string source) { String = source; Result = 0; for (var i = 0; i < String.Length; i++) { var character = CustomChar.Parse(String[i]); Result |= (BigInteger)character.Target << (CustomChar.BitsPerChar * (MaxChars - i - 1)); } double doubleValue; if (!double.TryParse(source, out doubleValue)) { return; } Result = new BigInteger(0x7F) << (MaxChars * CustomChar.BitsPerChar); var shifted = (BigInteger)(doubleValue * Math.Pow(2, 32)); Result += shifted; } public static implicit operator CustomString(string source) { return new CustomString(source); } } 

Note that ctor for CustomString finds duplicates and complements their BigInteger views to order objects for sorting numeric values.

This is a pretty quick throw, but you get your test result:

 class Program { public static string[] Sort(params CustomString[] strings) { return strings.OrderBy(x => x.Result).Select(x => x.String).ToArray(); } static void Main() { var result = Sort( "cat", "4", "5.4", "dog", "-400", "aardvark", "12.23.34.54", "i am a sentence", "0"); foreach (var str in result) { Console.WriteLine(str); } Console.ReadLine(); } } 
+2
source

I assume that your data is of type string , not object . The following function can be called with a Comparison<string> delegate.

 static int CompareTo(string string1, string string2) { double double1, double2; // Add null checks here if necessary... if (double.TryParse(string1, out double1)) { if (double.TryParse(string2, out double2)) { // string1 and string2 are both doubles return double1.CompareTo(double2); } else { // string1 is a double and string2 is text; string2 sorts first return 1; } } else if (double.TryParse(string2, out double2)) { // string1 is text and string2 is a double; string1 sorts first return -1; } else { // string1 and string2 are both text return string1.CompareTo(string2); } } 

You can check it as follows:

 static void Main(string[] args) { var list = new List<string>() { "cat", "4", "5.4", "dog", "-400", "aardvark", "12.23.34.54", "i am a sentence", "0" }; list.Sort(CompareTo); foreach (var item in list) Console.WriteLine(item); } 
0
source

Source: https://habr.com/ru/post/1395807/


All Articles