2
votes

Comment faire correspondre efficacement des chaînes entre deux grandes listes avec python? (510.000.000 comparaisons)

Je suis confronté au problème d'une boucle for très longue.

Il existe deux listes python (A et B):

A contient environ 170 000 chaînes avec des longueurs comprises entre 1 et 100 caractères. B contient environ 3.000 chaînes avec la même variété de longueur.

Maintenant, je dois trouver des éléments de la liste A qui contiennent un élément de la liste B.

Étant donné que chaque chaîne de A doit être comparée à chaque chaîne de B, il en résulte 510 000 000 de comparaisons. Cela semble trop coûteux en informatique.

Quelles sont les possibilités d'accélérer les choses?

Pseudo-code:

A[0] = "This is some random text in which I want to find words"
A[1] = "It is just some random text"
...
B[0] = "text"
B[1] = "some random text"
...

Exemple de contenu pour certains éléments de la liste:

A = []  # length: 170.000 (strings)
B = []  # length: 3.000 (strings)

for item in A:
    for element in B:
        if element in item:
            print("store the item which contains the element to db")

Je ne veux pas m'arrêter après le premier match car il pourrait y avoir plus de matchs. Le but est de stocker toutes les correspondances dans une nouvelle variable / db.


15 commentaires

Vous voulez ceci: Now i need to find items from list A which contain one item from list B ou l'inverse qui est représenté dans votre code? if item (A) in element (B)


Merci pour la correction. J'ai adapté le code pour l'adapter au texte maintenant.


Avez-vous essayé d'ajouter une pause si l'élément B est dans A? De cette façon, vous réduirez le temps d'exécution


Que pouvez-vous nous dire sur le contenu de la chaîne?


@IoaTzimas il pourrait y avoir plusieurs correspondances, donc ne peut pas rompre la boucle après la première correspondance.


Si l'élément A comprend au moins l'élément de B, vous ne vous souciez pas de plus, n'est-ce pas? Quoi qu'il en soit, il serait utile que vous fournissiez un échantillon de vos listes, afin que nous examinions des solutions plus rapides


@IoaTzimas Je pense que "élément" signifie "élément", pas "élément". La question est de savoir s'ils veulent que les éléments soient stockés dans db plusieurs fois (s'ils correspondent à plusieurs éléments). Sinon, changez les boucles et coupez comme vous le dites.


J'ai mis à jour la question concernant vos commentaires. Je stocke en fait l'élément qui contient l'élément. (désolé pour la confusion) et cela ne me dérange pas si je le stocke plusieurs fois


Vous pouvez trier les deux listes par longueur de chaînes et donc rompre votre boucle interne si la longueur de l' element est supérieure à celle de l' item . Cela ne réduira pas la complexité, mais cela diminuera le nombre d'opérations


@busfighter ce son est prometteur même si on se demande à quel point le tri est coûteux en calcul?


Quelle exaclty voulez-vous stocker dans db? L'élément A ou la partie accouplée (égale à l'élément B)?


le tri a une complexité O (n logn) (qui est inférieure à O (n m) si n et m sont d'un ordre) et trouver la longueur de la chaîne est moins chère (O (1)) que de vérifier si la chaîne est une sous-chaîne d'une autre (O (n * m) où n et m sont des longueurs de chaînes)). Alors oui ça doit être plus rapide dans le cas moyen


Maintenant, vous dites "ça ne me dérange pas si je le stocke plusieurs fois" . Mais voulez- vous / avez besoin de le stocker plusieurs fois, ou est-ce qu'une fois suffit?


Y a-t-il des caractères garantis de ne pas être dans les chaînes? Peut-être chr(0) ?


Je veux vraiment (!) Le stocker à chaque fois qu'il est trouvé et je veux stocker l'élément qui contient l'élément + l'élément dans une autre colonne de la base de données. (Cela fait partie d'un processus de cartographie.) Et j'attends tous les types de personnages possibles.


6 Réponses :


-3
votes

def two_list (a, b): pour élément dans a: pour num dans b: si élément == num: print (élément)

print (liste_deux (a, b))


1 commentaires

C'est manifestement faux. Elle a la même efficacité que la question d'OP et n'est pas de quoi il s'agit.



0
votes

Vous pouvez également le faire avec les pandas.

adf = (
    pandas.DataFrame(A,columns=['text'])
    .assign(strlen=lambda x: x['text'].str.len())
) #create a df from the first array

bdf = (
    pandas.DataFrame(B,columns=['text'])
    .assign(strlen=lambda x: x['text'].str.len())
    .sort_values('strlen')
) #create a df from the second array

resultdf = pandas.DataFrame()

for i,row in bdf.itterrows():
   if len(row['text']) > adf.text.max():
       break
   resultdf = resultdf.append(
           adf[lambda x: x['text'].str.contains(row['text'])],ignore_index=True)

resultdf


6 commentaires

mais sera-ce plus rapide que d'utiliser des listes classiques et des boucles for?


Je pense que le filtrage des pandas fonctionne plus rapidement que l'itération de plus de 170000 cellules de tableau. et vous n'aurez que 3000 itérations.


ok ça vaut le coup d'essayer. @busfighter a suggéré de trier les listes en premier et de rompre la boucle lorsque la longueur dépasse les éléments d'itérations suivants. Cela pourrait-il être mis en œuvre ici également?


Pour trier quelle liste? et par quoi? vous pouvez facilement trier les pandas df en utilisant .sort_values ('column_name', ascending = True ou False).


Busfighter a suggéré de trier les deux listes en fonction de la longueur de l'élément et une fois que la longueur de l'élément est plus longue, nous pouvons interrompre la boucle pour enregistrer les itérations qui ne peuvent pas correspondre de toute façon.


J'ai mis à jour la réponse pour en tenir compte



0
votes

Vous pouvez essayer ceci:

d={}
for i in range(1,101):
    d[i]=[]
    for x in A:
        for y in range(min(101, len(x))-i+1):
            d[i].append([x[y:y+i]), A])

