0

Estoy un poco atascada y es por falta de conocimientos. He estado buscando por la web pero no doy con lo que busco. Necesito crean una función en python que me cree un espacio después de (,), (.), (;), (:), que no permite dos espacios entre palabras y que si encuentra 4 espacios me cree un tabulador. De momento he llegado a esto, sin resultados...

def correcciones(texto):
    for c in texto:
        if c.endswith('.'):
            c = c.endswith('. ')
        elif c.endswith(':'):
            c = c.endswith(': ')
        elif c.endswith(';'):
            c = c.endswith('; ')
        elif c.endswith(','):
            c = c.endswith('; ')
        elif c.endswith('  '):
            c = c.endswith(' ')
        elif c.endswith('    '):
            c = c.endswith('.\t')
        else:
            pass

    return c
Patricio Moracho
  • 54,367
  • 12
  • 35
  • 68
Eva Rentero
  • 57
  • 1
  • 1
  • 7
  • 1
    Aparte de que `endswith()` no es adecuado, pues sólo mira el final de la cadena, el verdadero problema es que, entiendo, si un punto ya tenía un espacio detrás, entonces no habría que añadírselo, y el "doble espacio" no está permitido entre palabras ¿pero sí en otros lugares? ¿Y si hay tres espacios? ¿O si hay seis, deben juntarse los cuatro primeros para hacer un TAB y los dos siguientes comprimirse en uno? ¿O desartarse? Debes especificar más claramente el comportamiento ante cada posible caso. Y sospecho que la solución acabará usando expresiones regulares – abulafia Jan 02 '19 at 13:43
  • Arrea... pues es que no nos especifican tanto, tan solo que no están permitidos dos espacios seguidos (que entiendo que es entre cualquier elemento), y que si hay cuatro espacios seguidos (después de un punto imagino que querrá decir) se convierta en un tabulador. Entiendo que si hay seis se consideraría tabulador porque excede de tres... pero también existe la posibilidad de que existan seis espacios entre dos palabras por error...esto ultimo entraría en la posibilidad de "dos espacios entre palabras no están permitidos". Qué lio – Eva Rentero Jan 02 '19 at 13:49
  • Yo creo que la interpretación más razonable posible sería: cada cuatro espacios al inicio de una línea se convierten en TAB (si hay ocho, generaría dos TAB, si hay 6 generaría un TAB y dos espacios). Si en cambio aparecen por en medio de dos palabras cualquier número de espacios, se "colapsan" en uno. Después de un signo de puntuación debe haber un espacio (si hay más se colapsan a uno, si hay cero se inserta uno). Imagino que si el signo de puntuación está al final de una línea, no habría que meter espacio en ese caso. Todo esto me parece razonable, ¿no puedes consultar a quien te lo pide? – abulafia Jan 02 '19 at 13:54
  • Sí, lo acabo de hacer... le he expuesto en cierta manera lo que hemos visto, y sí, busca lo que has mencionado (reconozco que sus instrucciones son de aquella manera, y mas para gente que aun no tenemos ese razonamiento de millones de posibilidades) – Eva Rentero Jan 02 '19 at 14:09

2 Answers2

1

El siguiente código implementa tus especificaciones, concretando un poco más los casos que estaban poco especificados. En concreto, lo que implementa es:

  • Las secuencias de espacios al inicio de una línea se cambian por tabuladores, de modo que cada 4 espacios genera un tabulador (y los espacios sobrantes se conservan)
  • Las secuencias de espacios entre palabras se cambian a un solo espacio. Se considera "palabra" cualquier secuencia de símbolos que no sea un "blanco" (espacio, tabulador o retorno de carro)
  • Tras un signo de puntuación, si aparece pegado algo que no sea un "blanco", se debe insertar un espacio. Se consideran signos de puntuación cualquier combinación de ".,;:" Por tanto "..." también sería un signo de puntuación (uno solo) de modo que "Digo...esto" debe ser convertido en "Digo... esto", y no en "Digo. . . esto"

La siguiente función hace todo esto. En realidad no lo he probado con montones de casos, sino sólo con el ejemplo que después pondré. Si das con algún caso en que no se comporte como esperaba, plantéalo en un comentario.

