Как отсортировать числовые строки в виде чисел?

Если у вас есть строки вроде:

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

как вы можете отсортировать их так, чтобы "file_11" не появился после "file_1", но появляется после "file_6", начиная с 11 > 6.

Должен ли я разбирать строку и преобразовывать ее в число для этого?

Проводник Windows в Win7 сортирует файлы так, как я хотел.

Ответ 1

Вы можете импортировать функцию StrCmpLogicalW и использовать ее для сортировки строк. Это та самая функция, которую сам Explorer использует для имен файлов.

Не поможет, если вы не хотите, чтобы P/Invoke или оставался совместимым с другими системами.

Ответ 2

Должен ли я разбирать строку и преобразовывать ее в число для этого?

По существу, да; но LINQ может помочь:

var sorted = arr.OrderBy(s => int.Parse(s.Substring(5)));
foreach (string s in sorted) {
    Console.WriteLine(s);
}

Ответ 3

Чтобы обрабатывать сортировку смешанных строк и чисел для любого формата, вы можете использовать такой класс, чтобы разбить строки на строки и числовые компоненты и сравнить их:

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;
   }

}

Пример:

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);

Вывод:

9
14
26
999
file_1
file_2
file_10
file_14
item_1
item_5
item_66a
item_66b

Ответ 4

Следующий код на основе предложения Joey работает для меня (метод расширения для строки []):

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);

Ответ 5

Простым способом является наложение числовой части следующим образом:

file_00001
file_00002
file_00010
file_00011

и др.

Но это reles при знании максимального значения, которое может принимать числовая часть.

Ответ 6

Я использовал следующий подход в проекте некоторое время назад. Это не особенно эффективно, но если количество предметов, которые нужно сортировать, не огромно, оно достаточно хорошо работает для этого использования. Что он делает, так это то, что он разбивает строки на сравнение с массивами на символе '_', а затем сравнивает каждый элемент массивов. Сделана попытка проанализировать последний элемент как int и сделать там числовое сравнение.

У него также есть ранний выход, если входные строки будут содержать различное количество элементов (поэтому, если вы сравните "file_nbr_1" с "file_23", он не будет сравнивать каждую часть строк, а скорее просто регулярное сравнение строк по полным строкам):

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);

});