Java中有没有一种方法可以将整数转换为序数名称?

67

我想要将一个整数转换为序数,例如:

1 -> "First"
2 -> "Second"
3 -> "Third"
...

4
如何在Java中将数字转换为中文大写数字?您可以使用现有的第三方库如 open-source 的 NumberToWord,或者编写自己的方法进行转换。如果您选择编写自己的方法,则需要了解如何处理整数和小数部分,并根据不同的数字位数适当地添加“亿”、“万”等单位词语。 - Felix
1
如果它也能够处理前缀,比如st、nd、rd就更好了。 - Andrew White
1
请注意,如果您希望您的程序在您自己国家之外使用,您应该小心使用可以本地化的解决方案。 - Thorbjørn Ravn Andersen
如果您需要本地化,请查看下面@jfk的答案。 - Testo Testini
13个回答

162
如果你能接受使用“1st”、“2nd”、“3rd”等形式,那么这里有一些简单的代码可以正确处理任何整数:
public static String ordinal(int i) {
    String[] suffixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" };
    switch (i % 100) {
    case 11:
    case 12:
    case 13:
        return i + "th";
    default:
        return i + suffixes[i % 10];

    }
}

这里是一些测试极端情况的代码:

public static void main(String[] args) {
    int[] tests = {0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 20, 21, 22, 23, 24, 100, 101, 102, 103, 104, 111, 112, 113, 114, 1000};
    for (int test : tests) {
        System.out.println(ordinal(test));
    }
}

输出:

0th
1st
2nd
3rd
4th
5th
10th
11th
12th
13th
14th
20th
21st
22nd
23rd
24th
100th
101st
102nd
103rd
104th
111th
112th
113th
114th
1000th

2
这怎么成为被接受的答案了?OP要求将序数转换为单词,而这个答案并没有做到。可能他放弃了这个要求,但仍然有回答可以满足他的要求。 - desgraci
6
不确定是否能成为被采纳的答案,但我想它是最受欢迎的答案,因为它对其他人更有普适性。 - Thunderforge
在Java 17+中,您可以使用switch表达式:return switch (i % 100) { case 11, 12, 13 -> i + "th"; default -> i + suffixes[i % 10]; }; - ATutorMe

47
使用优秀的 ICU4J(也有一个出色的 C 版本),您还可以这样做并获得明文词序数;
RuleBasedNumberFormat nf = new RuleBasedNumberFormat(Locale.UK, RuleBasedNumberFormat.SPELLOUT);
for(int i = 0; i <= 30; i++)
{
    System.out.println(i + " -> "+nf.format(i, "%spellout-ordinal"));
}

例如产生

0 -> zeroth
1 -> first
2 -> second
3 -> third
4 -> fourth
5 -> fifth
6 -> sixth
7 -> seventh
8 -> eighth
9 -> ninth
10 -> tenth
11 -> eleventh
12 -> twelfth
13 -> thirteenth
14 -> fourteenth
15 -> fifteenth
16 -> sixteenth
17 -> seventeenth
18 -> eighteenth
19 -> nineteenth
20 -> twentieth
21 -> twenty-first
22 -> twenty-second
23 -> twenty-third
24 -> twenty-fourth
25 -> twenty-fifth
26 -> twenty-sixth
27 -> twenty-seventh
28 -> twenty-eighth
29 -> twenty-ninth
30 -> thirtieth

10
我认为这应该被选为采纳答案,因为它直接回答了问题。 - TheGT
请注意,对于某些语言(如意大利语),可能不存在%spellout-ordinal,必须在%spellout-ordinal-masculine和%spellout-ordinal-feminine之间选择,有些语言可能有更多的性别。 - Testo Testini
这很好,但是……我对库从本地化角度处理事情的方式并不完全信服。此答案中的示例代码表示它使用英国本地化设置,但对于数字101,382,它给出了“one hundred one thousand three hundred eighty-second”,而我期望它应该是“one hundred and one thousand three hundred and eighty-second”(加上那些“and”)。我认为这在英国更自然。这感觉更像是美国本地化书写单词的方式。 - andrewJames
对于“1st”,“2nd”,“3rd”等,使用RuleBasedNumberFormat.ORDINAL然后nf.format(x) - Raman

