我有一个复杂的排序模式可以复制,我的解决方案似乎有些牵强。 我的输入是一个数字列表,该数字可以有多个字母作为后缀,前缀都只是字母(“ aaa”,“ aab”,“ ac”等)。 我需要按数字排序,然后按后缀(如果有的话)排序,然后按前缀(如果有的话)排序。
例如
"a1a",
"5ac",
"1",
"12",
"2",
"11",
"5aa",
"3",
"5ab",
"a2b",
"abb11ca",
"1b",
"aba11ca"
将被排序为
1
a1a
1b
2
a2b
3
5aa
5ab
5ac
11
aba11ca
abb11ca
12
这是我使用Linq想到的解决方案。
static void Main(string[] args)
{
var arr = new []
{"b2","a1a","5ac","1","12","2","11","5aa","3","5ab","a1","a2b","abb11ca","1b","aba11ca"
};
var ordered = arr.Select(str => {
var parts = SplitIntoPrefixNumberSuffix(str);
var number = int.Parse(parts[1]);
return new { str, parts, number };
})
.OrderBy(x => x.number).ThenBy(x => x.parts[2]).ThenBy(x => x.parts[0])
.Select(x => x.str);
Console.WriteLine("sorted array: ");
foreach (var s in ordered)
{
Console.WriteLine("{0}", s);
}
Console.ReadLine();
}
public static string[] SplitIntoPrefixNumberSuffix(string str)
{
var numChar = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
var numLoc = str.IndexOfAny(numChar);
var nums = "";
foreach (var c in str)
{
if (char.IsDigit(c))
nums = nums + c;
}
Console.WriteLine("numLoc: {0}; nums: {1}", numLoc, nums.Count());
var prefix = str.Substring(0, numLoc);
var suffix = str.Substring(numLoc + nums.Count());
Console.WriteLine("prefix {0}; nums {1}; suffix {2}", prefix, nums, suffix);
return new[] { prefix, nums, suffix };
}
以下是它的.netfiddle文件:https://dotnetfiddle.net/C7ZA0b。
虽然有效,但感觉它不是一个很好的解决方案。我要遍历整个集合几次,我认为我应该使用自定义可比对象。
我以前从未写过可比书;我查看了Dot Net Pearls Alphanumeric Sorting,可以关注它,但对其进行修改不足以满足我的需求。
我可以使用IComparable来完成上述工作吗?在学习如何写作的好地方有什么建议吗?
答案 0 :(得分:3)
因此,您可以使用以正则表达式命名的组来分割字符串的各个组成部分,然后按每个组成部分进行排序:
var regex = new Regex(@"^(?<pre>\D*)(?<num>\d+)(?<suff>\D*)$");
var ordered = data.Select(d => (match: regex.Match(d), value: d))
.Where(x => x.match.Success) //throw away anything that doesn't conform
.Select(x => (
x.value,
pre: x.match.Groups["pre"].Value,
num: int.Parse(x.match.Groups["num"].Value),
suff: x.match.Groups["suff"].Value))
.OrderBy(x => x.num)
.ThenBy(x => x.suff)
.ThenBy(x => x.pre)
.Select(x => x.value);
...但是最终,这与您的解决方案并没有什么不同。我真的看不到专门的IComparer
如何简化这一过程。
如果没有可用的元组(data.Select(d => new { match = regex.Match(d), value = d})
.Where(x => x.match.Success)
.Select(x => new {
x.value,
pre = x.match.Groups["pre"].Value,
num = int.Parse(x.match.Groups["num"].Value),
suff = x.match.Groups["suff"].Value})
.OrderBy(x => x.num)
.ThenBy(x => x.suff)
.ThenBy(x => x.pre)
.Select(x => x.value)
答案 1 :(得分:1)
作为选项,您可以实现自己的自定义比较器
public class CustomStringComparer : IComparer<string>
{
public int Compare(string first, string second)
{
var compareByCore = CompareCore(first, second);
var compareBySuffix = CompareSuffix(first, second);
var compareByPrefix = ComparePrefix(first, second);
return compareByCore != 0 ? compareByCore
: compareBySuffix != 0 ? compareBySuffix
: compareByPrefix;
}
private int CompareCore(string a, string b)
{
var firstCoreNumber = Regex.Match(a, @"\d+").Value;
var secondCoreNumber = Regex.Match(b, @"\d+").Value;
if (!string.IsNullOrEmpty(firstCoreNumber) && !string.IsNullOrEmpty(secondCoreNumber))
{
return int.Parse(firstCoreNumber).CompareTo(int.Parse(secondCoreNumber));
}
return 0;
}
private int CompareSuffix(string a, string b)
{
var firstSuffix = Regex.Match(a, @"\D+$").Value;
var secondSuffix = Regex.Match(b, @"\D+$").Value;
return firstSuffix.CompareTo(secondSuffix);
}
private int ComparePrefix(string a, string b)
{
var firstPrefix = Regex.Match(a, @"^\D+").Value;
var secondPrefix = Regex.Match(b, @"^\D+").Value;
return firstPrefix.CompareTo(secondPrefix);
}
}
当您调用order方法时,只需发送此比较器的一个实例:
var arr = new[]
{ "a1a", "5ac", "1", "12", "2", "11", "5aa", "3", "5ab", "a2b", "abb11ca", "1b", "aba11ca" };
var sortedArr = arr.OrderBy(x => x, new CustomStringComparer());
foreach (var s in sortedArr)
{
Console.Write($"{s} ");
}
答案 2 :(得分:0)
作为我现有答案的替代方法,我不久前写了一个ComparerBuilder<T>
帮助程序,可以编写一些不错的客户端代码。
现在您可以:
var comparer = new ComparerBuilder<string>()
.SortKey(k => int.Parse(Regex.Match(k, @"\d+").Value))
.ThenKey(k => Regex.Match(k, @"\D*$").Value)
.ThenKey(k => Regex.Match(k, @"^\D*").Value)
.Build();
var ordered = data.OrderBy(x => x, comparer);
答案 3 :(得分:-1)
这里是implementation的IComparer
Dave Koelle字母数字。
编辑:添加代码示例。
void Main()
{
var arr = new[] {"b2","a1a","5ac","1","12","2","11","5aa","3","5ab","a1","a2b","abb11ca","1b","aba11ca" };
var items = arr.OrderBy(x => x.ToString(), new AlphanumComparator()).ToList();
Console.WriteLine("sorted array: ");
foreach (var s in items)
{
Console.WriteLine("{0}", s);
}
}
public class AlphanumComparator : IComparer<object>
{
private enum ChunkType { Alphanumeric, Numeric };
private bool InChunk(char ch, char otherCh)
{
ChunkType type = ChunkType.Alphanumeric;
if (char.IsDigit(otherCh))
{
type = ChunkType.Numeric;
}
if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
|| (type == ChunkType.Numeric && !char.IsDigit(ch)))
{
return false;
}
return true;
}
public int Compare(object x, object y)
{
String s1 = x as string;
String s2 = y as string;
if (s1 == null || s2 == null)
{
return 0;
}
int thisMarker = 0, thisNumericChunk = 0;
int thatMarker = 0, thatNumericChunk = 0;
while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
{
if (thisMarker >= s1.Length)
{
return -1;
}
else if (thatMarker >= s2.Length)
{
return 1;
}
char thisCh = s1[thisMarker];
char thatCh = s2[thatMarker];
StringBuilder thisChunk = new StringBuilder();
StringBuilder thatChunk = new StringBuilder();
while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || InChunk(thisCh, thisChunk[0])))
{
thisChunk.Append(thisCh);
thisMarker++;
if (thisMarker < s1.Length)
{
thisCh = s1[thisMarker];
}
}
while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || InChunk(thatCh, thatChunk[0])))
{
thatChunk.Append(thatCh);
thatMarker++;
if (thatMarker < s2.Length)
{
thatCh = s2[thatMarker];
}
}
int result = 0;
// If both chunks contain numeric characters, sort them numerically
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
{
thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
thatNumericChunk = Convert.ToInt32(thatChunk.ToString());
if (thisNumericChunk < thatNumericChunk)
{
result = -1;
}
if (thisNumericChunk > thatNumericChunk)
{
result = 1;
}
}
else
{
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
}
if (result != 0)
{
return result;
}
}
return 0;
}
}