Voici mon approche pour trouver les sous-chaînes dans une liste contenant des «phrases» en étant recherché par une liste contenant des «mots» et retourner les sous-chaînes correspondantes qui ont été trouvées dans chaque élément dans une liste contenant des phrases.

import re

def is_phrase_in(phrase, text):
    return re.search(r"\b{}\b".format(phrase), text, re.IGNORECASE) is not None

list_to_search = ['my', 'name', 'is', 'you', 'your']
list_to_be_searched = ['hello my', 'name is', 'john doe doe is last name', 'how are you', 'what is your name', 'my name is jane doe']

to_be_appended = []
for phrase in list_to_be_searched:
    searched = []
    for word in list_to_search:
        if is_phrase_in(word,phrase) is True:
            searched.append(word)
    to_be_appended.append(searched)
print(to_be_appended)

# (desired and actual) output
[['my'],
 ['name', 'is'],
 ['name', 'is'],
 ['you'],
 ['name', 'is', 'your'],
 ['my', 'name', 'is']]

Étant donné que la liste «mots» (ou list_to_search) contient ~ 1700 mots et que la liste «phrases» (ou list_to_be_searched) contient ~ 26561, il faut plus de 30 minutes pour terminer le code. Je ne pense pas que mon code ci-dessus ait été implémenté compte tenu de la méthode de codage Pythonic et de la structure de données efficace. :(

Quelqu'un pourrait-il donner des conseils pour l'optimiser ou le rendre plus rapide?

Merci!

En fait, j'ai écrit le mauvais exemple ci-dessus. Que faire si la 'list_to_search' contient des éléments de plus de 2 mots?

import re

def is_phrase_in(phrase, text):
    return re.search(r"\b{}\b".format(phrase), text, re.IGNORECASE) is not None

list_to_search = ['hello my', 'name', 'is', 'is your name', 'your']
list_to_be_searched = ['hello my', 'name is', 'john doe doe is last name', 'how are you', 'what is your name', 'my name is jane doe']

to_be_appended = []
for phrase in list_to_be_searched:
    searched = []
    for word in list_to_search:
        if is_phrase_in(word,phrase) is True:
            searched.append(word)
    to_be_appended.append(searched)
print(to_be_appended)
# (desired and actual) output
[['hello my'],
 ['name', 'is'],
 ['name', 'is'],
 [],
 ['name', 'is', 'is your name', 'your'],
 ['name', 'is']]

Timing 1st Method:

%%timeit
def is_phrase_in(phrase, text):
    return re.search(r"\b{}\b".format(phrase), text, re.IGNORECASE) is not None

    list_to_search = ['hello my', 'name', 'is', 'is your name', 'your']
    list_to_be_searched = ['hello my', 'name is', 'john doe doe is last name', 'how are you', 'what is your name', 'my name is jane doe']
to_be_appended = []
for phrase in list_to_be_searched:
    searched = []
    for word in list_to_search:
        if is_phrase_in(word,phrase) is True:
            searched.append(word)
    to_be_appended.append(searched)
#43.2 µs ± 346 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

2ème méthode (compréhension de liste imbriquée et re.findall)

%%timeit
[[j for j in list_to_search if j in re.findall(r"\b{}\b".format(j), i)] for i in list_to_be_searched]
#40.3 µs ± 454 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)\

Le timing s'est certainement amélioré, mais y aurait-il un moyen plus rapide? Ou, la tâche est génétiquement lente compte tenu de ce qu'elle fait?

2
nyob3 18 mars 2019 à 14:20

2 réponses

Meilleure réponse

Bien que l'approche la plus simple / claire consiste à utiliser des compréhensions de liste, je voulais voir si regex pouvait mieux le faire.

L'utilisation de regex sur chaque élément de list_to_be_searched ne semblait pas avoir de gains de performances. Mais en joignant le list_to_be_searched dans un gros bloc de texte et en le faisant correspondre avec un modèle regex construit à partir de list_to_search, les performances ont légèrement augmenté :

In [1]: import re
   ...:
   ...: list_to_search = ['my', 'name', 'is', 'you', 'your']
   ...: list_to_be_searched = ['hello my', 'name is', 'john doe doe is last name', 'how are you', 'what is your name', 'my name is jane doe']
   ...:
   ...: def simple_method(to_search, to_be_searched):
   ...:   return [[j for j in to_search if j in i.split()] for i in to_be_searched]
   ...:
   ...: def regex_method(to_search, to_be_searched):
   ...:   word = re.compile(r'(\b(?:' + r'|'.join(to_search) + r')\b(?:\n)?)')
   ...:   blob = '\n'.join(to_be_searched)
   ...:   phrases = word.findall(blob)
   ...:   return [phrase.split(' ') for phrase in ' '.join(phrases).split('\n ')]
   ...:
   ...: def alternate_regex_method(to_search, to_be_searched):
   ...:   word = re.compile(r'(\b(?:' + r'|'.join(to_search) + r')\b(?:\n)?)')
   ...:   phrases = []
   ...:   for item in to_be_searched:
   ...:     phrases.append(word.findall(item))
   ...:   return phrases
   ...:

In [2]: %timeit -n 100 simple_method(list_to_search, list_to_be_searched)
100 loops, best of 3: 23.1 µs per loop

In [3]: %timeit -n 100 regex_method(list_to_search, list_to_be_searched)
100 loops, best of 3: 18.6 µs per loop

In [4]: %timeit -n 100 alternate_regex_method(list_to_search, list_to_be_searched)
100 loops, best of 3: 23.4 µs per loop

Pour voir comment cela fonctionnait avec de grandes entrées, j'ai utilisé les 1000 mots les plus fréquents en anglais1 pris un mot à la fois comme list_to_search, et le texte entier de David Copperfield du projet Gutenberg< sup>2 pris une ligne à la fois comme list_to_be_searched :

In [5]: book = open('/tmp/copperfield.txt', 'r+')

In [6]: list_to_be_searched = [line for line in book]

In [7]: len(list_to_be_searched)
Out[7]: 38589

In [8]: words = open('/tmp/words.txt', 'r+')

In [9]: list_to_search = [word for word in words]

In [10]: len(list_to_search)
Out[10]: 1000

Voici les résultats:

In [15]: %timeit -n 10 simple_method(list_to_search, list_to_be_searched)
10 loops, best of 3: 31.9 s per loop

In [16]: %timeit -n 10 regex_method(list_to_search, list_to_be_searched)
10 loops, best of 3: 4.28 s per loop

In [17]: %timeit -n 10 alternate_regex_method(list_to_search, list_to_be_searched)
10 loops, best of 3: 4.43 s per loop

Alors, optez pour l'une des méthodes regex si vous aimez les performances. J'espère que cela a aidé ! :)

1
pradeepcep 19 mars 2019 à 03:46

Vous pouvez utiliser une compréhension de liste imbriquée:

list_to_search = ['my', 'name', 'is', 'you', 'your']
list_to_be_searched = ['hello my', 'name is', 'john doe doe is last name',
                       'how are you', 'what is your name', 'my name is jane doe']

[[j for j in list_to_search if j in i.split()] for i in list_to_be_searched]

[['my'],
 ['name', 'is'],
 ['name', 'is'],
 ['you'],
 ['name', 'is', 'your'],
 ['my', 'name', 'is']]
2
yatu 18 mars 2019 à 11:33