21

另一个解决方案

public static String ordinal(int i) {
    int mod100 = i % 100;
    int mod10 = i % 10;
    if(mod10 == 1 && mod100 != 11) {
        return i + "st";
    } else if(mod10 == 2 && mod100 != 12) {
        return i + "nd";
    } else if(mod10 == 3 && mod100 != 13) {
        return i + "rd";
    } else {
        return i + "th";
    }
}

优点:不需要初始化数组(减少垃圾)
缺点:不是一行代码解决的...


19

我已经想出了在Android上以一种非常简单的方式来实现这个问题的方法。您只需要将依赖项添加到appbuild.gradle文件中即可:

implementation "com.ibm.icu:icu4j:53.1"

接下来,创建这个方法:

Kotlin:

fun Number?.getOrdinal(): String? {
    if (this == null) {
        return null
    }

    val format = "{0,ordinal}"

    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        android.icu.text.MessageFormat.format(format, this)
    } else {
        com.ibm.icu.text.MessageFormat.format(format, this)
    }
}

Java:

public static String getNumberOrdinal(Number number) {
        if (number == null) {
            return null;
        }

        String format = "{0,ordinal}";

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return android.icu.text.MessageFormat.format(format, number);
        } else {
            return com.ibm.icu.text.MessageFormat.format(format, number);
        }
    }

那么,您可以像这样简单地使用它:

Kotlin:

val ordinal = 2.getOrdinal()

Java:

String ordinal = getNumberOrdinal(2)

工作原理

从 Android N(API 24) 开始,Android使用 icu.text 替代常规的 java.text (更多信息请参考这里),其中已经包含了国际化实现用于序数数字。因此,解决方案显而易见-将icu4j库添加到项目中,并在 Nougat 以下版本中使用它。


3
重要提示:此方法无法与更新的 ICU4J 版本(至少是 63.1)一起使用;由于 java.util.Locale.Category 不受支持,因此它会在 API 级别<24 上抛出异常。 - soren121
3
对于如此简单的功能来说,增加了太多 APK 的大小,达到了 +9MB。 - oxied
换句话说:android.icu.text.MessageFormat.format("{0,ordinal}", number)。 请注意,使用java.text.MessageFormat.format会引发异常。 对于国际化,这应该是被接受的答案! - Pat Lee

6
在一行中:
public static String ordinal(int i) {
    return i % 100 == 11 || i % 100 == 12 || i % 100 == 13 ? i + "th" : i + new String[]{"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"}[i % 10];
}

1
那可真会让垃圾回收器过得愉快 XD - Ky -
@Supuhstar,这个数组很容易被证明无法离开该方法的作用域,因此编译器和运行时可以对其进行相当重的优化。即使按照我最悲观的预测,这个方法在很短的时间内也不会产生任何垃圾。 - M. Prokhorov
@M.Prokhorov,我更希望解决方案不依赖于JVM实现细节,这些细节可能会在版本和环境之间发生变化,而是依赖于严格定义的语言行为。 - Ky -
@Supuhstar,任何关于Java垃圾回收的讨论都依赖于JVM实现细节。Java仅定义了超出作用域的对象会被垃圾回收。它没有定义任何影响应用程序性能的垃圾对象概念,并且不会因此而避免创建对象的讨论。 - M. Prokhorov
@M.Prokhorov 对,这就是为什么我认为将数组设置为静态变量更可靠,保证在应用程序的整个生命周期内与任何JVM处于范围内,从而保证永远不会被垃圾回收。 - Ky -

3

Bohemians的回答很好,但我建议改进错误处理。使用原始版本的序数,如果提供负整数,则会抛出ArrayIndexOutOfBoundsException异常。我认为下面的版本更清晰。我希望junit也有用,因此不需要视觉检查输出。

public class FormattingUtils {