import re


def correcciones(lineas):
  resultado = []
  for linea in lineas:

    # Reemplazar cada 4 espacios al inicio de línea por 1 TAB
    tabs = 0
    while linea.startswith("   "):
      tabs+=1
      linea = linea[4:]
    linea = "\t"*tabs + linea

    # Reemplazar varios espacios entre palabras por uno solo
    linea = re.sub(r"(\S) {2,}", r"\1 ", linea)

    # Añadir espacio tras signo de puntuación no final
    linea = re.sub(r"([:;.,]+)(\S)", r"\1 \2", linea)

    resultado.append(linea)

  return resultado

Caso de prueba (tiene de todo un poco):

txt = [
"Hola que   pasa,\n",
"    Aqui hay cuatro espacios al inicio.Y     muchos   entre palabras.\n",
"        Aqui hay ocho al   principio.",
"          Y aqui diez...Y mas texto."
]

print(correcciones(txt))

Sale:

['Hola que pasa,\n',
 '\tAqui hay cuatro espacios al inicio. Y muchos entre palabras.\n',
 '\t\tAqui hay ocho al principio.',
 '\t\t  Y aqui diez... Y mas texto.']

Que parece estar correcto.

Como ves en el código, el problema del tabulador al inicio de la línea lo resolví "a pelo", iterando quitando espacios de 4 en 4 hasta que no hubiera más. El resto de los casos los resolví con expresiones regulares. Si tienes interés puedo explicar cómo funcionan. No pude usar una expresión regular también para lo del tabulador por culpa del requisito "al principio de la línea". Si la conversión 4 espacios -> tabulador se tuviera que hacer en cualquier lugar, se podría haber hecho también con expresiones regulares (re.sub(r" {4}", "\t", linea))

Actualización

Explicación de la expresión regular. Una expresión regular es una cadena formada por una serie de símbolos que tienen un significado especial. La expresión regular representa un cierto patrón a buscar dentro de una cadena dada (por ejemplo: "una secuencia de cuatro dígitos", o "Una palabra que comienza por mayúscula y tiene seis letras", etc.) Las expresiones regulares se construyen usando un lenguaje concreto. No es este lugar para explicar al completo ese lenguaje. Puedes mirar la documentación de python al respecto o esta pregunta-resumen en Stack Overflow en Español.

En este apartado simplemente voy a explicar las expresiones regulares que he usado en mi solución.

La primera es la que aparece en:

linea = re.sub(r"(\S) {2,}", r"\1 ", linea)

re.sub() es una función que espera como primer parámetro una expresión regular, que enseguida explicaré, que encajará con ciertas partes de una cadena, como segundo parámetro una cadena de sustitución que se usará para sustituir las partes en las que la expresión regular haya encajado, y como tercer parámetro la cadena a la que hay que aplicárselo, en este caso la línea que estamos procesando.

La expresión regular en Python se escribe como una cadena entre comillas, pero con una r delante de la primera comilla (esto es porque en las expresiones regulares muy a menudo aparece la barra inversa \, que en una cadena normal tiene un significado especial, y en una cadena raw, marcada con la r fuera, no tiene significado especial para python). En nuestro caso la expresión regular es:

(\S) {2,}

Esto significa lo siguiente (carácter a carácter)

  • ( la apertura de un parentesis indica que comienza un "grupo de captura". Lo que haya dentro de los paréntesis será "capturado" cuando la expresión regular encaje con algo de lo que había en la línea. Esa "captura" estará disponible para usarse en la cadena de sustitución, donde se ponga \1.
  • El carácter \ es especial e indica que para interpretarlo hay que seguir mirando el siguiente carácter.
  • La secuencia \S es un símbolo que dentro de una expresión regular significa "cualquier carácter que no sea un blanco". Los blancos son espacios, tabuladores, retornos de carro y otros caracteres unicode con el mismo significado (separadores invisibles). Por tanto un "no-blanco" es cualquier otro signo visible (una letra, un número, un signo de puntuacion, etc.)
  • ) cierra el grupo de captura.
  • es un espacio en blanco
  • {2,} es un cuantificador que afecta al carácter anterior y que indica que debe aparecer repetido un mínimo de 2 veces en este caso y un máximo sin especificar. (Se puede poner un máximo tras la coma si se quiere indicar un máximo de repeticiones)

