序数替换

89

我目前正在寻找一种方法,用适当的序数表示法(1st, 2nd, 3rd)替换像first、second、third这样的单词。

我已经谷歌了一个星期,没有发现任何有用的标准工具或来自NLTK的任何函数。

所以是否有任何工具可用,还是我应该手动编写一些正则表达式呢?

感谢任何建议。


如果你找不到一个合适的,自己写一个也不应该太难,因为数字格式非常严格。像 pyparsing 这样的工具会让它变得更容易! - Katriel
3
哇,大多数答案并没有回答问题('first''1st'),而是另一个问题(11st)。 - törzsmókus
1
除此之外,这里有很多重复。 - Karl Knechtel
16个回答

155

number-parser可以将序数词(例如“first”,“second”等)解析为整数。

from number_parser import parse_ordinal
n = parse_ordinal("first")

要将整数转换为“1st”、“2nd”等形式,可以使用以下代码:

def ordinal(n: int):
    if 11 <= (n % 100) <= 13:
        suffix = 'th'
    else:
        suffix = ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)]
    return str(n) + suffix

以下是更加精简但不太易读的版本(摘自codegolf上的Gareth回答):

ordinal = lambda n: "%d%s" % (n,"tsnrhtdd"[(n//10%10!=1)*(n%10<4)*n%10::4])

这适用于任何数字:

print([ordinal(n) for n in range(1,32)])

['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th',
 '11th', '12th', '13th', '14th', '15th', '16th', '17th', '18th', '19th',
 '20th', '21st', '22nd', '23rd', '24th', '25th', '26th', '27th', '28th',
 '29th', '30th', '31st']

7
这段代码似乎在 Python3.4 中已经无法使用,例如 ordinal(13) 返回 '13rd'。原因不明。使用 str(n) + {1: 'st', 2: 'nd', 3: 'rd'}.get(4 if 10 <= n % 100 < 20 else n % 10, "th") 可以解决这个问题。请注意不要改变原文的意思。 - Brett DiDonato
2
@BrettDiDonato n/10 需要使用整数除法 /,这在 Python 2 和 3 之间有所改变。 - Sp3000
36
可爱,但是得了吧,那只是难看。 - Wells
21
在Python3中,你可以使用 // 进行整数除法: ordinal = lambda n: "%d%s" % (n,"tsnrhtdd"[(n//10%10!=1)*(n%10<4)*n%10::4]) 这段代码是一个用于生成序数词的lambda函数,它可以将阿拉伯数字转换为相应的英文序数词。 - Xerion
9
我正在努力克制自己使用这个强烈的冲动。 - Mateen Ulhaq
显示剩余6条评论

56

如果您不想引入外部库的其他依赖项(如luckydonald建议的),但也不希望未来代码维护者追杀并杀死您(因为您在生产中使用了golfed code),那么这里有一个简短但易于维护的变体:

def make_ordinal(n):
    '''
    Convert an integer into its ordinal representation::

        make_ordinal(0)   => '0th'
        make_ordinal(3)   => '3rd'
        make_ordinal(122) => '122nd'
        make_ordinal(213) => '213th'
    '''
    n = int(n)
    if 11 <= (n % 100) <= 13:
        suffix = 'th'
    else:
        suffix = ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)]
    return str(n) + suffix

1
这太棒了 :) - Ye Lin Aung
完美地加入了一行lambda函数:lambda n: "".join([str(n), ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)] if not 11 <= n <= 13 else "th"])例如:dt_tr_fn = lambda n: "".join([str(n), ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)] if not 11 <= n <= 13 else "th"]) [dt for dt in map(dt_tr_fn, range(1,32))]@ye-lin-aung - Doogle
1
@Doogle 我认为你的代码应该写成 11 <= (n % 100) <= 13 而不是只有 n,否则它将无法通过,例如 112 - Florian Brucker
为什么不将第一个后缀赋值移到else块中,以避免不必要的评估? - Taylor Vance
@TaylorVance:好主意,已更新代码。 - Florian Brucker
显示剩余2条评论

20

这个怎么样:

suf = lambda n: "%d%s"%(n,{1:"st",2:"nd",3:"rd"}.get(n%100 if (n%100)<20 else n%10,"th"))
print [suf(n) for n in xrange(1,32)]

