Is there an easy way to create ordinals in C#?
C#中有没有一种简单的方法可以为数字创建序数? 例如:
可以通过String.Format()完成此操作吗?有没有可用的功能可以做到这一点?
该页面为您提供了所有自定义数字格式设置规则的完整列表:
自定义数字格式字符串
如您所见,关于序号的内容不多,因此无法使用String.Format完成。然而,编写一个函数来实现它并不那么困难。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static string AddOrdinal(int num)
{
if( num <= 0 ) return num.ToString();
switch(num % 100)
{
case 11:
case 12:
case 13:
return num +"th";
}
switch(num % 10)
{
case 1:
return num +"st";
case 2:
return num +"nd";
case 3:
return num +"rd";
default:
return num +"th";
}
} |
更新:从技术上讲,对于<= 0而言,普通字符不存在,因此我更新了上面的代码。还删除了多余的ToString()方法。
另请注意,这不是国际化的。我不知道其他语言的普通语言是什么样子。
记住国际化!
这里的解决方案仅适用于英语。如果您需要支持其他语言,事情会变得更加复杂。
例如,在西班牙语中," 1st"将被写为" 1.o"," 1.a"," 1.os"或" 1.as",具体取决于您要计数的是男性,女性还是复数形式!
因此,如果您的软件需要支持其他语言,请尝试避免使用普通字符。
我的Jesse版本的Stu版本和samjudson版本的:)
包含的单元测试表明,当数字<1时,可接受的答案不正确
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| /// <summary>
/// Get the ordinal value of positive integers.
/// </summary>
/// <remarks>
/// Only works for english-based cultures.
/// Code from: http://stackoverflow.com/questions/20156/is-there-a-quick-way-to-create-ordinals-in-c/31066#31066
/// With help: http://www.wisegeek.com/what-is-an-ordinal-number.htm
/// </remarks>
/// <param name="number">The number.</param>
/// <returns>Ordinal value of positive integers, or <see cref="int.ToString"/> if less than 1.</returns>
public static string Ordinal(this int number)
{
const string TH ="th";
string s = number.ToString();
// Negative and zero have no ordinal representation
if (number < 1)
{
return s;
}
number %= 100;
if ((number >= 11) && (number <= 13))
{
return s + TH;
}
switch (number % 10)
{
case 1: return s +"st";
case 2: return s +"nd";
case 3: return s +"rd";
default: return s + TH;
}
}
[Test]
public void Ordinal_ReturnsExpectedResults()
{
Assert.AreEqual("-1", (1-2).Ordinal());
Assert.AreEqual("0", 0.Ordinal());
Assert.AreEqual("1st", 1.Ordinal());
Assert.AreEqual("2nd", 2.Ordinal());
Assert.AreEqual("3rd", 3.Ordinal());
Assert.AreEqual("4th", 4.Ordinal());
Assert.AreEqual("5th", 5.Ordinal());
Assert.AreEqual("6th", 6.Ordinal());
Assert.AreEqual("7th", 7.Ordinal());
Assert.AreEqual("8th", 8.Ordinal());
Assert.AreEqual("9th", 9.Ordinal());
Assert.AreEqual("10th", 10.Ordinal());
Assert.AreEqual("11th", 11.Ordinal());
Assert.AreEqual("12th", 12.Ordinal());
Assert.AreEqual("13th", 13.Ordinal());
Assert.AreEqual("14th", 14.Ordinal());
Assert.AreEqual("20th", 20.Ordinal());
Assert.AreEqual("21st", 21.Ordinal());
Assert.AreEqual("22nd", 22.Ordinal());
Assert.AreEqual("23rd", 23.Ordinal());
Assert.AreEqual("24th", 24.Ordinal());
Assert.AreEqual("100th", 100.Ordinal());
Assert.AreEqual("101st", 101.Ordinal());
Assert.AreEqual("102nd", 102.Ordinal());
Assert.AreEqual("103rd", 103.Ordinal());
Assert.AreEqual("104th", 104.Ordinal());
Assert.AreEqual("110th", 110.Ordinal());
Assert.AreEqual("111th", 111.Ordinal());
Assert.AreEqual("112th", 112.Ordinal());
Assert.AreEqual("113th", 113.Ordinal());
Assert.AreEqual("114th", 114.Ordinal());
Assert.AreEqual("120th", 120.Ordinal());
Assert.AreEqual("121st", 121.Ordinal());
Assert.AreEqual("122nd", 122.Ordinal());
Assert.AreEqual("123rd", 123.Ordinal());
Assert.AreEqual("124th", 124.Ordinal());
} |
简单,干净,快捷
1 2 3 4 5 6 7 8 9 10
| private static string GetOrdinalSuffix(int num)
{
if (num.ToString().EndsWith("11")) return"th";
if (num.ToString().EndsWith("12")) return"th";
if (num.ToString().EndsWith("13")) return"th";
if (num.ToString().EndsWith("1")) return"st";
if (num.ToString().EndsWith("2")) return"nd";
if (num.ToString().EndsWith("3")) return"rd";
return"th";
} |
或更好,作为一种扩展方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static class IntegerExtensions
{
public static string DisplayWithSuffix(this int num)
{
if (num.ToString().EndsWith("11")) return num.ToString() +"th";
if (num.ToString().EndsWith("12")) return num.ToString() +"th";
if (num.ToString().EndsWith("13")) return num.ToString() +"th";
if (num.ToString().EndsWith("1")) return num.ToString() +"st";
if (num.ToString().EndsWith("2")) return num.ToString() +"nd";
if (num.ToString().EndsWith("3")) return num.ToString() +"rd";
return num.ToString() +"th";
}
} |
现在你可以打电话
1 2
| int a = 1;
a.DisplayWithSuffix(); |
甚至直接
您必须自己动手。从我的头顶:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static string Ordinal(this int number)
{
var work = number.ToString();
if ((number % 100) == 11 || (number % 100) == 12 || (number % 100) == 13)
return work +"th";
switch (number % 10)
{
case 1: work +="st"; break;
case 2: work +="nd"; break;
case 3: work +="rd"; break;
default: work +="th"; break;
}
return work;
} |
然后你可以做
1
| Console.WriteLine(432.Ordinal()); |
修改为13/12/13例外。我从头顶上说:-)
针对1011版进行了编辑-其他人已经解决了此问题,只是想确保其他人不会抓住这个错误的版本。
我宁愿喜欢Stu和samjudson的解决方案中的元素,并将它们一起工作到我认为是可用的组合中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static string Ordinal(this int number)
{
const string TH ="th";
var s = number.ToString();
number %= 100;
if ((number >= 11) && (number <= 13))
{
return s + TH;
}
switch (number % 10)
{
case 1:
return s +"st";
case 2:
return s +"nd";
case 3:
return s +"rd";
default:
return s + TH;
}
} |
尽管我还没有对此进行基准测试,但是您应该能够避免所有条件案例语句,从而获得更好的性能。
这是java,但是C#的端口很简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class NumberUtil {
final static String[] ORDINAL_SUFFIXES = {
"th","st","nd","rd","th","th","th","th","th","th"
};
public static String ordinalSuffix(int value) {
int n = Math.abs(value);
int lastTwoDigits = n % 100;
int lastDigit = n % 10;
int index = (lastTwoDigits >= 11 && lastTwoDigits <= 13) ? 0 : lastDigit;
return ORDINAL_SUFFIXES[index];
}
public static String toOrdinal(int n) {
return new StringBuffer().append(n).append(ordinalSuffix(n)).toString();
}
} |
请注意,如果在紧密循环中生成大量的序数,则减少条件数和使用数组查找应可提高性能。但是,我也承认这不像case语句解决方案那样可读。
与Ryan的解决方案类似,但更基本,我只使用一个简单数组并使用一天来查找正确的序数:
1 2 3
| private string[] ordinals = new string[] {"","st","nd","rd","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","st","nd","rd","th","th","th","th","th","th","th","st" };
DateTime D = DateTime.Now;
String date ="Today's day is:"+ D.Day.ToString() + ordinals[D.Day]; |
我没有这个需要,但是我想如果您想获得多种语言支持,可以使用多维数组。
从我上大学的那一天起,我还记得这种方法需要服务器的最少工作。
要求samjudson回答的"较少冗余"版本...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public static string AddOrdinal(int number)
{
if (number <= 0) return number.ToString();
string GetIndicator(int num)
{
switch (num % 100)
{
case 11:
case 12:
case 13:
return"th";
}
switch (num % 10)
{
case 1:
return"st";
case 2:
return"nd";
case 3:
return"rd";
default:
return"th";
}
}
return number + GetIndicator(number);
} |
我使用这个扩展类:
1 2 3 4 5 6 7 8 9 10
| public static class Int32Extensions
{
public static string ToOrdinal(this int i)
{
return (i +"th")
.Replace("1th","1st")
.Replace("2th","2nd")
.Replace("3th","3rd");
}
} |
1
| private static string GetOrd(int num) => $"{num}{(!(Range(11, 3).Any(n => n == num % 100) ^ Range(1, 3).All(n => n != num % 10)) ? new[] {"??","??","??" }[num % 10 - 1] :"??")}"; |
如果有人在寻找单线。
尽管这里有很多好的答案,但我想这还是有一个余地,这一次是基于模式匹配的,如果没有其他目的,那么至少是值得商bat的可读性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static string Ordinals1(this int number)
{
switch (number)
{
case int p when p % 100 == 11:
case int q when q % 100 == 12:
case int r when r % 100 == 13:
return $"{number}th";
case int p when p % 10 == 1:
return $"{number}st";
case int p when p % 10 == 2:
return $"{number}nd";
case int p when p % 10 == 3:
return $"{number}rd";
default:
return $"{number}th";
}
} |
是什么使该解决方案与众不同?我只不过为其他各种解决方案添加了一些性能考虑因素
坦率地说,我怀疑性能对于这种特定情况(确实需要数百万个序数的情况)是否确实重要,但是至少它会浮现出一些需要考虑的比较...
1 million items for reference (your millage may vary based on machine specs of course)
with pattern matching and divisions (this answer)
~622 ms
with pattern matching and strings (this answer)
~1967 ms
with two switches and divisions (accepted answer)
~637 ms
with one switch and divisions (another answer)
~725 ms
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| void Main()
{
var timer = new Stopwatch();
var numbers = Enumerable.Range(1, 1000000).ToList();
// 1
timer.Reset();
timer.Start();
var results1 = numbers.Select(p => p.Ordinals1()).ToList();
timer.Stop();
timer.Elapsed.TotalMilliseconds.Dump("with pattern matching and divisions");
// 2
timer.Reset();
timer.Start();
var results2 = numbers.Select(p => p.Ordinals2()).ToList();
timer.Stop();
timer.Elapsed.TotalMilliseconds.Dump("with pattern matching and strings");
// 3
timer.Reset();
timer.Start();
var results3 = numbers.Select(p => p.Ordinals3()).ToList();
timer.Stop();
timer.Elapsed.TotalMilliseconds.Dump("with two switches and divisons");
// 4
timer.Reset();
timer.Start();
var results4 = numbers.Select(p => p.Ordinals4()).ToList();
timer.Stop();
timer.Elapsed.TotalMilliseconds.Dump("with one switche and divisons");
}
public static class Extensions
{
public static string Ordinals1(this int number)
{
switch (number)
{
case int p when p % 100 == 11:
case int q when q % 100 == 12:
case int r when r % 100 == 13:
return $"{number}th";
case int p when p % 10 == 1:
return $"{number}st";
case int p when p % 10 == 2:
return $"{number}nd";
case int p when p % 10 == 3:
return $"{number}rd";
default:
return $"{number}th";
}
}
public static string Ordinals2(this int number)
{
var text = number.ToString();
switch (text)
{
case string p when p.EndsWith("11"):
return $"{number}th";
case string p when p.EndsWith("12"):
return $"{number}th";
case string p when p.EndsWith("13"):
return $"{number}th";
case string p when p.EndsWith("1"):
return $"{number}st";
case string p when p.EndsWith("2"):
return $"{number}nd";
case string p when p.EndsWith("3"):
return $"{number}rd";
default:
return $"{number}th";
}
}
public static string Ordinals3(this int number)
{
switch (number % 100)
{
case 11:
case 12:
case 13:
return $"{number}th";
}
switch (number % 10)
{
case 1:
return $"{number}st";
case 2:
return $"{number}nd";
case 3:
return $"{number}rd";
default:
return $"{number}th";
}
}
public static string Ordinals4(this int number)
{
var ones = number % 10;
var tens = Math.Floor(number / 10f) % 10;
if (tens == 1)
{
return $"{number}th";
}
switch (ones)
{
case 1:
return $"{number}th";
case 2:
return $"{number}nd";
case 3:
return $"{number}rd";
default:
return $"{number}th";
}
}
} |
基于其他答案:
1 2 3 4 5 6
| public static string Ordinal(int n)
{
int r = n % 100, m = n % 10;
return (r<4 || r>20) && (m>0 && m<4) ? n+" stndrd".Substring(m*2,2) : n+"th";
} |
编辑:正如YM_Industries在评论中指出的那样,samjudson的答案确实适用于超过1000的人数,尼克的评论似乎已经消失了,我不记得我看到的问题是什么。在这里将这个答案留作比较时间。
正如nickf在评论中指出的那样,其中很多都不适用于大于999的数字(编辑:现在丢失)。
这是一个基于samjudson接受的答案的修改版本的版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public static String GetOrdinal(int i)
{
String res ="";
if (i > 0)
{
int j = (i - ((i / 100) * 100));
if ((j == 11) || (j == 12) || (j == 13))
res ="th";
else
{
int k = i % 10;
if (k == 1)
res ="st";
else if (k == 2)
res ="nd";
else if (k == 3)
res ="rd";
else
res ="th";
}
}
return i.ToString() + res;
} |
Shahzad Qureshi使用字符串操作的答案也很好,但是确实会降低性能。为了生成大量这样的代码,LINQPad示例程序使字符串版本比此整数之一慢6到7倍(尽管您必须产生很多注意)。
LINQPad示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| void Main()
{
"Examples:".Dump();
foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 10000013 })
Stuff.GetOrdinal(i).Dump();
String s;
System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
for(int iter = 0; iter < 100000; iter++)
foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 1000013 })
s = Stuff.GetOrdinal(i);
"Integer manipulation".Dump();
sw.Elapsed.Dump();
sw.Restart();
for(int iter = 0; iter < 100000; iter++)
foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 1000013 })
s = (i.ToString() + Stuff.GetOrdinalSuffix(i));
"String manipulation".Dump();
sw.Elapsed.Dump();
}
public class Stuff
{
// Use integer manipulation
public static String GetOrdinal(int i)
{
String res ="";
if (i > 0)
{
int j = (i - ((i / 100) * 100));
if ((j == 11) || (j == 12) || (j == 13))
res ="th";
else
{
int k = i % 10;
if (k == 1)
res ="st";
else if (k == 2)
res ="nd";
else if (k == 3)
res ="rd";
else
res ="th";
}
}
return i.ToString() + res;
}
// Use string manipulation
public static string GetOrdinalSuffix(int num)
{
if (num.ToString().EndsWith("11")) return"th";
if (num.ToString().EndsWith("12")) return"th";
if (num.ToString().EndsWith("13")) return"th";
if (num.ToString().EndsWith("1")) return"st";
if (num.ToString().EndsWith("2")) return"nd";
if (num.ToString().EndsWith("3")) return"rd";
return"th";
}
} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static string OrdinalSuffix(int ordinal)
{
//Because negatives won't work with modular division as expected:
var abs = Math.Abs(ordinal);
var lastdigit = abs % 10;
return
//Catch 60% of cases (to infinity) in the first conditional:
lastdigit > 3 || lastdigit == 0 || (abs % 100) - lastdigit == 10 ?"th"
: lastdigit == 1 ?"st"
: lastdigit == 2 ?"nd"
:"rd";
} |
FWIW,对于MS-SQL,此表达式即可完成工作。将第一个WHEN(WHEN num % 100 IN (11, 12, 13) THEN 'th')保留为列表中的第一个,因为这要先于其他尝试。
1 2 3 4 5 6 7
| CASE
WHEN num % 100 IN (11, 12, 13) THEN 'th' -- must be tried first
WHEN num % 10 = 1 THEN 'st'
WHEN num % 10 = 2 THEN 'nd'
WHEN num % 10 = 3 THEN 'rd'
ELSE 'th'
END AS Ordinal |
对于Excel:
1
| =MID("thstndrdth",MIN(9,2*RIGHT(A1)*(MOD(A1-11,100)>2)+1),2) |
对于除11,12,13结尾的任何数字以外的所有数字,表达式(MOD(A1-11,100)>2)为TRUE(1)(FALSE = 0)。因此2 * RIGHT(A1) * (MOD(A1-11,100)>2) +1)在13/12/12时最终为1。
1等于3
2到5
3至7
其他:9
-,然后从该位置的"thstndrdth"中选择所需的2个字符。
如果您真的想直接将其转换为SQL,这对我来说适用于一些测试值:
1 2 3 4 5 6 7 8 9 10
| DECLARE @n as int
SET @n=13
SELECT SubString( 'thstndrdth'
, (SELECT MIN(value) FROM
(SELECT 9 as value UNION
SELECT 1+ (2* (ABS(@n) % 10) * CASE WHEN ((ABS(@n)+89) % 100)>2 THEN 1 ELSE 0 END)
) AS Mins
)
, 2
) |
另外1个班轮。
1 2 3 4
| public static string Ordinal(this int n)
{
return n + (new [] {"st","nd","rd" }.ElementAtOrDefault((((n + 90) % 100 - 10) % 10 - 1)) ??"th");
} |
这是dart中的实现,可以根据语言进行修改。
1 2 3 4 5 6 7 8 9
| String getOrdinalSuffix(int num){
if (num.toString().endsWith("11")) return"th";
if (num.toString().endsWith("12")) return"th";
if (num.toString().endsWith("13")) return"th";
if (num.toString().endsWith("1")) return"st";
if (num.toString().endsWith("2")) return"nd";
if (num.toString().endsWith("3")) return"rd";
return"th";
} |
另一种形式,但不进行比较,只将正则表达式索引到数组中。
1 2 3 4
| public static string GetOrdinalSuffix(int input)
{
return new []{"th","st","nd","rd"}[Convert.ToInt32("0" + Regex.Match(input.ToString(),"(?<!1)[1-3]$").Value)];
} |
PowerShell版本可以进一步缩短:
1
| function ord($num) { return ('th','st','nd','rd')[[int]($num -match '(?<!1)[1-3]$') * $matches[0]] } |
这是DateTime扩展类。复制,粘贴和欣赏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public static class DateTimeExtensions
{
public static string ToStringWithOrdinal(this DateTime d)
{
var result ="";
bool bReturn = false;
switch (d.Day % 100)
{
case 11:
case 12:
case 13:
result = d.ToString("dd'th' MMMM yyyy");
bReturn = true;
break;
}
if (!bReturn)
{
switch (d.Day % 10)
{
case 1:
result = d.ToString("dd'st' MMMM yyyy");
break;
case 2:
result = d.ToString("dd'nd' MMMM yyyy");
break;
case 3:
result = d.ToString("dd'rd' MMMM yyyy");
break;
default:
result = d.ToString("dd'th' MMMM yyyy");
break;
}
}
if (result.StartsWith("0")) result = result.Substring(1);
return result;
}
} |
结果:
2014年10月9日
我根据所有其他建议使用的另一个替代方法,但不需要特殊的大小写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static string DateSuffix(int day)
{
if (day == 11 | day == 12 | day == 13) return"th";
Math.DivRem(day, 10, out day);
switch (day)
{
case 1:
return"st";
case 2:
return"nd";
case 3:
return"rd";
default:
return"th";
}
} |
|