Reemplazo de números ordinales

Actualmente estoy buscando la forma de reemplazar palabras como primero, segundo, tercero, … con la representación del número ordinal apropiado (1º, 2º, 3º). He estado buscando en Google durante la última semana y no encontré ninguna herramienta estándar útil o ninguna función de NLTK.

Entonces, ¿hay alguna o debo escribir algunas expresiones regulares manualmente?

Gracias por cualquier consejo

Aquí hay una solución concisa tomada de Gareth en codegolf :

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

Funciona en cualquier número:

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

Para Python 3.4+, se necesita math.floor :

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

Qué tal esto:

 suf = lambda n: "%d%s"%(n,{1:"st",2:"nd",3:"rd"}.get(n if n<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'] 

La respuesta aceptada a una pregunta anterior tiene un algoritmo para la mitad de esto: convierte "first" en 1 . Para ir de allí a "1st" , haz algo como:

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

Esto solo funciona para los números 0-19.

Quería usar ordinales para un proyecto mío y, después de algunos prototipos, creo que este método, aunque no pequeño, funcionará con cualquier entero positivo, sí, con cualquier entero .

Funciona al determinar si el número está por encima o por debajo de 20, si el número está por debajo de 20, convertirá el int 1 en la cadena 1st, 2, 2nd; 3, 3; y el rest tendrá “st” añadido.

Para los números de más de 20 tomará el último y el segundo hasta los últimos dígitos, a los que he llamado las decenas y la unidad respectivamente y los pruebo para ver qué agregar al número.

Por cierto, esto está en python, así que no estoy seguro de que otros idiomas puedan encontrar el último o segundo dígito del último en una cadena si lo hacen deberían traducirse con bastante facilidad.

 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 

Llamé a la función “o” para facilitar su uso y se me puede llamar importando el nombre del archivo que llamé “ordinal” mediante ordinal de importación y luego ordinal.o (número).

Déjame saber lo que piensas: D

Me encontré haciendo algo similar, necesitando convertir direcciones con números ordinales (‘Third St’) a un formato que un geocodificador podría comprender (‘3rd St’). Si bien esto no es muy elegante, una solución rápida y sucia es usar inflect.py para generar un diccionario para la traducción.

inflect.py tiene una función number_to_words() , que cambiará un número (por ejemplo, 2 ) a su forma de palabra (por ejemplo, 'two' ). Además, hay una función ordinal() que toma cualquier número (número o forma de palabra) y la convierte en su forma ordinal (por ejemplo, 4 -> fourth , six -> sixth ). Ninguno de ellos, por su cuenta, hace lo que está buscando, pero juntos pueden usarlos para generar un diccionario para traducir cualquier palabra de número ordinal (dentro de un rango razonable) a su respectivo número ordinal. Echar un vistazo:

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

Si está dispuesto a dedicar algo de tiempo, podría ser posible examinar el funcionamiento interno de inflect.py en ambas funciones y crear su propio código para hacer esto dinámicamente (no he intentado hacerlo).

Otra solución es la biblioteca num2words ( pip | github ). Especialmente ofrece diferentes idiomas, por lo que la localización / internacionalización (también conocida como l10n / i18n) es una tarea fácil.

El uso es fácil después de que lo instaló con pip install num2words :

 from num2words import num2words # english is default num2words(4458, to="ordinal_num") '4458rd' # examples for other languages num2words(4458, lang="en", to="ordinal_num") '4458rd' 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' 

Prima:

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

Si no desea obtener una dependencia adicional de una biblioteca externa (como lo sugiere el Luckydonald ), pero tampoco desea que el futuro mantenedor del código lo persiga y lo mate (porque usó el código de golf en la producción) Entonces aquí hay una variante corta pero mantenible:

 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) suffix = ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)] if 11 <= (n % 100) <= 13: suffix = 'th' return str(n) + suffix 

Si usas django, podrías hacer:

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

(o use ordinal en una plantilla de django como el filtro de plantilla que pretendía ser, aunque llamarlo así desde el código de Python también funciona)

Si no usas django, podrías robar su implementación, lo cual es muy bueno.

esta función funciona bien para cada número n . Si n es negativo, se convierte en positivo. Si n no es un entero, se convierte en entero.

 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 

