我有一个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
..
答案 0 :(得分:43)
这是因为字符串的默认排序是标准字母数字字典(词典)排序,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
填充仅发生在用于比较的密钥上。原始字符串(没有填充)将保留在结果中。
请注意,此方法假设输入中的数字的最大位数。
答案 1 :(得分:9)
可以在Dave Koelle的网站上找到“正常工作”的字母数字排序方法的正确实现。 C# version is here。{{3}}。
答案 2 :(得分:5)
如果你想使用LINQ和一个自定义比较器(如Dave Koelle之类的自定义比较器)按特定属性对对象列表进行排序,你可以这样做:
...
items = items.OrderBy(x => x.property, new AlphanumComparator()).ToList();
...
你还必须改变Dave的类来继承System.Collections.Generic.IComparer<object>
而不是基本的IComparer
,这样类签名就变成了:
...
public class AlphanumComparator : System.Collections.Generic.IComparer<object>
{
...
就个人而言,我更喜欢James McCormack的实现,因为它实现了IDisposable,尽管我的基准测试表明它稍慢。
答案 3 :(得分:3)
您可以 PInvoke 到StrCmpLogicalW
(Windows函数)来执行此操作。见这里:Natural Sort Order in C#
答案 4 :(得分:3)
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());
答案 5 :(得分:3)
您可以使用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
一样使用它。
答案 6 :(得分:1)
看起来好像是在进行字典排序,而不管是小型还是大写字母。
您可以尝试在该lambda中使用一些自定义表达式来执行此操作。
答案 7 :(得分:1)
在.NET中没有自然的方法,but have a look at this blog post on natural sorting
您可以将其放入扩展方法并使用它而不是OrderBy
答案 8 :(得分:0)
由于开头的字符数是可变的,因此使用正则表达式会有所帮助:
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;
});
答案 9 :(得分:-2)
我不知道如何在LINQ中这样做,但也许你喜欢这样:
Array.Sort(partNumbers, new AlphanumComparatorFast());
//显示结果
foreach (string h in partNumbers )
{
Console.WriteLine(h);
}