次のようにファイル名を並べ替える必要があります: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エクスプローラーで使用する関数であるStrCmpLogicalWを使用することもできます。WindowsXPから利用可能になっています。
[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)));
一般的な場合、またはこれを行うためのより「標準的な」方法を探している場合は、Explorerがビュー内のファイル名を並べ替えるために使用するStrCmpLogicalW()をp/invokeできます。ただし、これを行うと、を使用する場合はを実装するIComparer<string>
必要がありますOrderBy()
。
これらのいずれかを取る必要があります
数字以外の文字をすべて削除し、intに解析してから、次のように並べ替えることができます。
Regex r = new Regex(@"[^\d]");
OrderBy(fn => int.Parse(r.Replace(fn, "")));
最も簡単な(必ずしも最速/最適ではない)方法は、IMHOがそれらすべてを、ゼロを使用して事前定義された最大長に左パディングすることです。つまり
var data = new[] { "1.log", "10.log", "2.log" };
data.OrderBy(x => x.PadLeft(10, '0')).Dump();
辞書式順序であれば、より簡単になります。
文字列の比較は常に文字ごとに行われます。
整数を見ずにどう対処したいですか?
いいえ、別の比較者が唯一の解決策です。
いいえ、そうは思いません。データが単なる文字列である限り、自分で作成する必要があると思います。データを次のようなものにすると
struct LogDescription
{
public int LogBase { get; set; }
public override ToString()
{ return string.Format("{0}.log", LogBase); }
}
LogBase-Fieldを使用して並べ替えることができます
名前の形式が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));