Trova la frase nominale prima e dopo un numero in un testo

Aug 19 2020

Dato un testo devo trovare le parole che precedono tutti i numeri fino ad una stop word appartenente ad una lista check_words (tipo di stopword).

Il mio codice:

check_words = ['the', 'a', 'with','to']
mystring = 'the code to find the beautiful words 78 that i have to nicely check 45 with the snippet'
list_of_words = mystring.split()

In quel particolare testo controllerei prima '78'e '45'andrò indietro fino al punto in cui trovo una qualsiasi delle parole in check_words (ma non più di 8 parole).

Il codice per farlo potrebbe essere:

preceding_chunks = []
for i,word in enumerate(list_of_words):
    if any(char.isdigit() for char in word):
       
        # 8 precedent words (taking into account that I can not slice with 8 back at the beginning)
        preceding_words = list_of_words[max(0,i-8):i]
        preceding_words[::-1]

        # I check from the end of the list towards the start
        for j,sub_word in enumerate(preceding_words[::-1]):
            if  sub_word in check_words:
                # printing out j for checking
                myposition = j
                print(j)
                real_preceding_chunk = preceding_words[len(preceding_words)-j:]
                print(real_preceding_chunk)
                preceding_chunks.append(real_preceding_chunk)
                break

Questo codice funziona. in pratica controllo ogni parola tha Ma ho l'impressione (forse mi sbaglio) che si possa ottenere con un paio di one liner e quindi senza loop. Qualche idea?


NOTA: questa domanda riguarda il miglioramento della leggibilità del codice, il tentativo di eliminare i loop per rendere il codice più veloce e il tentativo di rendere il codice più gradevole, che fa parte dello Zen di Python.


NOTA 2: Alcuni controlli precedenti che ho fatto:

  • Trovare la posizione di un elemento in un altro elenco da un numero in un elenco diverso
  • Trovare l'indice di un elemento in un elenco
  • Trova nell'elenco

Risposte

MarioIshac Aug 19 2020 at 09:13

mi è venuto in mente questo:

import itertools
import re

chunks = (grouped_chunk.split() for grouped_chunk in re.split("\\s+\\d+\\s+", mystring))
preceding_chunks = []

for reversed_chunk in map(reversed, chunks):
    preceding_chunk = list(itertools.takewhile(lambda word: word not in check_words, reversed_chunk))[::-1]
    preceding_chunks.append(preceding_chunk)

Applichiamo itertools.takewhilea reversed_chunkche ci dà il blocco precedente in ordine inverso. Otteniamo quindi l'ordine corretto preceding_chunkinvertendo alla fine con [::-1].

La regex si divide mystringin base a un numero (l'escape \d+). Le s di escape circostanti \s+rappresentano qualsiasi riempimento attorno al numero. Ciò fa sì che questo codice abbia un comportamento diverso dal tuo se cifre e lettere sono mescolate nelle stesse parole (ad esempio, a1).

Per il tuo codice originale, darei un paio di suggerimenti:

  1. Segui PEP 8 . Ad esempio, aggiungi spaziatura dopo la virgola in i,word.
  2. Rimuovere l'espressione ridondante preceding_words[::-1]. Anche se questo viene valutato in reverse preceding_words, poiché non è sul posto, la valutazione non ha effetti collaterali. Inoltre, esegui già questa inversione in enumerate(preceding_words[::-1]).