Какой самый короткий способ в .NET сортировать строки, начинающиеся с 1, 10 и 2, и соблюдать порядок номеров? - PullRequest
10 голосов
/ 26 августа 2011

Мне нужно отсортировать имена файлов следующим образом: 1.log, 2.log, 10.log

Но когда я использую OrderBy (fn => fn), он будет сортировать их как: 1.log,10.log, 2.log

Я, очевидно, знаю, что это можно сделать, написав другой компаратор, но есть ли более простой способ перехода от лексикографического порядка к естественному порядку сортировки?

Редактировать: цель состоит в том, чтобы получить тот же порядок, что и при выборе «порядка по имени» в проводнике Windows.

Ответы [ 8 ]

7 голосов
/ 26 августа 2011

Вы можете использовать функцию Win32 CompareStringEx. В Windows 7 она поддерживает необходимую вам сортировку. Вам придется использовать P / Invoke:

static readonly Int32 NORM_IGNORECASE = 0x00000001;
static readonly Int32 NORM_IGNORENONSPACE = 0x00000002;
static readonly Int32 NORM_IGNORESYMBOLS = 0x00000004;
static readonly Int32 LINGUISTIC_IGNORECASE = 0x00000010;
static readonly Int32 LINGUISTIC_IGNOREDIACRITIC = 0x00000020;
static readonly Int32 NORM_IGNOREKANATYPE = 0x00010000;
static readonly Int32 NORM_IGNOREWIDTH = 0x00020000;
static readonly Int32 NORM_LINGUISTIC_CASING = 0x08000000;
static readonly Int32 SORT_STRINGSORT = 0x00001000;
static readonly Int32 SORT_DIGITSASNUMBERS = 0x00000008; 

static readonly String LOCALE_NAME_USER_DEFAULT = null;
static readonly String LOCALE_NAME_INVARIANT = String.Empty;
static readonly String LOCALE_NAME_SYSTEM_DEFAULT = "!sys-default-locale";

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern Int32 CompareStringEx(
  String localeName,
  Int32 flags,
  String str1,
  Int32 count1,
  String str2,
  Int32 count2,
  IntPtr versionInformation,
  IntPtr reserved,
  Int32 param
);

Затем вы можете создать IComparer, который использует флаг SORT_DIGITSASNUMBERS:

class LexicographicalComparer : IComparer<String> {

  readonly String locale;

  public LexicographicalComparer() : this(CultureInfo.CurrentCulture) { }

  public LexicographicalComparer(CultureInfo cultureInfo) {
    if (cultureInfo.IsNeutralCulture)
      this.locale = LOCALE_NAME_INVARIANT;
    else
      this.locale = cultureInfo.Name;
  }

  public Int32 Compare(String x, String y) {
    // CompareStringEx return 1, 2, or 3. Subtract 2 to get the return value.
    return CompareStringEx( 
      this.locale, 
      SORT_DIGITSASNUMBERS, // Add other flags if required.
      x, 
      x.Length, 
      y, 
      y.Length, 
      IntPtr.Zero, 
      IntPtr.Zero, 
      0) - 2; 
  }

}

Затем вы можете использовать IComparer в различных API сортировки:

var names = new [] { "2.log", "10.log", "1.log" };
var sortedNames = names.OrderBy(s => s, new LexicographicalComparer());

Вы также можете использовать StrCmpLogicalW , который используется в проводнике Windows. Он доступен с Windows XP:

[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
static extern Int32 StrCmpLogical(String x, String y);

class LexicographicalComparer : IComparer<String> {

  public Int32 Compare(String x, String y) {
    return StrCmpLogical(x, y);
  }

}

Проще, но у вас меньше контроля над сравнением.

4 голосов
/ 26 августа 2011

Если имена ваших файлов всегда состоят только из цифр, вы можете использовать Path.GetFileNameWithoutExtension () , чтобы отменить расширение файла, и Convert.ToInt32 () (или аналогичное) для преобразованияВаши имена файлов в целых числах для целей сравнения:

var ordered = yourFileNames.OrderBy(
    fn => Convert.ToInt32(Path.GetFileNameWithoutExtension(fn)));

В общем случае, или если вы ищете более «стандартный» способ сделать это, вы можете p / invoke StrCmpLogicalW () , который Explorer использует для сортировки имен файлов в своих представлениях.Тем не менее, выполнение этого заставит вас реализовать IComparer<string>, если вы хотите использовать OrderBy().

3 голосов
/ 26 августа 2011
2 голосов
/ 26 августа 2011

Самым простым (не обязательно самым быстрым / оптимальным) способом было бы ИМХО, чтобы дополнить их левой клавишей до некоторой заранее определенной максимальной длины с нулями.Т.е.

var data = new[] { "1.log", "10.log", "2.log" };
data.OrderBy(x => x.PadLeft(10, '0')).Dump();
2 голосов
/ 26 августа 2011

Вы можете просто удалить все нецифровые символы, разобрать в int и затем отсортировать:

Regex r = new Regex(@"[^\d]");
OrderBy(fn => int.Parse(r.Replace(fn, "")));
0 голосов
/ 26 августа 2011

Вы можете сделать что-то подобное, если убедитесь, что формат ваших имен - NUMBER.VALUE:

var q = strings.Select(s => s.Split(new[] {'.'}, 2))
    .Select(s => new
                        {
                            Number = Convert.ToInt32(s[0]),
                            Name = s[1]
                        })
    .OrderBy(s => s.Number)
    .Select(s => string.Format("{0}.{1}", s.Number, s.Name));
0 голосов
/ 26 августа 2011

нет, я так не думаю - думаю, вы должны написать это сами, если ваши данные - это просто строка.Если вы превратите свои данные во что-то вроде

struct LogDescription
{
   public int LogBase { get; set; }
   public override ToString()
   { return string.Format("{0}.log", LogBase); }
}

, вы можете отсортировать их, используя LogBase-Field

0 голосов
/ 26 августа 2011

Было бы проще, если бы это был лексикографический порядок.

Сравнение строк всегда буквенное.

Как вы хотите справиться с этим, не глядя на все число?

Нет, отдельный компаратор - единственное решение.

...