Si no desea importar un módulo externo y prefiere una solución de una línea, lo siguiente es probablemente (un poco) más legible que la respuesta aceptada:

 def suffix(i): return {1:"st", 2:"nd", 3:"rd"}.get(i%10*(i%100 not in [11,12,13]), "th")) 

Utiliza el diccionario .get , como lo sugiere https://codereview.stackexchange.com/a/41300/90593 y https://stackoverflow.com/a/36977549/5069869 .

Hice uso de la multiplicación con un booleano para manejar los casos especiales (11,12,13) ​​sin tener que iniciar un bloque if. Si la condición (i%100 not in [11,12,13]) evalúa como False , el número entero es 0 y obtenemos el caso ‘th’ predeterminado.

Esto puede manejar cualquier número de longitud, las excepciones para … # 11 a … # 13 y enteros negativos.

 def ith(i):return(('th'*(10<(abs(i)%100)<14))+['st','nd','rd',*['th']*7][(abs(i)-1)%10])[0:2] 

Sugiero usar ith () como nombre para evitar anular la orden incorporada ().

 # test routine for i in range(-200,200): print(i,ith(i)) 

Nota: Probado con Python 3.6; La función abs () estaba disponible sin incluir explícitamente un módulo matemático.

Esta es una opción alternativa utilizando el paquete num2words.

 >>> from num2words import num2words >>> num2words(42, to='ordinal_num') '42nd' 

Prueba esto

 import sys a = int(sys.argv[1]) for i in range(1,a+1): j = i if(j%100 == 11 or j%100 == 12 or j%100 == 13): print("%dth Hello"%(j)) continue i %= 10 if ((j%10 == 1) and ((i%10 != 0) or (i%10 != 1))): print("%dst Hello"%(j)) elif ((j%10 == 2) and ((i%10 != 0) or (i%10 != 1))): print("%dnd Hello"%(j)) elif ((j%10 == 3) and ((i%10 != 0) or (i%10 != 1))): print("%drd Hello"%(j)) else: print("%dth Hello"%(j)) 

Aquí hay una solución más complicada que acabo de escribir que tiene en cuenta los ordinales compuestos. Así funciona desde el first hasta nine hundred and ninety ninth . Lo necesitaba para convertir cadenas de nombres de calles a los números ordinales:

 import re from collections import OrderedDict ONETHS = { 'first': '1ST', 'second': '2ND', 'third': '3RD', 'fourth': '4TH', 'fifth': '5TH', 'sixth': '6TH', 'seventh': '7TH', 'eighth': '8TH', 'ninth': '9TH' } TEENTHS = { 'tenth': '10TH', 'eleventh': '11TH', 'twelfth': '12TH', 'thirteenth': '13TH', 'fourteenth': '14TH', 'fifteenth': '15TH', 'sixteenth': '16TH', 'seventeenth': '17TH', 'eighteenth': '18TH', 'nineteenth': '19TH' } TENTHS = { 'twentieth': '20TH', 'thirtieth': '30TH', 'fortieth': '40TH', 'fiftieth': '50TH', 'sixtieth': '60TH', 'seventieth': '70TH', 'eightieth': '80TH', 'ninetieth': '90TH', } HUNDREDTH = {'hundredth': '100TH'} # HUNDREDTH not s ONES = {'one': '1', 'two': '2', 'three': '3', 'four': '4', 'five': '5', 'six': '6', 'seven': '7', 'eight': '8', 'nine': '9'} TENS = {'twenty': '20', 'thirty': '30', 'forty': '40', 'fifty': '50', 'sixty': '60', 'seventy': '70', 'eighty': '80', 'ninety': '90'} HUNDRED = {'hundred': '100'} # Used below for ALL_ORDINALS ALL_THS = {} ALL_THS.update(ONETHS) ALL_THS.update(TEENTHS) ALL_THS.update(TENTHS) ALL_THS.update(HUNDREDTH) ALL_ORDINALS = OrderedDict() ALL_ORDINALS.update(ALL_THS) ALL_ORDINALS.update(TENS) ALL_ORDINALS.update(HUNDRED) ALL_ORDINALS.update(ONES) def split_ordinal_word(word): ordinals = [] if not word: return ordinals for key, value in ALL_ORDINALS.items(): if word.startswith(key): ordinals.append(key) ordinals += split_ordinal_word(word[len(key):]) break return ordinals def get_ordinals(s): ordinals, start, end = [], [], [] s = s.strip().replace('-', ' ').replace('and', '').lower() s = re.sub(' +',' ', s) # Replace multiple spaces with a single space s = s.split(' ') for word in s: found_ordinals = split_ordinal_word(word) if found_ordinals: ordinals += found_ordinals else: # else if word, for covering blanks if ordinals: # Already have some ordinals end.append(word) else: start.append(word) return start, ordinals, end def detect_ordinal_pattern(ordinals): ordinal_length = len(ordinals) ordinal_string = '' # ' '.join(ordinals) if ordinal_length == 1: ordinal_string = ALL_ORDINALS[ordinals[0]] elif ordinal_length == 2: if ordinals[0] in ONES.keys() and ordinals[1] in HUNDREDTH.keys(): ordinal_string = ONES[ordinals[0]] + '00TH' elif ordinals[0] in HUNDRED.keys() and ordinals[1] in ONETHS.keys(): ordinal_string = HUNDRED[ordinals[0]][:-1] + ONETHS[ordinals[1]] elif ordinals[0] in TENS.keys() and ordinals[1] in ONETHS.keys(): ordinal_string = TENS[ordinals[0]][0] + ONETHS[ordinals[1]] elif ordinal_length == 3: if ordinals[0] in HUNDRED.keys() and ordinals[1] in TENS.keys() and ordinals[2] in ONETHS.keys(): ordinal_string = HUNDRED[ordinals[0]][0] + TENS[ordinals[1]][0] + ONETHS[ordinals[2]] elif ordinals[0] in ONES.keys() and ordinals[1] in HUNDRED.keys() and ordinals[2] in ALL_THS.keys(): ordinal_string = ONES[ordinals[0]] + ALL_THS[ordinals[2]] elif ordinal_length == 4: if ordinals[0] in ONES.keys() and ordinals[1] in HUNDRED.keys() and ordinals[2] in TENS.keys() and \ ordinals[3] in ONETHS.keys(): ordinal_string = ONES[ordinals[0]] + TENS[ordinals[2]][0] + ONETHS[ordinals[3]] return ordinal_string 

