string[]
では、すべての要素が数値で終わります。
string[] partNumbers = new string[]
{
"ABC10", "ABC1","ABC2", "ABC11","ABC10", "AB1", "AB2", "Ab11"
};
上記の配列をLINQ
を使用して次のようにソートしようとしていますが、期待した結果が得られません。
var result = partNumbers.OrderBy(x => x);
実結果:
AB1
Ab11
AB2
ABC1
ABC10
ABC10
ABC11
ABC2
期待される結果
AB1
AB2
AB11
..
これは、文字列のデフォルトの順序が標準の英数字辞書(辞書式)の順序であり、順序が常に左から右に進むため、ABC11がABC2の前に来るためです。
必要なものを取得するには、次のように、order by句の数値部分を埋め込む必要があります。
var result = partNumbers.OrderBy(x => PadNumbers(x));
ここで、PadNumbers
は次のように定義できます。
public static string PadNumbers(string input)
{
return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, '0'));
}
これは、OrderBy
が次のように見えるように、入力文字列に現れる任意の数値(または複数の数値)のゼロを埋めます。
ABC0000000010
ABC0000000001
...
AB0000000011
パディングは、比較に使用されるキーでのみ発生します。元の文字列(パディングなし)は結果に保持されます。
このアプローチでは、入力の数値の最大桁数を想定していることに注意してください。
Dave Koelle のサイトで、「正しく動作する」英数字の並べ替えメソッドの適切な実装を見つけることができます。 C#バージョンはこちら 。
LINQおよび Dave Koelle のようなカスタムコンパレーターを使用して、特定のプロパティでオブジェクトのリストを並べ替える場合は、次のようにします。
...
items = items.OrderBy(x => x.property, new AlphanumComparator()).ToList();
...
また、基本的なIComparer
ではなくSystem.Collections.Generic.IComparer<object>
から継承するようにDaveのクラスを変更する必要があるため、クラスの署名は次のようになります。
...
public class AlphanumComparator : System.Collections.Generic.IComparer<object>
{
...
個人的に、私は James McCormack による実装を好みます。これは、IDisposableを実装するためです。ただし、私のベンチマークでは、少し遅いことが示されています。
PInvokeを使用すると、高速で優れた結果を得ることができます。
class AlphanumericComparer : IComparer<string>
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
static extern int StrCmpLogicalW(string s1, string s2);
public int Compare(string x, string y) => StrCmpLogicalW(x, y);
}
上記の答えからAlphanumComparatorFast
のように使用できます。
これを行うには、PInvokeをStrCmpLogicalW
(windows関数)に指定できます。ここを参照してください: C#の自然ソート順
public class AlphanumComparatorFast : IComparer
{
List<string> GetList(string s1)
{
List<string> SB1 = new List<string>();
string st1, st2, st3;
st1 = "";
bool flag = char.IsDigit(s1[0]);
foreach (char c in s1)
{
if (flag != char.IsDigit(c) || c=='\'')
{
if(st1!="")
SB1.Add(st1);
st1 = "";
flag = char.IsDigit(c);
}
if (char.IsDigit(c))
{
st1 += c;
}
if (char.IsLetter(c))
{
st1 += c;
}
}
SB1.Add(st1);
return SB1;
}
public int Compare(object x, object y)
{
string s1 = x as string;
if (s1 == null)
{
return 0;
}
string s2 = y as string;
if (s2 == null)
{
return 0;
}
if (s1 == s2)
{
return 0;
}
int len1 = s1.Length;
int len2 = s2.Length;
int marker1 = 0;
int marker2 = 0;
// Walk through two the strings with two markers.
List<string> str1 = GetList(s1);
List<string> str2 = GetList(s2);
while (str1.Count != str2.Count)
{
if (str1.Count < str2.Count)
{
str1.Add("");
}
else
{
str2.Add("");
}
}
int x1 = 0; int res = 0; int x2 = 0; string y2 = "";
bool status = false;
string y1 = ""; bool s1Status = false; bool s2Status = false;
//s1status ==false then string ele int;
//s2status ==false then string ele int;
int result = 0;
for (int i = 0; i < str1.Count && i < str2.Count; i++)
{
status = int.TryParse(str1[i].ToString(), out res);
if (res == 0)
{
y1 = str1[i].ToString();
s1Status = false;
}
else
{
x1 = Convert.ToInt32(str1[i].ToString());
s1Status = true;
}
status = int.TryParse(str2[i].ToString(), out res);
if (res == 0)
{
y2 = str2[i].ToString();
s2Status = false;
}
else
{
x2 = Convert.ToInt32(str2[i].ToString());
s2Status = true;
}
//checking --the data comparision
if(!s2Status && !s1Status ) //both are strings
{
result = str1[i].CompareTo(str2[i]);
}
else if (s2Status && s1Status) //both are intergers
{
if (x1 == x2)
{
if (str1[i].ToString().Length < str2[i].ToString().Length)
{
result = 1;
}
else if (str1[i].ToString().Length > str2[i].ToString().Length)
result = -1;
else
result = 0;
}
else
{
int st1ZeroCount=str1[i].ToString().Trim().Length- str1[i].ToString().TrimStart(new char[]{'0'}).Length;
int st2ZeroCount = str2[i].ToString().Trim().Length - str2[i].ToString().TrimStart(new char[] { '0' }).Length;
if (st1ZeroCount > st2ZeroCount)
result = -1;
else if (st1ZeroCount < st2ZeroCount)
result = 1;
else
result = x1.CompareTo(x2);
}
}
else
{
result = str1[i].CompareTo(str2[i]);
}
if (result == 0)
{
continue;
}
else
break;
}
return result;
}
}
このクラスの使用法:
List<string> marks = new List<string>();
marks.Add("M'00Z1");
marks.Add("M'0A27");
marks.Add("M'00Z0");
marks.Add("0000A27");
marks.Add("100Z0");
string[] Markings = marks.ToArray();
Array.Sort(Markings, new AlphanumComparatorFast());
まあそれは、小さな文字や大文字の文字に関係なく、辞書式順序付けをしているように見えます。
そのラムダでカスタム式を使用してそれを試すことができます。
これを.NETで行う自然な方法はありません ただし、この自然な並べ替えに関するブログの投稿をご覧ください
これを拡張メソッドに入れて、OrderByの代わりに使用できます。
先頭の文字数は可変であるため、正規表現が役立ちます。
var re = new Regex(@"\d+$"); // finds the consecutive digits at the end of the string
var result = partNumbers.OrderBy(x => int.Parse(re.Match(x).Value));
固定数のプレフィックス文字がある場合、Substring
メソッドを使用して、関連する文字から開始して抽出できます。
// parses the string as a number starting from the 5th character
var result = partNumbers.OrderBy(x => int.Parse(x.Substring(4)));
数値に小数点記号または桁区切り記号が含まれている可能性がある場合は、正規表現でそれらの文字も許可する必要があります。
var re = new Regex(@"[\d,]*\.?\d+$");
var result = partNumbers.OrderBy(x => double.Parse(x.Substring(4)));
正規表現またはSubstring
によって返された文字列がint.Parse
/double.Parse
で解析できない場合は、関連するTryParse
バリアントを使用します。
var re = new Regex(@"\d+$"); // finds the consecutive digits at the end of the string
var result = partNumbers.OrderBy(x => {
int? parsed = null;
if (int.TryParse(re.Match(x).Value, out var temp)) {
parsed = temp;
}
return parsed;
});
一般的なアプローチを好む人のために、AlphanumComparatorをDave Koelleに調整します: AlphanumComparator を少し。
ステップ1(クラスの名前を非省略形に変更し、TCompareTypeジェネリック型引数を取ります):
public class AlphanumericComparator<TCompareType> : IComparer<TCompareType>
次の調整は、次の名前空間をインポートすることです。
using System.Collections.Generic;
そして、CompareメソッドのシグネチャをオブジェクトからTCompareTypeに変更します。
public int Compare(TCompareType x, TCompareType y)
{ .... no further modifications
これで、AlphanumericComparatorの正しいタイプを指定できます。 (実際にはAlphanumericComparerと呼ばれるべきだと思います)、それを使うとき。
私のコードの使用例:
if (result.SearchResults.Any()) {
result.SearchResults = result.SearchResults.OrderBy(item => item.Code, new AlphanumericComparator<string>()).ToList();
}
これで、一般的な引数を受け入れ、さまざまな型で使用できる英数字コンパレータ(コンパレータ)ができました。
そして、これがコンパレータを使用するための拡張メソッドです:
/// <summary>
/// Returns an ordered collection by key selector (property expression) using alpha numeric comparer
/// </summary>
/// <typeparam name="T">The item type in the ienumerable</typeparam>
/// <typeparam name="TKey">The type of the key selector (property to order by)</typeparam>
/// <param name="coll">The source ienumerable</param>
/// <param name="keySelector">The key selector, use a member expression in lambda expression</param>
/// <returns></returns>
public static IEnumerable<T> OrderByMember<T, TKey>(this IEnumerable<T> coll, Func<T, TKey> keySelector)
{
var result = coll.OrderBy(keySelector, new AlphanumericComparer<TKey>());
return result;
}