Por tanto toda la expresión regular podría traducirse a lenguaje llano como:

Cualquier caracter no-blanco seguido de dos o más espacios (siendo el caracter en cuestión capturado)

Así, por ejemplo, en la cadena "Esto es una prueba", ese patrón encajaría en varios sitios. El primero sería en la o de Esto, pues esa o es un carácter no-blanco seguido de dos o más espacios. La expresión encajaría con las subcadenas marcadas en las siguiente figura:

Esto     es  una    prueba
   ^^^^^^ ^^^  ^^^^^ 

En el primer match tendría seis caracteres: o (una o y cinco espacios) y el grupo de captura asociado a ese match sería la o.

El siguiente parámetro de re.sub() vale r"\1 ", que significa:

Sustituir el match que se haya obtenido por una cadena formada por el contenido del grupo de captura y un espacio.

Por tanto "o " será sustituido por "o " (la o sería el \1). Análogamente "s " será sustituido por "s ", etc.

De este modo se sustituyen varios espacios por uno solo, pero sólo si esos espacios iban después de algo que no era espacio.

La siguiente expresión regular aparece en la línea:

linea = re.sub(r"([:;.,]+)(\S)", r"\1 \2", linea)

Ya hemos explicado qué hace re.sub(), expliquemos ahora la expresión regular usada en este caso, que tiene en esta ocasión dos grupos de captura:

([:;.,]+)(\S)
  • ( se abre el primer grupo de captura
  • [ el corchete indica un "conjunto". La expresión es como un "or" y encajará si uno de los elementos dentro de los corchetes aparece en la cadena.
  • :,., son los posibles caracteres a encajar, los signos de puntuación especificados en la pregunta
  • ] se cierra el conjunto. El conjunto completo encajará con un solo carácter. Por ejemplo, si la cadena contiene "Hola, mundo", ese conjunto encajaría con la coma.
  • + este signo indica que el carácter anterior puede aparecer más de una vez. Equivale a haber puesto {1,}. Por tanto ahora si aparecieran varios signos de puntuación seguidos, encajarían toda la secuencia. Por ejemplo en la cadena "Hola,.; mundo", encajaría la parte ,.;
  • ) se acaba el grupo de captura, que contendría por tanto la secuencia de signos hallada.
  • ( se incicia un segundo grupo de captura
  • \S que contiene un solo caracter, no-blanco
  • ) y se acaba el segundo grupo de captura.

Por tanto toda la expresión regular puede leerse como:

Una secuencia de uno o más signos del conjunto :.;, seguido inmediatamente después de un carácter no-blanco.

Por ejemplo, en la cadena "Hola, mundo" no se producirían coincidencias pues aunque aparece una coma, después va un blanco en lugar de un no-blanco. En cambio en la cadena "Hola,mundo" sí habría una coincidencia, pues aparece la coma y detrás un carácter no blanco que es la m. En ese match el primer grupo de captura contendría la coma, y el segundo la m.

Cuando se encuentra un match el texto encajado se cambia por este otro: r"\1 \2", es decir, se mete un espacio separando el signo de puntuación de la letra que le seguía.

La página web https://regex101.com/ es muy útil para cuando estás aprendiendo expresiones regulares. Te permite escribir una, y te la "traduce" en el panel derecho, a la vez que te permite comprobar si encaja o no con cualquier texto de prueba que quieras darle.

abulafia
  • 53,696
  • 3
  • 45
  • 80
0

La función endswith() devuelte true o false, para reemplazar parte de una cadena debes usar la función replace(), por ejemplo así:

texto = texto.replace('.','. ')
jacdDev
  • 371
  • 1
  • 7
  • Ok! Un pasito mas, es que no tengo demasiada experiencia con estas funciones en python. Mil gracias, voy a ver si consigo crearla. – Eva Rentero Jan 02 '19 at 13:43