当字符串为数字时,如何在按值排序时按字母顺序对字符串进行排序?


100

我正在尝试对一组数字进行排序,这些数字是字符串,我希望它们能按数字排序。

问题是我无法将数字转换为int

这是代码:

string[] things= new string[] { "105", "101", "102", "103", "90" };

foreach (var thing in things.OrderBy(x => x))
{
    Console.WriteLine(thing);
}

输出:101、102、103、105、90

我想要:90、101、102、103、105

编辑:输出不能为090、101、102 ...

更新了代码示例,使其说“东西”而不是“大小”。该数组可以是这样的:

string[] things= new string[] { "paul", "bob", "lauren", "007", "90" };

这意味着它需要按字母顺序和数字排序:

007、90,鲍勃,劳伦,保罗


8
为什么不能将它们转换为int?
Femaref 2011年

1
“大小”可以是“名称”之类的东西。代码示例只是简化了。
SF

2
这些数字是否为负?它们都是整数吗?整数的范围是多少?
埃里克·利珀特

“事物”可以是任何类型的字符串。我希望该列表按逻辑排序给不懂计算机的人。负数应在正数之前。就字符串长度而言,不会超过100个字符。
SF

5
您想走多远?应该image10之后image2吗?应该先JanuaryFebruary吗?
svick 2011年

Answers:


104

将自定义比较器传递给OrderBy。Enumerable.OrderBy将让您指定所需的任何比较器。

这是一种方法:

void Main()
{
    string[] things = new string[] { "paul", "bob", "lauren", "007", "90", "101"};

    foreach (var thing in things.OrderBy(x => x, new SemiNumericComparer()))
    {    
        Console.WriteLine(thing);
    }
}


public class SemiNumericComparer: IComparer<string>
{
    /// <summary>
    /// Method to determine if a string is a number
    /// </summary>
    /// <param name="value">String to test</param>
    /// <returns>True if numeric</returns>
    public static bool IsNumeric(string value)
    {
        return int.TryParse(value, out _);
    }

    /// <inheritdoc />
    public int Compare(string s1, string s2)
    {
        const int S1GreaterThanS2 = 1;
        const int S2GreaterThanS1 = -1;

        var IsNumeric1 = IsNumeric(s1);
        var IsNumeric2 = IsNumeric(s2);

        if (IsNumeric1 && IsNumeric2)
        {
            var i1 = Convert.ToInt32(s1);
            var i2 = Convert.ToInt32(s2);

            if (i1 > i2)
            {
                return S1GreaterThanS2;
            }

            if (i1 < i2)
            {
                return S2GreaterThanS1;
            }

            return 0;
        }

        if (IsNumeric1)
        {
            return S2GreaterThanS1;
        }

        if (IsNumeric2)
        {
            return S1GreaterThanS2;
        }

        return string.Compare(s1, s2, true, CultureInfo.InvariantCulture);
    }
}

1
对于给定的输入,这将产生与Recursive答案相同的结果,其中涉及PadLeft()。我假设您的输入实际上比此示例显示的要复杂,在这种情况下,使用自定义比较器是可行的。
Jeff Paulsen

干杯。该解决方案有效,并且似乎是一种易于阅读和清洁的实现方式。+1告诉我,您可以在OrderBy上使用IComparer :)
sf。

17
IsNumeric方法不好,由异常驱动的编码总是不好。使用int.TryParse代替。尝试使用大量清单的代码,这将永远永久。
Nean Der Thal 2015年

如果有帮助,我在此处对此版本添加了扩展名,从而增加了对单词排序的支持。对于我的需求,在空格上分割就足够了,我几乎不需要担心混合使用的单词(例如test12 vs test3),
matt.bungard

@NeanDerThal我很确定,如果要调试或正在访问Exception对象,则只能在循环中缓慢/不好地处理许多异常。
凯利·埃尔顿

90

只需将零填充到相同的长度即可:

int maxlen = sizes.Max(x => x.Length);
var result = sizes.OrderBy(x => x.PadLeft(maxlen, '0'));

+1为简单的解决方案,挑剔(已经完成编辑,很好)
MarinoŠimić2011年

好主意,但下一个要注意的是,我需要显示这些值,因此“ 90”必须是“ 90”,而不是“ 090”
SF。

6
@sf:尝试一下,您可能会喜欢结果。请记住,订购密钥不是要订购的东西。如果我说要按姓氏订购客户列表,那么我得到的是客户列表,而不是姓氏列表。如果您说要按转换后的字符串排序字符串列表,则结果是原始字符串(而不是转换后的字符串)的排序列表。
埃里克·利珀特

