How do I sort strings alphabetically while accounting for value when a string is numeric?

I'm trying to sort an array of numbers that are strings and I'd like them to sort numerically.

The catch is that I cannot convert the numbers into int.

Here is the code:

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


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

Output:

101, 102, 103, 105, 90

I'd like:

90, 101, 102, 103, 105

EDIT: The output can't be 090, 101, 102...

Updated the code sample to say "things" instead of "sizes". The array can be something like this:

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

That means it needs to be sorted alphabetically and by number:

007, 90, bob, lauren, paul
128502 次浏览

try this

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

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

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

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

只需用相同长度的零填充:

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

还有,这个怎么样..。

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

将自定义比较器传递到 OrderBy.可枚举的 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);
}
}
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());
}

我只是想在这个有趣的问题上做点贡献。

试试这个:

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

你说你不能把数字转换成整数,因为数组中可能包含不能转换成整数的元素,但是尝试一下也无妨:

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,鲍勃,劳伦,保罗

尽管这是一个老问题,我还是想给出一个解决方案:

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


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

Woha quite simple right? :D

这个网站讨论字母数字排序,并将排序的数字在逻辑意义上,而不是 ASCII 意义。它还考虑到了周围的阿尔法人群:

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

例子:

  • 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

  • 1
  • 2
  • 11
  • 22
  • 111D
  • 111F
  • 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;
}
}

The answer given by Jeff Paulsen is correct but the Comprarer can be much simplified to this:

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块,可以从 TryParse受益。

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

我的首选解决方案(如果所有字符串都是数值型的) :

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

值是一个字符串

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

工程

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

I guess this will be much more good if it has some numeric in the string. 希望能有所帮助。

PS: 我不确定性能或者复杂的字符串值,但是它的工作原理是这样的:

lorem ipsum
Lorem ipsum 1
Lorem ipsum 2
Lorem ipsum 3
...
lorem ipsum 20
Lorem ipsum 21 < br >

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

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


}
}

Expanding on Jeff Paulsen answer. I wanted to make sure it didn't matter how many number or char groups were in the strings:

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

在修改 所以,佩奇以处理文件名之后,我还从 所以,佩奇中提取了 SplitCharsAndNums。

Example of short IComparer class.

  1. 如果两个字符串参数都可以转换为整数,那么参数 解析为整数并进行比较
  2. if only one argument can be converted to integer, then integer is 优先(具有较低的值) ,并插入到字符串之前。
  3. 如果没有一个参数可以转换成整数,那么普通 string comparison is used.

密码:

public class CompareIntegerStrings : IComparer<string>
{
public int Compare(string x, string y)
{
if (int.TryParse(x, out int xOut) && int.TryParse(y, out int yOut))
return xOut.CompareTo(yOut);
else if (int.TryParse(x, out _))
return -1;
else if (int.TryParse(y, out _))
return 1;
else
return x.CompareTo(y);
    

}
}

在这个例子中

List<string> intStrings = new List<string> { "01","0022","abba", "11", "deep purple", "02", };
List<string> orderedIntStrings = intStrings.OrderBy(i=>i,new CompareIntegerStrings()).ToList();

有序列表 orderedIntString 是{“01”,“02”,“11”,“0022”,“ abba”,“深紫色”}。

使用正则表达式。替换是 so简单而有效。请注意,数字“3”必须是一个等于或大于您的最长字符串的数字,因此对于任何其他人,根据需要增加。

using System.Text.RegularExpressions;


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


foreach (var thing in things.OrderBy(x => Regex.Replace(x, @"\d+", i =>
i.Value.PadLeft(3, '0'))))
{
Console.WriteLine(thing);
}

在递归的回答下,我本来可以评论的,但是我的名声太低了。

因为递归的答案只适用于数字字符串(如果你有一个像 "I am just a damn long string"这样的字符串,它会在 "Not so long string"之后排序) ,OP 编辑了他的答案,我对这个问题的想法是通过将字符串区分成数字而不是数字来排序:

int maxlen = items.Max(x => x.Length);
var items  = items.OrderBy(x => long.TryParse(x, out _) == true ? x.PadLeft(maxlen, '0') : x);

下划线用于丢弃输出

建议使用 NaturalSort.Extension (nuget/Github) ,因为从答案中可以看出这是一个相当困难的操作。

using NaturalSort.Extension;
var ordered = things.OrderBy(x => x, StringComparison.OrdinalIgnoreCase.WithNaturalSort());