Y aquí hay algunos ejemplos de uso:

 # s = '32 one hundred and forty-third st toronto, on' #s = '32 forty-third st toronto, on' #s = '32 one-hundredth st toronto, on' #s = '32 hundred and third st toronto, on' #s = '32 hundred and thirty first st toronto, on' # s = '32 nine hundred and twenty third st toronto, on' #s = '32 nine hundred and ninety ninth st toronto, on' s = '32 sixty sixth toronto, on' st, ords, en = get_ordinals(s) print st, detect_ordinal_pattern(ords), en 

Saludo el código lambda de Gareth. Muy elegante. Aunque solo entiendo a medias cómo funciona. Así que traté de deconstruirlo y se me ocurrió esto:

 def ordinal(integer): int_to_string = str(integer) if int_to_string == '1' or int_to_string == '-1': print int_to_string+'st' return int_to_string+'st'; elif int_to_string == '2' or int_to_string == '-2': print int_to_string+'nd' return int_to_string+'nd'; elif int_to_string == '3' or int_to_string == '-3': print int_to_string+'rd' return int_to_string+'rd'; elif int_to_string[-1] == '1' and int_to_string[-2] != '1': print int_to_string+'st' return int_to_string+'st'; elif int_to_string[-1] == '2' and int_to_string[-2] != '1': print int_to_string+'nd' return int_to_string+'nd'; elif int_to_string[-1] == '3' and int_to_string[-2] != '1': print int_to_string+'rd' return int_to_string+'rd'; else: print int_to_string+'th' return int_to_string+'th'; >>> print [ordinal(n) for n in range(1,25)] 1st 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th', '11th', '12th', '13th', '14th', '15th', '16th', '17th', '18th', '19th', '20th', '21st', '22nd', '23rd', '24th'] 

Código de Gareth expresado usando el moderno .format ()

 ordinal = lambda n: "{}{}".format(n,"tsnrhtdd"[(n/10%10!=1)*(n%10<4)*n%10::4]) 

Hay una función ordinal en humanizar.

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')]