我必须添加“ sizes = sizes.OrderBy(...)”才能使此工作正常进行。那是正常的还是应该编辑答案?
gorgabal

1
@gorgabal:通常,重新分配sizes也不起作用,因为结果是不同的类型。答案有点简而言之,因为第二行将结果显示为表达式,但这要由读者来决定。我添加了另一个变量赋值,以使其更清楚。
递归

74

而且,如何...

string[] sizes = new string[] { "105", "101", "102", "103", "90" };

var size = from x in sizes
           orderby x.Length, x
           select x;

foreach (var p in size)
{
    Console.WriteLine(p);
}

呵呵,我真的很喜欢这个-非常聪明。很抱歉,如果我没有提供的全套原始数据
SF。

3
就像上面的垫选项一样,这要好得多了。
dudeNumber4

3
var size = sizes.OrderBy(x => x.Length).ThenBy(x => x);
菲利普·戴维斯

1
但这会混合像这样的字母字符串:"b", "ab", "101", "103", "bob", "abcd"
安德鲁

67

值是一个字符串

List = List.OrderBy(c => c.Value.Length).ThenBy(c => c.Value).ToList();

作品


2
这个答案是我的最爱。
LacOniC

2
谢谢,我发现它退出了“ ThenBy”方法。
ganchito55 2013年

这对于我的用例非常有用,其中输入的格式为newstring[] { "Object 1", "Object 9", "Object 14" }
thelem

2
这是最好的答案。它的工作原理是一个很好的学习。谢谢 !!
2011

1
但这会混合像这样的字母字符串:"b", "ab", "101", "103", "bob", "abcd"
安德鲁

13

Windows StrCmpLogicalW中有一个本机函数,它将字符串中的数字作为数字而不是字母进行比较。制作一个比较器可以很容易地调用该函数并将其用于比较。

public class StrCmpLogicalComparer : Comparer<string>
{
    [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
    private static extern int StrCmpLogicalW(string x, string y);

    public override int Compare(string x, string y)
    {
        return StrCmpLogicalW(x, y);
    }
}

它甚至适用于同时具有文本和数字的字符串。这是一个示例程序,它将显示默认排序与StrCmpLogicalW排序之间的差异

class Program
{
    static void Main()
    {
        List<string> items = new List<string>()
        {
            "Example1.txt", "Example2.txt", "Example3.txt", "Example4.txt", "Example5.txt", "Example6.txt", "Example7.txt", "Example8.txt", "Example9.txt", "Example10.txt",
            "Example11.txt", "Example12.txt", "Example13.txt", "Example14.txt", "Example15.txt", "Example16.txt", "Example17.txt", "Example18.txt", "Example19.txt", "Example20.txt"
        };

        items.Sort();

        foreach (var item in items)
        {
            Console.WriteLine(item);
        }

        Console.WriteLine();

        items.Sort(new StrCmpLogicalComparer());

        foreach (var item in items)
        {
            Console.WriteLine(item);
        }
        Console.ReadLine();
    }
}

哪个输出

Example1.txt
Example10.txt
Example11.txt
Example12.txt
Example13.txt
Example14.txt
Example15.txt
Example16.txt
Example17.txt
Example18.txt
Example19.txt
Example2.txt
Example20.txt
Example3.txt
Example4.txt
Example5.txt
Example6.txt
Example7.txt
Example8.txt
Example9.txt

Example1.txt
Example2.txt
Example3.txt
Example4.txt
Example5.txt
Example6.txt
Example7.txt
Example8.txt
Example9.txt
Example10.txt
Example11.txt
Example12.txt
Example13.txt
Example14.txt
Example15.txt
Example16.txt
Example17.txt
Example18.txt
Example19.txt
Example20.txt

我希望可以更轻松地在C#中使用系统库
Kyle Delaney

这本来是完美的,但是不幸的是它不能处理负数。-1 0 10 2排序为0 -1 2 10
nphx,

5

试试这个

sizes.OrderBy(x => Convert.ToInt32(x)).ToList<string>();

注意:当所有字符串都可以转换为int .....时,这将很有帮助


1
这种将字符串转换为整数。
Femaref 2011年

1
“大小”也可以是非数字
SF。

对于“ LINQ to SQL”,请不要忘记ToList()之前=>sizes.ToList().OrderBy(x => Convert.ToInt32(x))
A. Morel

5

我想如果字符串中包含一些数字,那会更好。希望会有所帮助。

PS:我不确定性能或复杂的字符串值,但效果很好,如下所示:

lorem ipsum
lorem ipsum 1
lorem ipsum 2
lorem ipsum 3
...
lorem ipsum 20
lorem ipsum 21

public class SemiNumericComparer : IComparer<string>
{
    public int Compare(string s1, string s2)
    {
        int s1r, s2r;
        var s1n = IsNumeric(s1, out s1r);
        var s2n = IsNumeric(s2, out s2r);

        if (s1n && s2n) return s1r - s2r;
        else if (s1n) return -1;
        else if (s2n) return 1;

        var num1 = Regex.Match(s1, @"\d+$");
        var num2 = Regex.Match(s2, @"\d+$");

        var onlyString1 = s1.Remove(num1.Index, num1.Length);
        var onlyString2 = s2.Remove(num2.Index, num2.Length);

        if (onlyString1 == onlyString2)
        {
            if (num1.Success && num2.Success) return Convert.ToInt32(num1.Value) - Convert.ToInt32(num2.Value);
            else if (num1.Success) return 1;
            else if (num2.Success) return -1;
        }

        return string.Compare(s1, s2, true);
    }