result=[]
for item in B:
    s=d[len(item)]
    for k in s:
        if item==s[0]:
            result.append(s[1])

Explication: Nous créons un dict avec les clés 1-100 qui représentent les longueurs possibles des éléments dans B. Nous bouclons dans la liste A. Pour chaque élément de A, nous bouclons de 1 à max (minimumn à partir de la longueur de l'élément ou 100) et sauvegardons toutes les parties de A à la clé associée en d. Lorsque cela se termine, nous n'avons qu'à boucler la liste B une fois, et comparer l'élément (de B) avec les valeurs dans la clé respective de d. Par exemple, si la longueur de l'élément est 20, nous ne vérifierons que d [20]. Si l'élément est unique avec un élément, nous sauvegardons l'élément A respectif


5 commentaires

Approche intéressante que je vais essayer. Peut-il être adapté pour être plus flexible en ce qui concerne la longueur des caractères, car il peut également y avoir des chaînes plus longues dans la suite.


Que diriez-vous simplement d'un dict mappant les sous-chaînes de chaînes A à leurs chaînes A? Doit être plus rapide et éviter que les objets de sous-chaîne dupliqués soient conservés en mémoire.


@superb rain ce serait une bonne idée, s'il y a beaucoup de doublons dans A, mais cela a à voir avec ce que nous allons enregistrer dans la base de données. Si nous sauvegardons l'intégralité de l'élément A, cela n'aiderait pas


Pourquoi cela n'aiderait-il pas? Cela supprimerait la fouille sur l' s .


Oh attendez, en fait ... puisque vous recherchez des éléments entiers de B ... ne construisez pas une structure pour A mais transformez B en un ensemble. Et puis vérifiez simplement si la sous-chaîne A actuelle est dans cet ensemble. (Ou avoir plusieurs ensembles d'éléments B, un pour chaque longueur de chaîne.)



0
votes

Première réponse: si vous ne devez le faire qu'une seule fois, forcez-le simplement. Les opérations de sous-chaînes 570M représentent beaucoup, oui, je suppose que cela prendra environ une heure, mais c'est moins de temps qu'il n'en faudra pour comprendre, écrire et déboguer une solution plus rapide.

Deuxième réponse: essayez de mettre les chaînes de B dans un trie . En théorie, cela le rendra plus rapide, mais en pratique, ce ne sera probablement pas le cas à moins que vous ne trouviez une bibliothèque de trie python implémentée en C. Sinon, parcourir un trie (ou vraiment toute autre structure de données de recherche de chaîne) en python va être lent.

Le problème auquel vous êtes confronté est que si vous avez une seule chaîne de A et une seule chaîne de B, alors b in a sera relativement rapide car sous le capot, la correspondance de sous-chaîne fonctionnera en C.Mais si vous codez un plus théoriquement solution efficace en python, même si le temps d'exécution du "gros O" est plus rapide, le temps d'exécution réel sera probablement plus lent car le python interprété est beaucoup plus lent que C.


0 commentaires

0
votes

Voici deux solutions (où l1 est la première liste et l2 est la deuxième liste):

Solution A, recherche binaire (complexité temporelle O (nlogn)):

B_d = {key: [] for key in l2}
results = []
for l in l1:
    if l in B_d:
        results.append(l)

Table de hachage de la deuxième solution (complexité temporelle O (n)):

import bisect
def method_bisect(x, b):
    index = bisect.bisect_left(b, x)
    if x == b[index]:
        return x
    return None


results = []
l2.sort()
for l in l1:
    result = method_bisect(l, l2)
    if result:
        results.append(result)


0 commentaires

0
votes

En fin de compte, j'ai choisi la solution suggérée par @busfighter dans les commentaires:

"Vous pouvez trier les deux listes par longueur de chaînes et donc rompre votre boucle interne si la longueur de l'élément est supérieure à celle de l'élément . Cela ne rendra pas la complexité plus faible mais cela diminuera le nombre d'opérations."

Speedwise il dit:

"le tri a une complexité O (nlogn) (qui est inférieure à O (nm) si n et m sont d'un ordre) et trouver la longueur de la chaîne est moins chère (O (1)) que de vérifier si la chaîne est une sous-chaîne d'une autre ( O (n * m) où n et m sont des longueurs de chaînes)) "


0 commentaires