Filtre à puce avec python
-
10-10-2019 - |
Question
Salut
J'ai besoin de filtrer toutes les lignes qui ne contiennent pas des symboles de liste énorme « nécessaire », par exemple le code suivant:
def any_it(iterable):
for element in iterable:
if element: return True
return False
regexp = re.compile(r'fruit=([A-Z]+)')
necessary = ['YELLOW', 'GREEN', 'RED', ...] # huge list of 10 000 members
f = open("huge_file", "r") ## file with > 100 000 lines
lines = f.readlines()
f.close()
## File rows like, let's say:
# 1 djhds fruit=REDSOMETHING sdkjld
# 2 sdhfkjk fruit=GREENORANGE lkjfldk
# 3 dskjldsj fruit=YELLOWDOG sldkfjsdl
# 4 gfhfg fruit=REDSOMETHINGELSE fgdgdfg
filtered = (line for line in lines if any_it(regexp.findall(line)[0].startswith(x) for x in necessary))
Je python 2.4, donc je ne peux pas utiliser intégré any()
.
J'attends depuis longtemps pour ce filtrage, mais est-il un moyen d'optimiser? Par exemple la ligne 1 et 4 contient « ROUGE .. » modèle, si nous avons constaté que « RED .. » pattern est ok, peut-on SKIP recherche dans la liste 10000 membres pour la ligne 4 du même modèle ??
Y at-il une autre façon d'optimiser le filtrage
Merci.
... édité ...
UPD: Voir les données exemple réel dans les commentaires à ce poste. Je suis également intéressé par le tri par « fruits » le résultat. Merci!
... fin édité ...
La solution
Je suis convaincu réponse de Zach est sur la bonne voie. Par curiosité, j'ai mis en place une autre version (intégrant les commentaires de Zach sur l'utilisation d'un dict au lieu de bisect
) et croisai dans une solution qui correspond à votre exemple.
#!/usr/bin/env python
import re
from trieMatch import PrefixMatch # https://gist.github.com/736416
pm = PrefixMatch(['YELLOW', 'GREEN', 'RED', ]) # huge list of 10 000 members
# if list is static, it might be worth picking "pm" to avoid rebuilding each time
f = open("huge_file.txt", "r") ## file with > 100 000 lines
lines = f.readlines()
f.close()
regexp = re.compile(r'^.*?fruit=([A-Z]+)')
filtered = (line for line in lines if pm.match(regexp.match(line).group(1)))
Par souci de concision, la mise en œuvre de PrefixMatch
est publié ici .
Si votre liste de préfixes necessary
est statique ou change rarement, vous pouvez accélérer les exécutions ultérieures par décapage et réutilisation de l'objet PickleMatch
au lieu de le reconstruire à chaque fois.
Mise à jour (sur les résultats classés)
Selon le changelog pour Python 2.4 :
touche doit être une fonction unique paramètre qui prend un élément de liste et renvoie une clé de comparaison pour la élément. La liste est ensuite triée à l'aide les touches de comparaison.
aussi, la source code, ligne 1792 :
/* Special wrapper to support stable sorting using the decorate-sort-undecorate
pattern. Holds a key which is used for comparisons and the original record
which is returned during the undecorate phase. By exposing only the key
.... */
Cela signifie que votre modèle de regex est seulement évaluée une fois pour chaque entrée (pas une seule fois pour chaque comparer), donc il ne devrait pas être trop coûteux à faire:
sorted_generator = sorted(filtered, key=regexp.match(line).group(1))
Autres conseils
Si vous avez organisé la liste necessary
comme Trie, alors vous pouvez regarder dans cette structure arborescente à vérifier si le fruit
commence par un préfixe valide. Cela devrait être plus rapide que la comparaison du fruit
contre chaque préfixe.
Par exemple (testé uniquement légèrement):
import bisect
import re
class Node(object):
def __init__(self):
self.children = []
self.children_values = []
self.exists = False
# Based on code at http://docs.python.org/library/bisect.html
def _index_of(self, ch):
i = bisect.bisect_left(self.children_values, ch)
if i != len(self.children_values) and self.children_values[i] == ch:
return (i, self.children[i])
return (i, None)
def add(self, value):
if len(value) == 0:
self.exists = True
return
i, child = self._index_of(value[0])
if not child:
child = Node()
self.children.insert(i, child)
self.children_values.insert(i, value[0])
child.add(value[1:])
def contains_prefix_of(self, value):
if self.exists:
return True
i, child = self._index_of(value[0])
if not child:
return False
return child.contains_prefix_of(value[1:])
necessary = ['RED', 'GREEN', 'BLUE', 'ORANGE', 'BLACK',
'LIGHTRED', 'LIGHTGREEN', 'GRAY']
trie = Node()
for value in necessary:
trie.add(value)
# Find lines that match values in the trie
filtered = []
regexp = re.compile(r'fruit=([A-Z]+)')
for line in open('whatever-file'):
fruit = regexp.findall(line)[0]
if trie.contains_prefix_of(fruit):
filtered.append(line)
Cela change votre algorithme de O(N * k)
, où N
est le nombre d'éléments de necessary
et k
est la longueur de fruit
, juste O(k)
(plus ou moins). Il ne prend plus de mémoire si, mais ce pourrait être un compromis intéressant pour votre cas.
Personnellement, je aime votre code tout comme puisque vous considérez comme « fruit = COLOR » comme un modèle que d'autres ne fonctionne pas. Je pense que vous voulez trouver une solution comme memoization qui vous permet de sauter test problème déjà résolu, mais ce n'est pas le cas, je suppose.
def any_it (iterable): pour l'élément en iterable: si l'élément: Vrai retour return false
= nécessaires [ 'JAUNE', 'VERT', 'RED', ...]
prédicat = ligne lambda: any_it ( "fruit =" + couleur dans la ligne de couleur dans nécessaire)
filtré = ifilter (prédicat, ouvert ( "testest"))
Testé (mais unbenchmarked) Code:
import re
import fileinput
regexp = re.compile(r'^.*?fruit=([A-Z]+)')
necessary = ['YELLOW', 'GREEN', 'RED', ]
filtered = []
for line in fileinput.input(["test.txt"]):
try:
key = regexp.match(line).group(1)
except AttributeError:
continue # no match
for p in necessary:
if key.startswith(p):
filtered.append(line)
break
# "filtered" now holds your results
print "".join(filtered)
Diff de code en question:
-
Nous ne pas d'abord charger le fichier entier en mémoire (comme cela se fait lorsque vous utilisez
file.readlines()
). Au lieu de cela, nous traitons chaque ligne que le fichier est lu. J'utilise le modulefileinput
ici par souci de concision, mais on peut aussi utiliserline = file.readline()
et une boucle dewhile line:
. -
Nous itérer arrêter la liste des
necessary
une fois par correspondance est trouvée. -
Nous avons modifié le modèle regex et l'utilisation
re.match
au lieu dere.findall
. Cela suppose que chaque ligne ne contiendrait un "fruit = ..." entrée.
Mise à jour
Si le format du fichier d'entrée est compatible, vous pouvez presser un peu plus de performance en se débarrassant de regex tout à fait.
try:
# with line = "2 asdasd fruit=SOMETHING asdasd...."
key = line.split(" ", 3)[2].split("=")[1]
except:
continue # no match
filtered=[]
for line in open('huge_file'):
found=regexp.findall(line)
if found:
fruit=found[0]
for x in necessary:
if fruit.startswith(x):
filtered.append(line)
break
ou peut-être:
necessary=['fruit=%s'%x for x in necessary]
filtered=[]
for line in open('huge_file'):
for x in necessary:
if x in line:
filtered.append(line)
break
Je fait une liste simple ['fruit=RED','fruit=GREEN'...
etc. avec ['fruit='+n for n in necessary]
, puis utilisez in
plutôt que d'un regex pour les tester. Je ne pense pas qu'il y ait une façon de le faire très rapidement, cependant.
filtered = (line for line in f if any(a in line for a in necessary_simple))
(La toute fonction () fait la même chose que votre fonction any_it ())
Oh, et se débarrasser de file.readlines (), juste itérateur sur le fichier.
code non testé:
filtered = []
for line in lines:
value = line.split('=', 1)[1].split(' ',1)[0]
if value not in necessary:
filtered.append(line)
Ce devrait être plus rapide que 10 000 modèle correspondant à des motifs sur une ligne. Peut-être il existe des moyens encore plus rapides. :)
Il ne faut pas prendre trop de temps pour parcourir 100.000 cordes, mais je vois que vous avez une liste de 10.000 chaînes, qui signifie que vous itérer 10 000 * 100 000 = 1000000000 fois les cordes, donc je ne sais pas qu'est-ce que vous attendez. .. Quant à votre question, si vous rencontrez un mot dans la liste et vous avez seulement besoin de 1 ou plus (si vous voulez exacly 1 vous devez parcourir la liste complète) vous peut sauter le reste, il devrait optimiser l'opération de recherche.