    public bool IsNumeric(string value, out int result)
    {
        return int.TryParse(value, out result);
    }
}

正是我想要的。谢谢!
klugerama

4

您说不能将数字转换为int,因为数组可以包含无法转换为int的元素,但是尝试这样做没有害处:

string[] things = new string[] { "105", "101", "102", "103", "90", "paul", "bob", "lauren", "007", "90" };
Array.Sort(things, CompareThings);

foreach (var thing in things)
    Debug.WriteLine(thing);

然后像这样比较:

private static int CompareThings(string x, string y)
{
    int intX, intY;
    if (int.TryParse(x, out intX) && int.TryParse(y, out intY))
        return intX.CompareTo(intY);

    return x.CompareTo(y);
}

输出:007、90、90、101、102、103、105,鲍勃,劳伦,保罗


顺便说一句,为简单起见,我使用了Array.Sort,但您可以在IComparer中使用相同的逻辑并使用OrderBy。
Ulf Kristiansen 2014年

此解决方案似乎比使用IComparer更快(我认为)。15000结果,我觉得这产生了第二个差异。
杰森·福利亚

3

这似乎是一个奇怪的请求,值得一个奇怪的解决方案:

string[] sizes = new string[] { "105", "101", "102", "103", "90" };

foreach (var size in sizes.OrderBy(x => {
    double sum = 0;
    int position = 0;
    foreach (char c in x.ToCharArray().Reverse()) {
        sum += (c - 48) * (int)(Math.Pow(10,position));
        position++;
    }
    return sum;
}))

{
    Console.WriteLine(size);
}

我的意思是0x30。同样,该数组仍可以包含非数字字符串,对于该字符串,解决方案将产生有趣的结果。
Femaref 2011年

并注意-48或不改变绝对没有什么,我们可以直接使用char的整数值,因此,如果打扰您,则将其删除-48
MarinoŠimić2011年

该char值是为0x30,如果转换为int,它仍然会为0x30,这不是数字0
Femaref

唯一转换为整数的东西是Math.Pow
MarinoŠimić2011年

femaref不管它是否为零都无关紧要,十进制系统会解决这个问题,如果您愿意的话,它可以是,,唯一重要的是数字在字符集中按升序排列,而该数字要少比10
MarinoŠimić2011年

3

该站点讨论字母数字排序,并将以逻辑意义而不是ASCII意义对数字进行排序。它还考虑了周围的alpha:

http://www.dotnetperls.com/alphanumeric-sorting

例:

  • C:/TestB/333.jpg
  • 11
  • C:/TestB/33.jpg
  • 1个
  • C:/TestA/111.jpg
  • 111楼
  • C:/TestA/11.jpg
  • 2
  • C:/TestA/1.jpg
  • 111D
  • 22
  • 111Z
  • C:/TestB/03.jpg

  • 1个
  • 2
  • 11
  • 22
  • 111D
  • 111楼
  • 111Z
  • C:/TestA/1.jpg
  • C:/TestA/11.jpg
  • C:/TestA/111.jpg
  • C:/TestB/03.jpg
  • C:/TestB/33.jpg
  • C:/TestB/333.jpg

代码如下:

class Program
{
    static void Main(string[] args)
    {
        var arr = new string[]
        {
           "C:/TestB/333.jpg",
           "11",
           "C:/TestB/33.jpg",
           "1",
           "C:/TestA/111.jpg",
           "111F",
           "C:/TestA/11.jpg",
           "2",
           "C:/TestA/1.jpg",
           "111D",
           "22",
           "111Z",
           "C:/TestB/03.jpg"
        };
        Array.Sort(arr, new AlphaNumericComparer());
        foreach(var e in arr) {
            Console.WriteLine(e);
        }
    }
}

public class AlphaNumericComparer : IComparer
{
    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;
        }

