Contar letras en un texto en galés
¿Cómo cuento las letras en Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch?
print(len('Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch'))
Dice 58
Bueno, si fuera tan fácil, no te lo estaría preguntando, ¿verdad?
Wikipedia dice (https://en.wikipedia.org/wiki/Llanfairpwllgwyngyll#Placename_and_toponymy)
La forma larga del nombre es el topónimo más largo del Reino Unido y uno de los más largos del mundo con 58 caracteres (51 "letras" ya que "ch" y "ll" son dígrafos y se tratan como letras individuales en el Idioma galés).
Así que quiero contar eso y obtener la respuesta 51.
Okey dokey.
print(len(['Ll','a','n','f','a','i','r','p','w','ll','g','w','y','n','g','y','ll','g','o','g','e','r','y','ch','w','y','r','n','d','r','o','b','w','ll','ll','a','n','t','y','s','i','l','i','o','g','o','g','o','g','o','ch']))
51
Sí, pero eso es trampa, obviamente quiero usar la palabra como entrada, no la lista.
Wikipedia también dice que los dígrafos en galés son ch, dd, ff, ng, ll, ph, rh, th
https://en.wikipedia.org/wiki/Welsh_orthography#Digraphs
Así que nos vamos. Sumamos la longitud y luego quitemos el conteo doble.
word='Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch'
count=len(word)
print('starting with count of',count)
for index in range(len(word)-1):
substring=word[index]+word[index+1]
if substring.lower() in ['ch','dd','ff','ng','ll','ph','rh','th']:
print('taking off double counting of',substring)
count=count-1
print(count)
Esto me lleva tan lejos
starting with count of 58
taking off double counting of Ll
taking off double counting of ll
taking off double counting of ng
taking off double counting of ll
taking off double counting of ch
taking off double counting of ll
taking off double counting of ll
taking off double counting of ll
taking off double counting of ch
49
Parece que he restado demasiados entonces. Se supone que debo obtener 51. Ahora, un problema es que con el llll
ha encontrado 3 ll
sy sacado tres en lugar de dos. Así que habrá que arreglarlo. (No debe superponerse).
Y luego hay otro problema. El ng
. Wikipedia no dijo nada acerca de que había una letra "ng" en el nombre, pero aparece como uno de los dígrafos en la página que cité anteriormente.
Wikipedia nos da más pistas aquí: "puede ser necesaria información adicional para distinguir un dígrafo genuino de una yuxtaposición de letras" . Y da el ejemplo de " llongyfarch " donde ng es simplemente una "yuxtaposición de letras", y " llong " donde es un dígrafo.
Así que parece que 'Llanfairpwllgwy ng yllgogerychwyrndrobwllllantysiliogogogoch' es una de esas palabras donde la -ng- es solo una "yuxtaposición de letras".
Y, obviamente, no hay forma de que la computadora pueda saber eso. Entonces voy a tener que darle esa "información adicional" de la que habla Wikipedia.
De todos modos, decidí buscar en un diccionario en línea. http://geiriadur.ac.uk/gpc/gpc.htmly puede ver que si busca llongyfarch (el ejemplo de Wikipedia que tiene la "yuxtaposición de letras") lo muestra con una línea vertical entre la ny la g, pero si busca "llong", entonces no hacer esto.


Así que decidí que está bien, lo que tenemos que hacer es proporcionar la información adicional colocando un |
en la cadena de entrada como lo hace en el diccionario, solo para que el algoritmo sepa que el ng
bit es realmente dos letras. Pero, obviamente, no quiero que el |
mismo se cuente como una letra.
Entonces ahora tengo estas entradas:
word='llong'
ANSWER NEEDS TO BE 3 (ll o ng)
word='llon|gyfarch'
ANSWER NEEDS TO BE 9 (ll o n g y f a r ch)
word='Llanfairpwllgwyn|gyllgogerychwyrndrobwllllantysiliogogogoch'
ANSWER NEEDS TO BE 51 (Ll a n f a i r p w ll g w y n g y ll g o g e r y ch w y r n d r o b w ll ll a n t y s i l i o g o g o g o ch)
y aún esta lista de dígrafos:
['ch','dd','ff','ng','ll','ph','rh','th']
y las reglas van a ser:
ignorar caso
si ves un dígrafo, cuéntalo como 1
trabajar de izquierda a derecha para que
llll
seall
+ll
, nol
+ll
+l
si ves un
|
no lo cuentes, pero no puedes ignorarlo por completo, está ahí para dejar deng
ser un dígrafo
y quiero que lo cuente como 51 y que lo haga por las razones correctas, no solo por casualidad.
Ahora obtengo 51, pero lo está cambiando porque está contando |
como una letra (1 demasiado alto), y luego está quitando una de más con el llll
(1 demasiado bajo) - ERRORES CANCELAR
Se está haciendo llong
bien (3).
Se está llon|gyfarch
equivocando (10) - contando la |
espalda
¿Cómo puedo solucionarlo de la manera correcta?
Respuestas
Como muchos problemas relacionados con las cadenas, esto se puede hacer de una manera sencilla con una expresión regular.
>>> word = 'Llanfairpwllgwyn|gyllgogerychwyrndrobwllllantysiliogogogoch'
>>> import re
>>> pattern = re.compile(r'ch|dd|ff|ng|ll|ph|rh|th|[^\W\d_]', flags=re.IGNORECASE)
>>> len(pattern.findall(word))
51
La clase de caracteres [^\W\d_]
(a partir de aquí ) coincide con caracteres de palabra que no son dígitos ni guiones bajos, es decir, letras, incluidos los que tienen signos diacríticos.
Puede obtener la longitud reemplazando todas las letras dobles con un .
(o cualquier otro carácter, ?
estaría bien) y midiendo la longitud de la cadena resultante (restando la cantidad de |
):
def get_length(name):
name = name.lower()
doubles = ['ch', 'dd', 'ff', 'ng', 'll', 'ph', 'rh', 'th']
for double in doubles:
name = name.replace(double, '.')
return len(name) - name.count('|')
name = 'Llanfairpwllgwyn|gyllgogerychwyrndrobwllllantysiliogogogoch'
print(get_length(name))
>>> 51
- Paso a través de la cadena letra por letra
- Si está en el índice n y y s [n: n + 2] es un dígrafo, agregue o incremente un diccionario con el dígrafo como clave, e incremente el índice en 1 también para no comenzar con el segundo dígrafo personaje. Si no es un dígrafo, simplemente agregue o incremente la letra al dict y vaya a la siguiente letra.
- Si ve el | personaje, no lo cuentes, solo salta.
- Y no olvides usar minúsculas.
Cuando hayas visto todas las letras, el ciclo termina y agregas todos los conteos en el dict.
Aquí está mi código, funciona en sus tres ejemplos:
from collections import defaultdict
digraphs=['ch','dd','ff','ng','ll','ph','rh','th']
breakchars=['|']
def welshcount(word):
word = word.lower()
index = 0
counts = defaultdict(int) # keys start at 0 if not already present
while index < len(word):
if word[index:index+2] in digraphs:
counts[word[index:index+2]] += 1
index += 1
elif word[index] in breakchars:
pass # in case you want to do something here later
else: # plain old letter
counts[word[index]] += 1
index += 1
return sum(counts.values())
word1='llong'
#ANSWER NEEDS TO BE 3 (ll o ng)
word2='llon|gyfarch'
#ANSWER NEEDS TO BE 9 (ll o n g y f a r ch)
word3='Llanfairpwllgwyn|gyllgogerychwyrndrobwllllantysiliogogogoch'
#ANSWER NEEDS TO BE 51 (Ll a n f a i r p w ll g w y n g y ll g o g e r y ch w y r n d r o b w ll ll a n t y s i l i o g o g o g o ch)
print(welshcount(word1))
print(welshcount(word2))
print(welshcount(word3))
Puede usar un carácter Combining Grapheme Joiner (+ u034F) para unir las letras y luego tomar el recuento de caracteres y quitar el número de estos ensambladores * 2.
http://www.comisiynyddygymraeg.cymru/English/Part%203/10%20Locales%20alphabets%20and%20character%20sets/10.2%20Alphabets/Pages/10-2-4-Combining-Grapheme-Joiner.aspx
El comisionado de la lengua galesa también aborda el problema aquí: http://www.comisiynyddygymraeg.cymru/English/Part%203/10%20Locales%20alphabets%20and%20character%20sets/10.2%20Alphabets/Pages/10-2-1-Character-vs--letter-counts.aspx