['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th',
 '11th', '12th', '13th', '14th', '15th', '16th', '17th', '18th', '19th',
 '20th', '21st', '22nd', '23rd', '24th', '25th', '26th', '27th', '28th',
 '29th', '30th', '31st']

4
我喜欢这个版本,更易读。但是对于n>100是否有效呢? - Xerion
1
@Xerion,我认为加一个n%100 < 20就可以了,对吧?"%d%s"%(n,{1:"st",2:"nd",3:"rd"}.get(n if (n % 100)<20 else n%10,"th")) - Sreenikethan I
1
可以使用以下代码进行小修正:suf = lambda n: "%d%s"%(n,{1:"st",2:"nd",3:"rd"}.get(n%100 if n%100<20 else n%10,"th")) - Konstantin Glukhov
不错,但它并没有回答问题('first''1st'),而是提出了另一个问题(1'1st')。 - törzsmókus

13
另一种将数字格式化为“1th”、“2nd”、“3rd”等的解决方案是使用pip | github 中的num2words库。它特别提供了不同的语言,因此本地化/国际化(也称为l10n/i18n)非常简单。在使用pip install num2words安装后,使用很容易:
from num2words import num2words
# english is default
num2words(4458, to="ordinal_num")
'4458th'

# examples for other languages
num2words(4458, lang="en", to="ordinal_num")
'4458th'

num2words(4458, lang="es", to="ordinal_num")
'4458º'

num2words(4458, lang="de", to="ordinal_num")
'4458.'

num2words(4458, lang="id", to="ordinal_num")
'ke-4458'

奖励:

num2words(4458, lang="en", to="ordinal")
'four thousand, four hundred and fifty-eighth'

如果你需要将单词"first"、"second"、"third"等解析为数字1、2、3等(正如问题中所要求的),你可以使用number-parser库(pip | github)来完成:
from number_parser import parse_ordinal
parse_ordinal("twenty third")
23

请注意,此时此刻它仅支持英语、印地语、西班牙语、乌克兰语和俄语。


为什么第一个例子显示“4458rd”?不应该是“4458th”吗? - numbermaniac
1
@numbermaniac(用户名检查通过),你是对的。那就是它给出的输出。我不知道它怎么会在我的答案中变成“4458rd”。 - luckydonald
不错,但它没有回答问题('first''1st'),而是解决另一个问题(1'1st')。 - törzsmókus
虽然这可能不是大多数来自google.com的人的用例——实际上花了4年时间才有人注意到——我现在也包含了这部分。 - luckydonald

10

之前的问题的被采纳答案提供了一种算法,可以将"first"转换为1。要从这里转换为"1st",可以进行如下操作:

suffixes = ["th", "st", "nd", "rd", ] + ["th"] * 16
suffixed_num = str(num) + suffixes[num % 100]

这仅适用于0-19的数字。


如果我理解正确的话,那么我需要将所有值写入字典中,例如 {'first':'1', 'second':'2', 'third':'3', 'fourth':'4',...},是吗? - skornos
@skornos 是的。一般而言,如果没有这样的字典是无法做到的。但是通过足够多的谷歌搜索,你很可能会发现有人已经为你完成了繁琐的工作。否则,如果你最终不得不自己做,你可以通过设置字典为{'first': '1st'}等来避免使用 suffixes 列表。 - lvc
其中极少数是正确的答案!大多数其他人并没有回答问题('first''1st'),而是提出了另一个问题(1'1st')。 - törzsmókus

9
我发现自己需要做类似的事情,将带序数词(如'Third St')的地址转换为地理编码器可以理解的格式('3rd St')。虽然这不太优雅,但一个快速而粗略的解决方案是使用inflect.py生成翻译字典。
inflect.py有一个number_to_words()函数,可将数字(如2)转换为其单词形式(如'two')。另外,还有一个ordinal()函数,它将任何数字(数字符号或单词形式)转换为其序数形式(如4->fourthsix->sixth)。这两个函数本身都不能实现您想要的功能,但结合起来可以生成一个字典来将提供的序数-数字-单词(在合理范围内)翻译成相应的数字序数。看一下:
>>> import inflect
>>> p = inflect.engine()
>>> word_to_number_mapping = {}
>>>
>>> for i in range(1, 100):
...     word_form = p.number_to_words(i)  # 1 -> 'one'
...     ordinal_word = p.ordinal(word_form)  # 'one' -> 'first'
...     ordinal_number = p.ordinal(i)  # 1 -> '1st'
...     word_to_number_mapping[ordinal_word] = ordinal_number  # 'first': '1st'
...
>>> print word_to_number_mapping['sixth']
6th
>>> print word_to_number_mapping['eleventh']
11th
>>> print word_to_number_mapping['forty-third']
43rd

