Home > Programmierung > Strings Sortieren wie im Windows-Explorer

Strings Sortieren wie im Windows-Explorer

Der Windows Explorer sortiert Dateinamen seit den letzten Windows-Versionen so, wie es Menschen erwarten und nicht so, wie es Programmierer tun – er beendet also die Ära der Dateinamen die mit 01, 02, usw. geendet haben. Er macht dies möglich indem er erkennt, wenn Zahlen vorhanden sind und Dateinamen so sortiert, wie es Menschen erwarten. Z.B. wird aus den Dateien

  • A1B.txt
  • A10B.txt
  • A2B.txt

die sortierte Folge

  1. A1B.txt
  2. A2B.txt
  3. A10B.txt

Im Gegensatz zur lexikographischen Sortierung, welche die 10 vor die 2 setzen würde. Gesucht wurde als eine Methode zwei Strings miteinander zu vergleichen, und Zahlen als Zahlen zu verwenden. Dazu müssen beide Strings von vorne durchlaufen werden und jeweils in Zahlenteile und Text/Symbol-Teile unterteilt werden, welche dann jeweils miteinander verglichen werden können.

Dazu habe ich folgende Klasse implementiert, die von IComparer<string> erbt und so wiederverwendbar ist:

public class NumericStringComparer1 : IComparer<string> { private static char DecimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator[0]; public NumericStringComparer1() { SortDirection = ListSortDirection.Ascending; } public string PropertyName { get; set; } public ListSortDirection SortDirection { get; set; } /// <summary> /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. /// </summary> /// <param name="x">The first object to compare.</param> /// <param name="y">The second object to compare.</param> /// <returns> /// A signed integer that indicates the relative values of <paramref name="x" /> and <paramref name="y" />, as shown in the following table.Value Meaning Less than zero<paramref name="x" /> is less than <paramref name="y" />.Zero<paramref name="x" /> equals <paramref name="y" />.Greater than zero<paramref name="x" /> is greater than <paramref name="y" />. /// </returns> public int Compare(string x, string y) { int result = 0; if (x == null) result = -1; else if (y == null) result = 1; else { int i = 0, j = 0; double dx, dy; string sx = null, sy = null; while (i < x.Length && j < y.Length) { sx = GetStringPart(x, i, out i); sy = GetStringPart(y, j, out j); if (sx == null && sy == null) { // We only get the numbers, if we need to dx = GetNumericPart(x, i, out i); dy = GetNumericPart(y, j, out j); result = dx.CompareTo(dy); // For Numbers as Strings, which are equal, the string-length is also relevant if (result == 0) result = i.CompareTo(j); } else if (sx == null) result = -1; else if (sy == null) result = 1; else { result = sx.CompareTo(sy); } i = Math.Min(i + 1, x.Length); j = Math.Min(j + 1, x.Length); if (result != 0) break; } } if (result != 0 && this.SortDirection != ListSortDirection.Ascending) result *= -1; return result; } /// <summary> /// Gets the numeric value from the string starting at the given startIndex. /// The endIndex is set to the last character that is numeric /// If no numeric value is found, Double.NaN is returned. /// </summary> /// <param name="stringVal">The string value.</param> /// <param name="startIndex">The start index.</param> /// <param name="endIndex">The end index.</param> /// <returns></returns> internal static double GetNumericPart(string stringVal, int startIndex, out int endIndex) { double ret = Double.NaN; StringBuilder sb = new StringBuilder(); bool decimalAdded = false; endIndex = startIndex; for (int i = startIndex; i < stringVal.Length; i++) { if (char.IsNumber(stringVal[i])) { sb.Append(stringVal[i]); } else if (decimalAdded == false && stringVal[i] == DecimalSeparator) { decimalAdded = true; sb.Append(stringVal[i]); } else { break; } endIndex = i; } if (sb.Length > 0) ret = double.Parse(sb.ToString()); return ret; } /// <summary> /// Gets the string part from the given string starting at the given index. /// If nos string-part is found (meaning there is a number at the given startIndes), null is returned /// and the endIndex is set to the startIndex. /// /// </summary> /// <param name="stringVal">The string value.</param> /// <param name="startIndex">The start index.</param> /// <param name="endIndex">The end index.</param> /// <returns></returns> internal static string GetStringPart(string stringVal, int startIndex, out int endIndex) { endIndex = startIndex; bool stringFound = false; for (int i = startIndex; i < stringVal.Length; i++) { char c = stringVal[i]; if (char.IsDigit(stringVal[i]) == false) { endIndex = i; stringFound = true; } else { break; } } if (stringFound) { return stringVal.Substring(startIndex, endIndex - startIndex + 1); } else return null; } }

Be Sociable, Share!
KategorienProgrammierung Tags: ,
  1. Bisher keine Kommentare
  1. Bisher keine Trackbacks