我需要对文件名进行如下排序:1.log、2.log、10.log
但是当我使用 OrderBy(fn => fn) 时,它会将它们排序为:1.log、10.log、2.log
我显然知道这可以通过编写另一个比较器来完成,但是有没有更简单的方法可以从字典顺序更改为自然排序顺序?
编辑:目标是获得与在 Windows 资源管理器中选择“按名称排序”时相同的排序。
我需要对文件名进行如下排序:1.log、2.log、10.log
但是当我使用 OrderBy(fn => fn) 时,它会将它们排序为:1.log、10.log、2.log
我显然知道这可以通过编写另一个比较器来完成,但是有没有更简单的方法可以从字典顺序更改为自然排序顺序?
编辑:目标是获得与在 Windows 资源管理器中选择“按名称排序”时相同的排序。
您可以使用 Win32CompareStringEx
功能。在 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());
您还可以使用Windows Explorer 使用的函数StrCmpLogicalW 。它从 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);
}
}
更简单,但您对比较的控制较少。
如果您的文件名始终只包含数字,您可以使用Path.GetFileNameWithoutExtension()丢弃文件扩展名和Convert.ToInt32()(或类似方法)将文件名转换为整数以进行比较:
var ordered = yourFileNames.OrderBy(
fn => Convert.ToInt32(Path.GetFileNameWithoutExtension(fn)));
在一般情况下,或者如果您正在寻找一种更“标准”的方式来执行此操作,您可以 p/invoke StrCmpLogicalW(),Explorer 使用它在其视图中对文件名进行排序。但是,IComparer<string>
如果您想使用OrderBy()
.
你应该采取其中之一
最简单(不一定是最快/最佳)的方法是恕我直言,将它们全部左填充到某个预定义的最大长度,并用零。IE
var data = new[] { "1.log", "10.log", "2.log" };
data.OrderBy(x => x.PadLeft(10, '0')).Dump();
您可以删除所有非数字字符,解析为 int 然后排序:
Regex r = new Regex(@"[^\d]");
OrderBy(fn => int.Parse(r.Replace(fn, "")));
当您可以确保您的姓名格式为 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));
如果它是一个字典顺序会更容易,。
字符串比较总是一个字母一个字母。
您想如何在不查看整数的情况下处理它?
不,单独的比较器是唯一的解决方案。
不,我不这么认为——我猜你必须自己写,只要你的数据只是一个字符串。如果你把你的数据变成类似的东西
struct LogDescription
{
public int LogBase { get; set; }
public override ToString()
{ return string.Format("{0}.log", LogBase); }
}
您可以使用 LogBase-Field 进行排序