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