如果你愿意花些时间,可能可以检查inflect.py的两个函数的内部工作,并构建自己的代码来动态地完成这个任务(我没有尝试过这样做)。

其中极少数是正确的答案!大多数其他人并没有回答问题('first''1st'),而是提出了另一个问题(1'1st')。 - törzsmókus

6

我想在我的一个项目中使用序数词,在尝试了几个原型之后,我认为这种方法虽然不小,但适用于任何正整数,是的任何整数

它的工作原理是确定数字是大于还是小于20,如果数字小于20,它将把int 1转换为字符串1st,2转换为2nd;3转换为3rd;其余的数字后面加上"st"。

对于超过20的数字,它将取最后两位数,分别称为十位和个位,并测试它们以查看要添加到数字的内容。

顺便说一下,这是在Python中完成的,所以我不确定其他语言是否能够在字符串中找到最后或倒数第二个数字,如果可以的话,它应该很容易翻译。

def o(numb):
    if numb < 20: #determining suffix for < 20
        if numb == 1: 
            suffix = 'st'
        elif numb == 2:
            suffix = 'nd'
        elif numb == 3:
            suffix = 'rd'
        else:
            suffix = 'th'  
    else:   #determining suffix for > 20
        tens = str(numb)
        tens = tens[-2]
        unit = str(numb)
        unit = unit[-1]
        if tens == "1":
           suffix = "th"
        else:
            if unit == "1": 
                suffix = 'st'
            elif unit == "2":
                suffix = 'nd'
            elif unit == "3":
                suffix = 'rd'
            else:
                suffix = 'th'
    return str(numb)+ suffix

我为了方便使用将该函数命名为“o”,可以通过导入文件名“ordinal”并调用ordinal.o(number)来调用该函数。
请告诉我您的想法:D

5

如果使用Django,您可以执行以下操作:

from django.contrib.humanize.templatetags.humanize import ordinal
var = ordinal(number)

如果您使用Django模板,可以使用序数作为模板过滤器,尽管从Python代码中这样调用也可以。

如果不使用Django,您可以借鉴他们的实现,非常整洁。


不错,但它并没有回答问题('first' → '1st'),而是解决了另一个问题(1 → '1st')。 - törzsmókus

4

humanize中有一个序数函数。

pip install humanize

>>> [(x, humanize.ordinal(x)) for x in (1, 2, 3, 4, 20, 21, 22, 23, 24, 100, 101,
...                                     102, 103, 113, -1, 0, 1.2, 13.6)]
[(1, '1st'), (2, '2nd'), (3, '3rd'), (4, '4th'), (20, '20th'), (21, '21st'),
 (22, '22nd'), (23, '23rd'), (24, '24th'), (100, '100th'), (101, '101st'),
 (102, '102nd'), (103, '103rd'), (113, '113th'), (-1, '-1th'), (0, '0th'),
 (1.2, '1st'), (13.6, '13th')]


不错,但它并没有回答问题('first''1st'),而是提出了另一个问题(1'1st')。 - törzsmókus

2

这个函数适用于每个数字n。如果n是负数,它将被转换为正数。如果n不是整数,它将被转换为整数。

def ordinal( n ):

    suffix = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th']

    if n < 0:
        n *= -1

    n = int(n)

    if n % 100 in (11,12,13):
        s = 'th'
    else:
        s = suffix[n % 10]

    return str(n) + s

这是一种叫做“死灵术”的行为,因为这个话题已经死了一段时间了,但是...这是很好的“死灵术”。没有可爱的代码,非常易读,容易理解。不错! - chris
不错,但它没有回答问题('first''1st'),而是解决了另一个问题(1'1st')。 - törzsmókus

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