Как отсортировать числовые строки в числовые? - PullRequest
11 голосов
/ 09 августа 2009

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

"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 сортирует файлы так, как я хотел.

Ответы [ 6 ]

12 голосов
/ 09 августа 2009

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

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

var sorted = arr.OrderBy(s => int.Parse(s.Substring(5)));
foreach (string s in sorted) {
    Console.WriteLine(s);
}
10 голосов
/ 09 августа 2009

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

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
9 голосов
/ 09 августа 2009

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

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

6 голосов
/ 09 апреля 2011

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

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);
2 голосов
/ 09 августа 2009

Я использовал следующий подход в проекте некоторое время назад. Это не особенно эффективно, но если количество сортируемых элементов невелико, оно достаточно хорошо для этого использования. Что он делает, так это то, что он разбивает строки для сравнения на массивы символа '_', а затем сравнивает каждый элемент массивов. Предпринята попытка проанализировать последний элемент как 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);

});
2 голосов
/ 09 августа 2009

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

file_00001
file_00002
file_00010
file_00011

и т.д.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...