    /**
     * Return the ordinal of a cardinal number (positive integer) (as per common usage rather than set theory).
     * {@link https://dev59.com/mWw15IYBdhLWcg3wIYO_}
     * 
     * @param i
     * @return
     * @throws {@code IllegalArgumentException}
     */
    public static String ordinal(int i) {
        if (i < 0) {
            throw new IllegalArgumentException("Only +ve integers (cardinals) have an ordinal but " + i + " was supplied");
        }

        String[] sufixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" };
        switch (i % 100) {
        case 11:
        case 12:
        case 13:
            return i + "th";
        default:
            return i + sufixes[i % 10];
        }
    }
}


import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;

public class WhenWeCallFormattingUtils_Ordinal {

    @Test
    public void theEdgeCasesAreCovered() {
        int[] edgeCases = { 0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 20, 21, 22, 23, 24, 100, 101, 102, 103, 104, 111, 112,
                113, 114 };
        String[] expectedResults = { "0th", "1st", "2nd", "3rd", "4th", "5th", "10th", "11th", "12th", "13th", "14th",
                "20th", "21st", "22nd", "23rd", "24th", "100th", "101st", "102nd", "103rd", "104th", "111th", "112th",
                "113th", "114th" };

        for (int i = 0; i < edgeCases.length; i++) {
            assertThat(FormattingUtils.ordinal(edgeCases[i])).isEqualTo(expectedResults[i]);
        }
    }

    @Test(expected = IllegalArgumentException.class)
    public void supplyingANegativeNumberCausesAnIllegalArgumentException() {
        FormattingUtils.ordinal(-1);
    }

}

1
private static String getOrdinalIndicator(int number) {
        int mod = number;
        if (number > 13) {
            mod = number % 10;
        }
        switch (mod) {
        case 1:
            return "st";
        case 2:
            return "nd";
        case 3:
            return "rd";
        default:
            return "th";
        }
    }

1
问题是要返回1时的“First”,11时的“Eleventh”等。您的答案没有实现这一点。 - Stuti Rastogi

0

如果有人正在寻找 Kotlin 扩展 版本:


fun Int.toEnOrdinal(): String {
    val suffixes = arrayListOf("th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th")
    return when (this % 100) {
        11, 12, 13 -> "{$this}th"
        else -> "$this" + suffixes[this % 10]
    }
}

那么你只需要在任何数字上调用此函数即可,例如:

5.toUKOrdinal() --> 第5

21.toUKOrdinal() --> 第21

等等


0

换个角度来看,使用Scala编程

List(1, 2, 3, 4, 5, 10, 11, 12, 13, 14 , 19, 20, 23, 33, 100, 113, 123, 101, 1001, 1011, 1013, 10011) map {
    case a if (a % 10) == 1 && (a % 100) != 11 => a + "-st"
    case b if (b % 10) == 2 && (b % 100) != 12 => b + "-nd"
    case c if (c % 10) == 3 && (c % 100) != 13 => c + "-rd"
    case e                                     => e + "-th"
  }  foreach println

这个问题是关于Java的吗? - Enoobong

0

我有一个又长又复杂的,但是概念很容易理解。

private static void convertMe() {

    Scanner in = new Scanner(System.in);
    try {
        System.out.println("input a number to convert: ");
        int n = in.nextInt();

        String s = String.valueOf(n);
        //System.out.println(s);

        int len = s.length() - 1;
        if (len == 0){
            char lastChar = s.charAt(len);
            if (lastChar == '1'){
                System.out.println(s + "st");
            } else if (lastChar == '2') {
                System.out.println(s + "nd");
            } else if (lastChar == '3') {
                System.out.println(s + "rd");
            } else {
                System.out.println(s + "th");
            }
        } else if (len > 0){
            char lastChar = s.charAt(len);
            char preLastChar = s.charAt(len - 1);
            if (lastChar == '1' && preLastChar != '1'){ //not ...11
                System.out.println(s + "st");
            } else if (lastChar == '2' && preLastChar != '1'){ //not ...12
                System.out.println(s + "nd");
            } else if (lastChar == '3' && preLastChar != '1'){ //not ...13
                System.out.println(s + "rd");
            } else {
                System.out.println(s + "th");
            }
        }


    } catch(InputMismatchException exception){
        System.out.println("invalid input");
    }


}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接