.NET 中对以 1、10 和 2 开头的字符串进行排序并遵循数字顺序的最短方法是什么?

发布于 2024-12-01 16:55:45 字数 239 浏览 0 评论 0原文

我需要按如下方式对文件名进行排序:1.log、2.log、10.log

但是当我使用 OrderBy(fn => fn) 时,它将对它们进行排序: 1.log, 10.log, 2.log

我显然知道这可以通过编写另一个比较器来完成,但是有没有更简单的方法可以从字典顺序更改为自然排序顺序?

编辑:目标是获得与在 Windows 资源管理器中选择“按名称排序”时相同的顺序。

I need to sort file names as follows: 1.log, 2.log, 10.log

But when I use OrderBy(fn => fn) it will sort them as:
1.log, 10.log, 2.log

I obviously know that this could be done by writing another comparer, but is there a simpler way to change from lexicographical order to natural sort order?

Edit: the objective is to obtain the same ordering as when selecting "order by name" in Windows Explorer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

蓬勃野心 2024-12-08 16:55:45

您可以使用 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
);

然后您可以创建一个使用 SORT_DIGITSASNUMBERS 标志的 IComparer

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

}

更简单,但您对比较的控制权较少。

You can use the Win32 CompareStringEx function. On Windows 7 it supports the sorting you need.
You will have use 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
);

You can then create an IComparer that uses the SORT_DIGITSASNUMBERS flag:

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

}

You can then use the IComparer in various sorting API's:

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

You can also use StrCmpLogicalW which is the function used by Windows Explorer. It has been available since 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);
  }

}

Simpler, but you have less control over the comparison.

归属感 2024-12-08 16:55:45

如果您的文件名始终仅包含数字,则可以使用 Path.GetFileNameWithoutExtension() 放弃文件扩展名和 Convert.ToInt32() (或类似)将文件名转换为整数以进行比较:

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

在一般情况下,或者如果您正在寻找更“标准”的方法为此,您可以 p/invoke StrCmpLogicalW(),资源管理器用于在其视图中对文件名进行排序。但是,如果您想使用 OrderBy(),这样做将强制您实现 IComparer

If your file names always only consist in digits, you can use Path.GetFileNameWithoutExtension() to discard the file extension and Convert.ToInt32() (or similar) to convert your file names to integers for comparison purposes:

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

In the general case, or if you're looking for a more "standard" way to do this, you can p/invoke StrCmpLogicalW(), which Explorer uses to sort file names in its views. However, doing that will force you to implement an IComparer<string> if you want to use OrderBy().

傾旎 2024-12-08 16:55:45

最简单(不一定是最快/最佳)的方法是恕我直言,将它们全部左填充到某个预定义的最大长度,并用零填充。 IE

var data = new[] { "1.log", "10.log", "2.log" };
data.OrderBy(x => x.PadLeft(10, '0')).Dump();

The simplest (not necessarily fastest/optimal) way would be IMHO to left-pad them all to some predefined maximum length with zeroes. I.e.

var data = new[] { "1.log", "10.log", "2.log" };
data.OrderBy(x => x.PadLeft(10, '0')).Dump();
倾`听者〃 2024-12-08 16:55:45

您可以删除所有非数字字符,解析为 int,然后排序:

Regex r = new Regex(@"[^\d]");
OrderBy(fn => int.Parse(r.Replace(fn, "")));

You could just remove all the non digit characters, parse to int and then sort:

Regex r = new Regex(@"[^\d]");
OrderBy(fn => int.Parse(r.Replace(fn, "")));
温柔女人霸气范 2024-12-08 16:55:45

当您可以确保名称的格式为 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));

You can do something like this when you can assure the format of your names are 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));
纵性 2024-12-08 16:55:45

不,我不这么认为 - 我想你必须自己编写它,只要你的数据只是一个字符串。
如果您将数据设置为

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

可以使用 LogBase-Field 进行排序的内容

no I don't think so - I guess you have to write it yourself as long as your data is just a string.
If you make your data into something like

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

you can sort by using the LogBase-Field

命硬 2024-12-08 16:55:45

如果是按字典顺序排列就更容易了。

字符串比较始终是逐个字母的。

在不查看整个数字的情况下,您想如何处理这个问题?

不,单独的比较器是唯一的解决方案。

It would be easier if it would be a lexicographical order,.

String comparison is always letter by letter.

How you want to deal with that without looking at the whole number?

No, a separate comparer is the only solution.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文