¿Cuál es la forma más rápida de buscar si una cadena contiene otra cadena basada en una lista?

Este funciona bien, pero es demasiado lento para mí cuando la cadena es grande y la lista es larga.

test_string = "Hello! This is a test. I love to eat apples."

fruits = ['apples', 'oranges', 'bananas'] 

for fruit in fruits:
    if fruit in test_string:
        print(fruit+" contains in the string")
7
Kristoffer 4 oct. 2019 a las 17:18

6 respuestas

La mejor respuesta

Para esto, sugeriría primero tokenizar la cadena con RegexpTokenizer para eliminar todos los caracteres especiales y luego use sets para encontrar la intersección:

from nltk.tokenize import RegexpTokenizer
test_string = "Hello! This is a test. I love to eat apples."

tokenizer = RegexpTokenizer(r'\w+')
test_set = set(tokenizer.tokenize(test_string))
# {'Hello', 'I', 'This', 'a', 'apples', 'eat', 'is', 'love', 'test', 'to'}

Habiendo tokenizado la cadena y construido un conjunto, encuentre el set.intersection:

set(['apples', 'oranges', 'bananas']) & test_set
# {'apples'}
8
yatu 4 oct. 2019 a las 15:07

Los conjuntos son probablemente su mejor apuesta para la velocidad cuando se utiliza el operador in.

Para construir un conjunto que contenga solo palabras, necesitamos:

1) eliminar la puntuación de la cadena;

2) divide la cadena en espacios en blanco.

Para eliminar la puntuación, esta respuesta probablemente tenga la solución más rápida ( usando str.makestrans y string.punctuation).

Aquí hay un ejemplo usando su caso de prueba:

import string

test_string = "Hello! This is a test. I love to eat apples."
test_string_no_punctuation = test_string.translate(str.maketrans('', '', string.punctuation))
word_set = set(test_string_no_punctuation.split())

fruits = ['apples', 'oranges', 'bananas'] 

for fruit in fruits:
    if fruit in word_set:
        print(fruit+" contains in the string")

Es posible que desee ajustar las operaciones detalladas de eliminar signos de puntuación + dividir la cadena en una función:

def word_set(input_string):
    return set(input_string.translate(str.maketrans('', '', string.punctuation)).split())
1
jfaccioni 4 oct. 2019 a las 14:30

El texto suele ser más grande que la lista de palabras que está buscando.


for fruit in fruits:
    if fruit in test_string:
        print(fruit+" contains in the string")

Esto es realmente ineficiente porque en realidad está recorriendo todo el texto de cada fruta en la lista de frutas, puede que no sea un problema para las oraciones cortas, pero si estaba buscando textos largos, este proceso tomaría mucho más tiempo.

Una mejor manera es iterar a través del texto una vez y atrapar todas las palabras que están en la lista de frutas en el camino.

1
Issac Newton 4 oct. 2019 a las 14:30

Podrías hacer algo como esto:

import re

fruits = ['apples', 'oranges', 'bananas']
test_string = "Hello! This is a test. I love to eat apples."

basket = set(fruits)
words = re.compile('\w+')

for match in words.finditer(test_string):
    fruit = match.group()
    if fruit in basket:
        print(fruit + " contains in the string")

Salida

apples contains in the string
0
Dani Mesejo 4 oct. 2019 a las 15:26

Si solo le interesa si hay una palabra presente:

>>> words = set(test_string.replace('.',' ').lower().split())
>>> any(fruit in words for fruit in fruits)
True

Por supuesto, puede recorrer cada fruta para verificar cuáles se pueden encontrar en el pastel de frutas. Por lo tanto, cambiaría if fruit in test_string a if fruit in words en su ejemplo de bucle.

0
Jonas Byström 4 oct. 2019 a las 14:40

Si. puedes disminuir tus iteraciones de esta manera:

print(any(fruit in frozenset(test_string.replace('.',' ').lower().split()) for fruit in fruits))
2
Sunil Khatri 4 oct. 2019 a las 15:31
58238184