217

C# 中是否有一种简单的方法可以为数字创建数?例如:

  • 1 返回第一个
  • 2 返回第 2
  • 3 返回第 3
  • ...ETC

这可以通过String.Format()还是有任何功能可以做到这一点?

4

21 回答 21

333

此页面为您提供了所有自定义数字格式规则的完整列表:

自定义数字格式字符串

如您所见,其中没有关于序数的任何内容,因此无法使用String.Format. 然而,编写一个函数来做到这一点并不难。

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()方法。

另请注意,这不是国际化的。我不知道其他语言中的序数是什么样的。

于 2008-08-21T15:03:47.973 回答
77

记住国际化!

这里的解决方案仅适用于英语。如果您需要支持其他语言,事情会变得更加复杂。

例如,在西班牙语中,“1st”会写成“1.o”、“1.a”、“1.os”或“1.as”,具体取决于您计算的事物是阳性、阴性还是复数!

因此,如果您的软件需要支持不同的语言,请尽量避免使用序数。

于 2008-09-22T15:17:33.343 回答
23

我的 Jesse 版本的 Stu 的版本和 samjudson 的版本 :)

包含单元测试以显示当数字 < 1 时接受的答案不正确

/// <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());
}
于 2009-03-06T21:31:34.640 回答
20

简单、干净、快速

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

或者更好的是,作为一种扩展方法

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

现在你可以打电话

int a = 1;
a.DisplayWithSuffix(); 

甚至直接

1.DisplayWithSuffix();
于 2014-10-17T21:12:10.267 回答
16

你必须自己动手。从我的头顶:

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

然后你可以做

Console.WriteLine(432.Ordinal());

针对 2013 年 11 月 12 日的例外情况进行了编辑。我确实是从头顶说的:-)

为 1011 编辑 - 其他人已经修复了这个问题,只是想确保其他人不会抓住这个不正确的版本。

于 2008-08-21T14:59:10.967 回答
13

我更喜欢Stusamjudson的解决方案中的元素,并将它们组合成我认为可用的组合:

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;
    }
}
于 2008-08-27T19:56:15.977 回答
9

虽然我还没有对此进行基准测试,但您应该能够通过避免所有条件 case 语句来获得更好的性能。

这是 java,但移植到 C# 是微不足道的:

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

请注意,如果在紧密循环中生成大量序数,则减少条件和使用数组查找应该会提高性能。但是,我也承认这不像案例陈述解决方案那样可读。

于 2008-09-21T17:52:01.517 回答
3

类似于 Ryan 的解决方案,但更基本的是,我只使用一个普通数组并使用 day 来查找正确的序数:

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

我没有需要,但我假设如果你想支持多语言,你可以使用多维数组。

根据我在 Uni 时代的记忆,这种方法只需要服务器付出最少的努力。

于 2013-05-02T13:48:06.333 回答
3
private static string GetOrd(int num) => $"{num}{(!(Range(11, 3).Any(n => n == num % 100) ^ Range(1, 3).All(n => n != num % 10)) ? new[] { "ˢᵗ&quot;, "ⁿᵈ", "ʳᵈ&quot; }[num % 10 - 1] : "ᵗʰ&quot;)}";

如果有人正在寻找单线。

于 2016-04-27T00:29:32.650 回答
2

我使用这个扩展类:

public static class Int32Extensions
{
    public static string ToOrdinal(this int i)
    {
        return (i + "th")
            .Replace("1th", "1st")
            .Replace("2th", "2nd")
            .Replace("3th", "3rd");
    }
}
于 2016-09-23T16:15:29.777 回答
2

要求 samjudson 答案的“减少冗余”版本......

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);
}
于 2017-04-06T16:32:25.287 回答
1
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";
}
于 2013-12-16T12:02:49.343 回答
1

编辑:正如 YM_Industries 在评论中指出的那样,samjudson 的回答确实适用于超过 1000 的数字,nickf 的评论似乎已经消失,我不记得我看到的问题是什么。将此答案留在这里用于比较时间。

正如nickf在评论中指出的那样,其中很多不适用于 > 999 的数字(编辑:现在丢失)。

这是一个基于samjudson接受的答案的修改版本的版本。

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 示例:

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";
        }
}
于 2014-12-16T16:59:06.723 回答
1

基于其他答案:

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";                                              
}
于 2017-05-16T19:36:30.467 回答
1

虽然这里有很多很好的答案,但我想还有另一个空间,这一次是基于模式匹配,如果没有其他任何东西,那么至少是有争议的可读性

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

是什么让这个解决方案与众不同?除了我正在为各种其他解决方案添加一些性能考虑之外的事实

坦率地说,我怀疑性能对于这种特殊情况(谁真的需要数百万个数字的序数)真的很重要,但至少它显示了一些需要考虑的比较......

100 万件供参考(当然,您的米数可能会因机器规格而异)

带有模式匹配和除法(这个答案)

~622 毫秒

使用模式匹配和字符串(这个答案)

~1967 毫秒

带有两个开关和分区(接受的答案)

~637 毫秒

一个开关和一个部门(另一个答案)

~725 毫秒

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

于 2019-10-14T14:03:47.823 回答
0

FWIW,对于 MS-SQL,这个表达式将完成这项工作。将第一个 WHEN ( ) 保留WHEN num % 100 IN (11, 12, 13) THEN 'th'为列表中的第一个,因为这依赖于在其他之前尝试。

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:

=MID("thstndrdth",MIN(9,2*RIGHT(A1)*(MOD(A1-11,100)>2)+1),2)

对于除以(FALSE = 0)(MOD(A1-11,100)>2)结尾的任何数字之外的所有数字,表达式 均为 TRUE (1)。11,12,13所以2 * RIGHT(A1) * (MOD(A1-11,100)>2) +1)在 11/12/13 结束时为 1,否则:
1 计算为 3
2 到5,3
到 7
其他:9 - 并且从该位置开始
选择所需的 2 个字符。"thstndrdth"

如果您真的想将其直接转换为 SQL,这对我来说适用于少数测试值:

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
                )
于 2014-09-19T13:46:47.880 回答
0

这是实现,dart可以根据语言进行修改。

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";
}
于 2019-06-04T07:45:56.067 回答
0

另一个单行,但不通过仅将正则表达式结果索引到数组中进行比较。

public static string GetOrdinalSuffix(int input)
{
    return new []{"th", "st", "nd", "rd"}[Convert.ToInt32("0" + Regex.Match(input.ToString(), "(?<!1)[1-3]$").Value)];
}

PowerShell 版本可以进一步缩短:

function ord($num) { return ('th','st','nd','rd')[[int]($num -match '(?<!1)[1-3]$') * $matches[0]] }
于 2019-10-30T19:56:39.053 回答
0

另一个 1 班轮。

public static string Ordinal(this int n)
{    
 return n + (new [] {"st","nd","rd" }.ElementAtOrDefault((((n + 90) % 100 - 10) % 10 - 1)) ?? "th");
}
于 2020-05-06T20:35:33.363 回答
-2

这是 DateTime 扩展类。复制、粘贴和享受

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 日

于 2014-10-09T15:16:11.193 回答
-3

我根据所有其他建议使用的另一种替代方法,但不需要特殊的外壳:

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";
    }
}
于 2014-02-23T12:18:39.487 回答