有没有一种简单的方法可以在 C# 中创建序数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20156/
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-01 09:05:05  来源:igfitidea点击:

Is there an easy way to create ordinals in C#?

提问by GateKiller

Is there an easy way in C# to create Ordinalsfor a number? For example:

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

  • 1 returns 1st
  • 2 returns 2nd
  • 3 returns 3rd
  • ...etc
  • 1 返回第一个
  • 2 返回第二
  • 3 返回第 3
  • ...等等

Can this be done through String.Format()or are there any functions available to do this?

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

采纳答案by samjudson

This page gives you a complete listing of all custom numerical formatting rules:

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

http://msdn.microsoft.com/en-us/library/0c899ak8.aspx

http://msdn.microsoft.com/en-us/library/0c899ak8.aspx

As you can see, there is nothing in there about ordinals, so it can't be done using String.Format. However its not really that hard to write a function to do it.

如您所见,其中没有关于序数的内容,因此不能使用 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";
    }
}

Update: Technically Ordinals don't exist for <= 0, so I've updated the code above. Also removed the redundant ToString()methods.

更新:从技术上讲,<= 0 不存在序数,所以我更新了上面的代码。也去掉了多余的ToString()方法。

Also note, this is not internationalized. I've no idea what ordinals look like in other languages.

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

回答by Stu

You'll have to roll your own. From the top of my head:

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

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

You can then do

然后你可以做

Console.WriteLine(432.Ordinal());

Edited for 11/12/13 exceptions. I DID say from the top of my head :-)

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

Edited for 1011 -- others have fixed this already, just want to make sure others don't grab this incorrect version.

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

回答by Jesse C. Slicer

I rather liked elements from both Stu's and samjudson's solutions and worked them together into what I think is a usable combo:

我更喜欢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;
        }
    }

回答by Ryan McGeary

While I haven't benchmarked this yet, you should be able to get better performance by avoiding all the conditional case statements.

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

This is java, but a port to C# is trivial:

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

Note, the reduction of conditionals and the use of the array lookup should speed up performance if generating a lot of ordinals in a tight loop. However, I also concede that this isn't as readable as the case statement solution.

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

回答by roomaroo

Remember internationalisation!

记住国际化!

The solutions here only work for English. Things get a lot more complex if you need to support other languages.

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

For example, in Spanish "1st" would be written as "1.o", "1.a", "1.os" or "1.as" depending on whether the thing you're counting is masculine, feminine or plural!

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

So if your software needs to support different languages, try to avoid ordinals.

所以如果你的软件需要支持不同的语言,尽量避免使用序数。

回答by si618

My version of Jesse's version of Stu's and samjudson's versions :)

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

Included unit test to show that the accepted answer is incorrect when number < 1

包含单元测试以显示当数字 < 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());
    }

回答by shawad

Similar to Ryan's solution, but even more basic, I just use a plain array and use the day to look up the correct ordinal:

类似于 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];

I have not had the need, but I would assume you could use a multidimensional array if you wanted to have multiple language support.

我没有这个需求,但我认为如果您想获得多语言支持,您可以使用多维数组。

From what I can remember from my Uni days, this method requires minimal effort from the server.

从我在大学时代所记得的情况来看,这种方法只需要很少的服务器工作量。

回答by Faust

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

回答by Rupert

Another alternative that I used based on all the other suggestions, but requires no special casing:

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

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

回答by AjV Jsy

FWIW, for MS-SQL, this expression will do the job. Keep the first WHEN (WHEN num % 100 IN (11, 12, 13) THEN 'th') as the first one in the list, as this relies upon being tried before the others.

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

For Excel :

对于 Excel:

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

The expression (MOD(A1-11,100)>2)is TRUE (1) for all numbers except any ending in 11,12,13(FALSE = 0). So 2 * RIGHT(A1) * (MOD(A1-11,100)>2) +1)ends up as 1 for 11/12/13, otherwise :
1 evaluates to 3
2 to 5,
3 to 7
others : 9
- and the required 2 characters are selected from "thstndrdth"starting from that position.

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

If you really want to convert that fairly directly to SQL, this worked for me for a handful of test values :

如果您真的想将其直接转换为 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
                )