How to sort out numeric strings as numerics?

前端 未结 6 2006
面向向阳花
面向向阳花 2020-12-30 09:04

If you have strings like:

\"file_0\"
\"file_1\"
\"file_2\"
\"file_3\"
\"file_4\"
\"file_5\"
\"file_6\"
\"file_11\"

how can you sort them so

相关标签:
6条回答
  • 2020-12-30 09:19

    The following code based on Joey's suggestion works for me (extension method to string[]):

    public static void SortLogical(this string[] files)
    {
        Array.Sort<string>(files, new Comparison<string>(StrCmpLogicalW));
    }
    
    [DllImport("shlwapi.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
    private static extern int StrCmpLogicalW(String x, String y);
    
    0 讨论(0)
  • 2020-12-30 09:25

    You could import the StrCmpLogicalW function and use that to sort the strings. This is the very same function that Explorer itself uses for file names.

    Won't help you if you don't want P/Invoke or stay compatible on other systems, though.

    0 讨论(0)
  • 2020-12-30 09:26

    A simple way is to pad the numeric portion like so:

    file_00001
    file_00002
    file_00010
    file_00011
    

    etc.

    But this reles on knowing the maximum value the numeric portion can take.

    0 讨论(0)
  • 2020-12-30 09:28

    Do I have to parse the string and convert it into a number for this?

    Essentially, yes; but LINQ may help:

    var sorted = arr.OrderBy(s => int.Parse(s.Substring(5)));
    foreach (string s in sorted) {
        Console.WriteLine(s);
    }
    
    0 讨论(0)
  • 2020-12-30 09:29

    To handle sorting of intermixed strings and numbers for any kind of format, you can use a class like this to split the strings into string and number components and compare them:

    public class StringNum : IComparable<StringNum> {
    
       private List<string> _strings;
       private List<int> _numbers;
    
       public StringNum(string value) {
          _strings = new List<string>();
          _numbers = new List<int>();
          int pos = 0;
          bool number = false;
          while (pos < value.Length) {
             int len = 0;
             while (pos + len < value.Length && Char.IsDigit(value[pos+len]) == number) {
                len++;
             }
             if (number) {
                _numbers.Add(int.Parse(value.Substring(pos, len)));
             } else {
                _strings.Add(value.Substring(pos, len));
             }
             pos += len;
             number = !number;
          }
       }
    
       public int CompareTo(StringNum other) {
          int index = 0;
          while (index < _strings.Count && index < other._strings.Count) {
             int result = _strings[index].CompareTo(other._strings[index]);
             if (result != 0) return result;
             if (index < _numbers.Count && index < other._numbers.Count) {
                result = _numbers[index].CompareTo(other._numbers[index]);
                if (result != 0) return result;
             } else {
                return index == _numbers.Count && index == other._numbers.Count ? 0 : index == _numbers.Count ? -1 : 1;
             }
             index++;
          }
          return index == _strings.Count && index == other._strings.Count ? 0 : index == _strings.Count ? -1 : 1;
       }
    
    }
    

    Example:

    List<string> items = new List<string> {
      "item_66b",
      "999",
      "item_5",
      "14",
      "file_14",
      "26",
      "file_2",
      "item_66a",
      "9",
      "file_10",
      "item_1",
      "file_1"
    };
    
    items.Sort((a,b)=>new StringNum(a).CompareTo(new StringNum(b)));
    
    foreach (string s in items) Console.WriteLine(s);
    

    Output:

    9
    14
    26
    999
    file_1
    file_2
    file_10
    file_14
    item_1
    item_5
    item_66a
    item_66b
    
    0 讨论(0)
  • 2020-12-30 09:35

    I have used the following approach in a project a while ago. It's not particularly efficient, but if the number of items to sort is not huge it performed well enough for that use. What it does is that it splits up the strings to compare into arrays on the '_' character, and then compares each element of the arrays. An attempt is made to parse the last element as an int, and make a numeric comparison there.

    It also has an early exit if the input strings would contain a different number of elements (so if you compare "file_nbr_1" to "file_23", it will not go into comparing each part of the strings, but rather just to a regular string comparison on the full strings):

    char[] splitChars = new char[] { '_' };
    string[] strings = new[] {
        "file_1",
        "file_8",
        "file_11",
        "file_2"
    };
    
    Array.Sort(strings, delegate(string x, string y)
    {
        // split the strings into arrays on each '_' character
        string[] xValues = x.Split(splitChars);
        string[] yValues = y.Split(splitChars);
    
        // if the arrays are of different lengths, just 
        //make a regular string comparison on the full values
        if (xValues.Length != yValues.Length)
        {
            return x.CompareTo(y);
        }
    
        // So, the arrays are of equal length, compare each element
        for (int i = 0; i < xValues.Length; i++)
        {
            if (i == xValues.Length - 1)
            {
                // we are looking at the last element of the arrays
    
                // first, try to parse the values as ints
                int xInt = 0;
                int yInt = 0;
                if (int.TryParse(xValues[i], out xInt) 
                    && int.TryParse(yValues[i], out yInt))
                {
                    // if parsing the values as ints was successful 
                    // for both values, make a numeric comparison 
                    // and return the result
                    return xInt.CompareTo(yInt);
                }
            }
    
            if (string.Compare(xValues[i], yValues[i], 
                StringComparison.InvariantCultureIgnoreCase) != 0)
            {
                break;
            }
        }
    
        return x.CompareTo(y);
    
    });
    
    0 讨论(0)
提交回复
热议问题