        int len1 = s1.Length;
        int len2 = s2.Length;
        int marker1 = 0;
        int marker2 = 0;

        // Walk through two the strings with two markers.
        while (marker1 < len1 && marker2 < len2)
        {
            char ch1 = s1[marker1];
            char ch2 = s2[marker2];

            // Some buffers we can build up characters in for each chunk.
            char[] space1 = new char[len1];
            int loc1 = 0;
            char[] space2 = new char[len2];
            int loc2 = 0;

            // Walk through all following characters that are digits or
            // characters in BOTH strings starting at the appropriate marker.
            // Collect char arrays.
            do
            {
                space1[loc1++] = ch1;
                marker1++;

                if (marker1 < len1)
                {
                    ch1 = s1[marker1];
                }
                else
                {
                    break;
                }
            } while (char.IsDigit(ch1) == char.IsDigit(space1[0]));

            do
            {
                space2[loc2++] = ch2;
                marker2++;

                if (marker2 < len2)
                {
                    ch2 = s2[marker2];
                }
                else
                {
                    break;
                }
            } while (char.IsDigit(ch2) == char.IsDigit(space2[0]));

            // If we have collected numbers, compare them numerically.
            // Otherwise, if we have strings, compare them alphabetically.
            string str1 = new string(space1);
            string str2 = new string(space2);

            int result;

            if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
            {
                int thisNumericChunk = int.Parse(str1);
                int thatNumericChunk = int.Parse(str2);
                result = thisNumericChunk.CompareTo(thatNumericChunk);
            }
            else
            {
                result = str1.CompareTo(str2);
            }

            if (result != 0)
            {
                return result;
            }
        }
        return len1 - len2;
    }
}

2

Jeff Paulsen给出的答案是正确的,但是Comprarer可以简化为:

public class SemiNumericComparer: IComparer<string>
{
    public int Compare(string s1, string s2)
    {
        if (IsNumeric(s1) && IsNumeric(s2))
          return Convert.ToInt32(s1) - Convert.ToInt32(s2)

        if (IsNumeric(s1) && !IsNumeric(s2))
            return -1;

        if (!IsNumeric(s1) && IsNumeric(s2))
            return 1;

        return string.Compare(s1, s2, true);
    }

    public static bool IsNumeric(object value)
    {
        int result;
        return Int32.TryParse(value, out result);
    }
}

之所以有效,是因为检查的结果的唯一方法是Comparer结果是否更大,更小或等于零。一个人可以简单地从另一个中减去值,而不必处理返回值。

同样,该IsNumeric方法不必必须使用try-block并可以从中受益TryParse

对于那些不确定的人:该比较器将对值进行排序,以便始终将非数字值附加到列表的末尾。如果在开始时要它们,则if必须交换第二个和第三个块。


由于调用TryParse方法可能会产生一些开销,因此我会先将s1和s2的isNumeric值存储到布尔值中,然后对它们进行比较。这样,它们不会被多次评估。
Optavius '16

1

试试这个 :

string[] things= new string[] { "105", "101", "102", "103", "90" };

int tmpNumber;

foreach (var thing in (things.Where(xx => int.TryParse(xx, out tmpNumber)).OrderBy(xx =>     int.Parse(xx))).Concat(things.Where(xx => !int.TryParse(xx, out tmpNumber)).OrderBy(xx => xx)))
{
    Console.WriteLine(thing);
}

1
public class NaturalSort: IComparer<string>
{
          [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
          public static extern int StrCmpLogicalW(string x, string y);

          public int Compare(string x, string y)
          {
                 return StrCmpLogicalW(x, y);
          }
}

arr = arr.OrderBy(x => x,new NaturalSort())。ToArray();

我需要它的原因是将其归档在文件名以数字开头的目录中:

public static FileInfo[] GetFiles(string path)
{
  return new DirectoryInfo(path).GetFiles()
                                .OrderBy(x => x.Name, new NaturalSort())
                                .ToArray();
}

0
Try this out..  



  string[] things = new string[] { "paul", "bob", "lauren", "007", "90", "-10" };

