C# 排序混合数字和字符串

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1032775/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 06:35:50  来源:igfitidea点击:

Sorting mixed numbers and strings

c#sortingformattingtostring

提问by Boris Callens

I have a list of strings that can contain a letter or a string representation of an int (max 2 digits). They need to be sorted either alphabetically or (when it is actually an int) on the numerical value it represents.

我有一个字符串列表,其中可以包含一个字母或一个 int 的字符串表示形式(最多 2 位数字)。它们需要按字母顺序或(当它实际上是一个 int)按它所代表的数值排序。

Example:

例子:

IList<string> input = new List<string>()
    {"a", 1.ToString(), 2.ToString(), "b", 10.ToString()};

input.OrderBy(s=>s)
  // 1
  // 10
  // 2
  // a
  // b

What I would want is

我想要的是

  // 1
  // 2
  // 10
  // a
  // b

I have some idea involving formatting it with trying to parse it, then if it is a successfull tryparse to format it with my own custom stringformatter to make it have preceding zeros. I'm hoping for something more simple and performant.

我有一些想法涉及通过尝试解析它来格式化它,然后如果它成功尝试使用我自己的自定义 stringformatter 对其进行格式化以使其具有前面的零。我希望有更简单、更高效的东西。

Edit
I ended up making an IComparer I dumped in my Utils library for later use.
While I was at it I threw doubles in the mix too.

编辑
我最终制作了一个 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);
}

采纳答案by Nathan Baulch

Perhaps you could go with a more generic approach and use a natural sortingalgorithm such as the C# implementation here.

也许您可以采用更通用的方法并使用自然排序算法,例如此处的 C# 实现。

回答by Hugoware

I'd say you could split up the values using a RegularExpression (assuming everything is an int) and then rejoin them together.

我想说您可以使用正则表达式(假设所有内容都是整数)拆分值,然后将它们重新连接在一起。

//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);
    }
}

Then with your two lists you can sort each of them and then merge them back together in whatever format you want.

然后使用您的两个列表,您可以对它们中的每一个进行排序,然后以您想要的任何格式将它们合并在一起。

回答by Christian Hayter

Use the other overload of OrderBythat takes an IComparerparameter.

使用另一个OrderByIComparer参数的重载。

You can then implement your own IComparerthat uses int.TryParseto tell if it's a number or not.

然后你可以实现你自己的IComparerint.TryParse用来判断它是否是一个数字。

回答by Jonathan Rupp

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);
}

It still does the conversion, but only once/item.

它仍然进行转换,但只有一次/项目。

回答by LBushkin

Two ways come to mind, not sure which is more performant. Implement a custom IComparer:

想到了两种方法,不确定哪个更高效。实现自定义 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() );

Or, split the sequence into numbers and non-numbers, then sort each subgroup, finally join the sorted results; something like:

或者,将序列拆分为数字和非数字,然后对每个子组进行排序,最后加入排序后的结果;就像是:

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 ) );

回答by Mark Seemann

You could use a custom comparer - the ordering statement would then be:

您可以使用自定义比较器 - 排序语句将是:

var result = input.OrderBy(s => s, new MyComparer());

where MyComparer is defined like this:

其中 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);
    }
}

Although this may seem a bit verbose, it encapsulates the sorting logic into a proper type. You can then, if you wish, easily subject the Comparer to automated testing (unit testing). It is also reusable.

虽然这看起来有点冗长,但它把排序逻辑封装成一个合适的类型。然后,如果您愿意,您可以轻松地对比较器进行自动化测试(单元测试)。它也是可重复使用的。

(It may be possible to make the algorithm a bit clearer, but this was the best I could quickly throw together.)

(也许可以让算法更清晰一点,但这是我能快速拼凑起来的最好的方法。)

回答by Rob Rolnick

You could also "cheat" in some sense. Based on your description of the problem, You know any String of length 2 will be a number. So just sort all the Strings of length 1. And then sort all the Strings of length 2. And then do a bunch of swapping to re-order your Strings in the correct order. Essentially the process will work as follows: (assuming your data is in an array.)

在某种意义上,你也可以“作弊”。根据您对问题的描述,您知道任何长度为 2 的字符串都是一个数字。所以只需对所有长度为 1 的字符串进行排序。然后对所有长度为 2 的字符串进行排序。然后进行一堆交换以按正确的顺序重新排序您的字符串。本质上,该过程将按如下方式工作:(假设您的数据在数组中。)

Step 1: Push all Strings of length 2 to the end of the array. Keeping track of how many you have.

步骤 1:将所有长度为 2 的字符串推送到数组的末尾。跟踪你有多少。

Step 2: In place sort the Strings of length 1 and Strings of length 2.

步骤 2:就地排序长度为 1 的字符串和长度为 2 的字符串。

Step 3: Binary search for 'a' which would be on the boundary of your two halves.

第 3 步:二分搜索“a”,它位于两半的边界上。

Step 4: Swap your two digit Strings with the letters as necessary.

第 4 步:根据需要将两位数的字符串与字母交换。

That said, while this approach will work, does not involve regular expressions, and does not attempt to parse non-int values as an int -- I don't recommend it. You'll be writing significantly more code than other approaches already suggested. It obfuscates the point of what you are trying to do. It doesn't work if you suddenly get two letter Strings or three digit Strings. Etc. I'm just including it to show how you can look at problems differently, and come up with alternative solutions.

也就是说,虽然这种方法可行,但不涉及正则表达式,并且不会尝试将非 int 值解析为 int —— 我不推荐它。您将编写比已经建议的其他方法多得多的代码。它混淆了你想要做的事情的重点。如果你突然得到两个字母的字符串或三位数的字符串,它就不起作用。等等。我只是将它包含在内,以展示您如何以不同的方式看待问题,并提出替代解决方案。

回答by Skizz

You could just use function provided by the Win32 API:

您可以只使用Win32 API 提供的函数

[DllImport ("shlwapi.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
static extern int StrCmpLogicalW (String x, String y);

and call it from an ICompareras others have shown.

IComparer像其他人所展示的那样从 an 调用它。

回答by Greg Bacon

Use a Schwartzian Transformto perform O(n) conversions!

使用Schwartzian 变换来执行 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));
}

回答by mike

I had a similar problem and landed here: sorting strings that have a numeric suffix as in the following example.

我遇到了类似的问题并在这里登陆:对具有数字后缀的字符串进行排序,如下例所示。

Original:

原来的:

"Test2", "Test1", "Test10", "Test3", "Test20"

Default sort result:

默认排序结果:

"Test1", "Test10", "Test2", "Test20", "Test3"

Desired sort result:

期望的排序结果:

"Test1", "Test2", "Test3, "Test10", "Test20"

I ended up using a custom 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)

HTH ;o)