我有一个字符串列表,可以包含一个字母或一个int的字符串表示(最多2位数)。 它们需要按字母顺序排序,或者(当它实际上是一个int时)对它所代表的数值进行排序。
示例:
IList<string> input = new List<string>()
{"a", 1.ToString(), 2.ToString(), "b", 10.ToString()};
input.OrderBy(s=>s)
// 1
// 10
// 2
// a
// b
我想要的是
// 1
// 2
// 10
// a
// b
我有一些想法涉及通过尝试解析它来格式化它,然后如果它是一个成功的tryparse用我自己的自定义stringformatter格式化它使它有前面的零。我希望能有更简单,更高效的东西。
修改
我最终制作了一个IComparer,我把它放在我的Utils库中供以后使用
当我在场时,我也把混合物扔进了混合物中。
public class MixedNumbersAndStringsComparer : IComparer<string> {
public int Compare(string x, string y) {
double xVal, yVal;
if(double.TryParse(x, out xVal) && double.TryParse(y, out yVal))
return xVal.CompareTo(yVal);
else
return string.Compare(x, y);
}
}
//Tested on int vs int, double vs double, int vs double, string vs int, string vs doubl, string vs string.
//Not gonna put those here
[TestMethod]
public void RealWorldTest()
{
List<string> input = new List<string>() { "a", "1", "2,0", "b", "10" };
List<string> expected = new List<string>() { "1", "2,0", "10", "a", "b" };
input.Sort(new MixedNumbersAndStringsComparer());
CollectionAssert.AreEquivalent(expected, input);
}
答案 0 :(得分:20)
我想到了两种方式,不确定哪种方式更具性能。实现自定义IComparer:
class MyComparer : IComparer<string>
{
public int Compare(string x, string y)
{
int xVal, yVal;
var xIsVal = int.TryParse( x, out xVal );
var yIsVal = int.TryParse( y, out yVal );
if (xIsVal && yIsVal) // both are numbers...
return xVal.CompareTo(yVal);
if (!xIsVal && !yIsVal) // both are strings...
return x.CompareTo(y);
if (xIsVal) // x is a number, sort first
return -1;
return 1; // x is a string, sort last
}
}
var input = new[] {"a", "1", "10", "b", "2", "c"};
var e = input.OrderBy( s => s, new MyComparer() );
或者,将序列拆分为数字和非数字,然后对每个子组进行排序,最后加入排序结果;类似的东西:
var input = new[] {"a", "1", "10", "b", "2", "c"};
var result = input.Where( s => s.All( x => char.IsDigit( x ) ) )
.OrderBy( r => { int z; int.TryParse( r, out z ); return z; } )
.Union( input.Where( m => m.Any( x => !char.IsDigit( x ) ) )
.OrderBy( q => q ) );
答案 1 :(得分:12)
也许您可以使用更通用的方法并使用natural sorting算法,例如C#实现here。
答案 2 :(得分:3)
使用带有OrderBy
参数的IComparer
的其他重载。
然后,您可以实施自己使用IComparer
的{{1}}来判断它是否为数字。
答案 3 :(得分:2)
我会说你可以使用RegularExpression拆分值(假设一切都是int),然后将它们重新加入。
//create two lists to start
string[] data = //whatever...
List<int> numbers = new List<int>();
List<string> words = new List<string>();
//check each value
foreach (string item in data) {
if (Regex.IsMatch("^\d+$", item)) {
numbers.Add(int.Parse(item));
}
else {
words.Add(item);
}
}
然后使用您的两个列表,您可以对它们进行排序,然后以您想要的任何格式将它们合并在一起。
答案 4 :(得分:2)
你可以使用函数provided by the Win32 API:
[DllImport ("shlwapi.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
static extern int StrCmpLogicalW (String x, String y);
并像其他人一样从IComparer
拨打电话。
答案 5 :(得分:1)
public static int? TryParse(string s)
{
int i;
return int.TryParse(s, out i) ? (int?)i : null;
}
// in your method
IEnumerable<string> input = new string[] {"a", "1","2", "b", "10"};
var list = input.Select(s => new { IntVal = TryParse(s), String =s}).ToList();
list.Sort((s1, s2) => {
if(s1.IntVal == null && s2.IntVal == null)
{
return s1.String.CompareTo(s2.String);
}
if(s1.IntVal == null)
{
return 1;
}
if(s2.IntVal == null)
{
return -1;
}
return s1.IntVal.Value.CompareTo(s2.IntVal.Value);
});
input = list.Select(s => s.String);
foreach(var x in input)
{
Console.WriteLine(x);
}
它仍然进行转换,但只进行一次/项目。
答案 6 :(得分:1)
您可以使用自定义比较器 - 订购声明将是:
var result = input.OrderBy(s => s, new MyComparer());
其中MyComparer的定义如下:
public class MyComparer : Comparer<string>
{
public override int Compare(string x, string y)
{
int xNumber;
int yNumber;
var xIsNumber = int.TryParse(x, out xNumber);
var yIsNumber = int.TryParse(y, out yNumber);
if (xIsNumber && yIsNumber)
{
return xNumber.CompareTo(yNumber);
}
if (xIsNumber)
{
return -1;
}
if (yIsNumber)
{
return 1;
}
return x.CompareTo(y);
}
}
虽然这看起来有点冗长,但它将排序逻辑封装成适当的类型。如果您愿意,您可以轻松地使Comparer进行自动化测试(单元测试)。它也是可重复使用的。
(有可能使算法更清晰一点,但这是我能迅速拼凑起来的最佳方法。)
答案 7 :(得分:1)
你也可以在某种意义上“欺骗”。根据您对问题的描述,您知道任何长度为2的字符串都是数字。所以只需对长度为1的所有字符串进行排序。然后对长度为2的所有字符串进行排序。然后进行一堆交换,以正确的顺序重新排序字符串。本质上,该过程将按如下方式工作:(假设您的数据位于数组中。)
步骤1:将长度为2的所有字符串推送到数组的末尾。跟踪你有多少。
步骤2:在适当的位置对长度为1的字符串和长度为2的字符串进行排序。
第3步:二元搜索“a”,它将位于你们两半的边界上。
步骤4:根据需要用字母交换两位数的字符串。
尽管如此,虽然这种方法可行,但不涉及正则表达式,并且不会尝试将非int值解析为int - 我不推荐它。您将编写比已建议的其他方法更多的代码。它模糊了你想要做的事情的重点。如果你突然得到两个字母的字符串或三个数字字符串,它不起作用。等等。我只是把它包括在内,以展示你如何以不同的方式看待问题,并提出替代解决方案。
答案 8 :(得分:1)
使用Schwartzian Transform执行O(n)转换!
private class Normalized : IComparable<Normalized> {
private readonly string str;
private readonly int val;
public Normalized(string s) {
str = s;
val = 0;
foreach (char c in s) {
val *= 10;
if (c >= '0' && c <= '9')
val += c - '0';
else
val += 100 + c;
}
}
public String Value { get { return str; } }
public int CompareTo(Normalized n) { return val.CompareTo(n.val); }
};
private static Normalized In(string s) { return new Normalized(s); }
private static String Out(Normalized n) { return n.Value; }
public static IList<String> MixedSort(List<String> l) {
var tmp = l.ConvertAll(new Converter<String,Normalized>(In));
tmp.Sort();
return tmp.ConvertAll(new Converter<Normalized,String>(Out));
}
答案 9 :(得分:1)
我遇到了类似的问题,并在此处着陆:排序具有数字后缀的字符串,如下例所示。
原文:
"Test2", "Test1", "Test10", "Test3", "Test20"
默认排序结果:
"Test1", "Test10", "Test2", "Test20", "Test3"
所需的排序结果:
"Test1", "Test2", "Test3, "Test10", "Test20"
我最终使用了自定义Comparer:
public class NaturalComparer : IComparer
{
public NaturalComparer()
{
_regex = new Regex("\\d+$", RegexOptions.IgnoreCase);
}
private Regex _regex;
private string matchEvaluator(System.Text.RegularExpressions.Match m)
{
return Convert.ToInt32(m.Value).ToString("D10");
}
public int Compare(object x, object y)
{
x = _regex.Replace(x.ToString, matchEvaluator);
y = _regex.Replace(y.ToString, matchEvaluator);
return x.CompareTo(y);
}
}
HTH; o)