        List<int> num = new List<int>();
        List<string> str = new List<string>();
        for (int i = 0; i < things.Count(); i++)
        {

            int result;
            if (int.TryParse(things[i], out result))
            {
                num.Add(result);
            }
            else
            {
                str.Add(things[i]);
            }


        }

现在对列表进行排序并将其合并回去...

        var strsort = from s in str
                      orderby s.Length
                      select s;

        var numsort = from n in num
                     orderby n
                     select n;

        for (int i = 0; i < things.Count(); i++)
        {

         if(i < numsort.Count())
             things[i] = numsort.ElementAt(i).ToString();
             else
             things[i] = strsort.ElementAt(i - numsort.Count());               
               }

我试图在这个有趣的问题上做出贡献...


0

我的首选解决方案(如果所有字符串均为数字):

// Order by numerical order: (Assertion: all things are numeric strings only) 
foreach (var thing in things.OrderBy(int.Parse))
{
    Console.Writeline(thing);
}

0
public class Test
{
    public void TestMethod()
    {
        List<string> buyersList = new List<string>() { "5", "10", "1", "str", "3", "string" };
        List<string> soretedBuyersList = null;

        soretedBuyersList = new List<string>(SortedList(buyersList));
    }

    public List<string> SortedList(List<string> unsoredList)
    {
        return unsoredList.OrderBy(o => o, new SortNumericComparer()).ToList();
    }
}

   public class SortNumericComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        int xInt = 0;
        int yInt = 0;
        int result = -1;

        if (!int.TryParse(x, out xInt))
        {
            result = 1;
        }

        if(int.TryParse(y, out yInt))
        {
            if(result == -1)
            {
                result = xInt - yInt;
            }
        }
        else if(result == 1)
        {
             result = string.Compare(x, y, true);
        }

        return result;
    }
}

你能解释一下你的代码吗?纯代码答案可能会被删除。

Jeff Paulsen的帖子帮助我实现了IComparer <string>来解决我的问题。。
kumar 2015年

0

扩展Jeff Paulsen的答案。我想确保在字符串中有多少个数字或字符组无关紧要:

public class SemiNumericComparer : IComparer<string>
{
    public int Compare(string s1, string s2)
    {
        if (int.TryParse(s1, out var i1) && int.TryParse(s2, out var i2))
        {
            if (i1 > i2)
            {
                return 1;
            }

            if (i1 < i2)
            {
                return -1;
            }

            if (i1 == i2)
            {
                return 0;
            }
        }

        var text1 = SplitCharsAndNums(s1);
        var text2 = SplitCharsAndNums(s2);

        if (text1.Length > 1 && text2.Length > 1)
        {

            for (var i = 0; i < Math.Max(text1.Length, text2.Length); i++)
            {

                if (text1[i] != null && text2[i] != null)
                {
                    var pos = Compare(text1[i], text2[i]);
                    if (pos != 0)
                    {
                        return pos;
                    }
                }
                else
                {
                    //text1[i] is null there for the string is shorter and comes before a longer string.
                    if (text1[i] == null)
                    {
                        return -1;
                    }
                    if (text2[i] == null)
                    {
                        return 1;
                    }
                }
            }
        }

        return string.Compare(s1, s2, true);
    }

    private string[] SplitCharsAndNums(string text)
    {
        var sb = new StringBuilder();
        for (var i = 0; i < text.Length - 1; i++)
        {
            if ((!char.IsDigit(text[i]) && char.IsDigit(text[i + 1])) ||
                (char.IsDigit(text[i]) && !char.IsDigit(text[i + 1])))
            {
                sb.Append(text[i]);
                sb.Append(" ");
            }
            else
            {
                sb.Append(text[i]);
            }
        }

        sb.Append(text[text.Length - 1]);

        return sb.ToString().Split(' ');
    }
}

在修改它以处理文件名之后,我还从SO页中获取了SplitCharsAndNums 。


-1

即使这是一个古老的问题,我也想给出一个解决方案:

string[] things= new string[] { "105", "101", "102", "103", "90" };

foreach (var thing in things.OrderBy(x => Int32.Parse(x) )
{
    Console.WriteLine(thing);
}

哇哈很简单吧?:D


-1
namespace X
{
    public class Utils
    {
        public class StrCmpLogicalComparer : IComparer<Projects.Sample>
        {
            [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
            private static extern int StrCmpLogicalW(string x, string y);


            public int Compare(Projects.Sample x, Projects.Sample y)
            {
                string[] ls1 = x.sample_name.Split("_");
                string[] ls2 = y.sample_name.Split("_");
                string s1 = ls1[0];
                string s2 = ls2[0];
                return StrCmpLogicalW(s1, s2);
            }
        }

    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.