[PYTHON] Ce que j'ai fait pour accélérer la tâche de recherche de chaînes

Je l'ai essayé car il y avait un problème d'algorithme pour accélérer la recherche de chaînes de caractères au camp de développement de l'entreprise. Au fait, je suis un programmeur soi-disant copier / coller qui sait fabriquer des produits similaires et les refactoriser par rapport à ceux existants.

1. Défis

Des échantillons et des données de test implémentés à l'avance avec certaines des tâches suivantes cachées et cachées ensemble, et leurs fichiers de résultats de traitement seront fournis.

■ Contenu du programme

・ Recherchez plusieurs chaînes de caractères pour plusieurs fichiers texte et affichez les éléments suivants -Combien de chaînes de caractères applicables sont incluses (nombre total dans plusieurs textes) -Nombre de fichiers texte contenant la chaîne de caractères correspondante

・ Exécuter sur l'invite de commande ou Windows PowerShell ・ Spécifiez les arguments suivants pour "programme" Fichier de sortie du fichier d'entrée du dossier programme Dossier: stocke les fichiers cibles de recherche (pluriel) Fichier d'entrée: liste la chaîne de caractères de recherche Fichier de sortie: résultats de la recherche

・ Lors de la recherche, faites la distinction entre les majuscules et les minuscules, pleine largeur et demi-largeur ・ Le texte à rechercher et la chaîne de caractères de recherche sont utf-8.

・ Recherche chaîne de caractères -150 cas dans un fichier, séparés par des sauts de ligne Rechercher le nombre d'occurrences dans le fichier pour toutes -Toute longueur de 1 ou plusieurs caractères -Ne comprend pas les blancs, les tabulations, les sauts de ligne, les symboles spéciaux -Toute chaîne de caractères pouvant faire partie d'un mot

・ Résultats de la recherche -Sortir le nombre d'occurrences dans l'ordre d'apparition de la chaîne de caractères de recherche, séparés par des tabulations Le nombre d'apparitions est la somme des résultats de plusieurs fichiers

2. Exemple de programme

échantillon,

-Nombre de fichiers texte contenant la chaîne de caractères correspondante

Une partie n'a pas été mise en œuvre.

"""
Compter le nombre d'occurrences de la chaîne de caractères spécifiée à partir de tous les fichiers du dossier spécifié
Le résultat est sorti dans TSV
python sample1.py text_folder strings_file result_file
"""
import sys
from pathlib import Path

class TextManager:
    """
Classe qui gère les informations dans les fichiers texte
    """
    def __init__(self, foldername):
        '''
Lisez et stockez les fichiers dans le dossier

        Parameters
        ----------
        foldername : str
Nom de dossier
        '''
        self.__alllines = []
        p = Path(foldername)
        for filename in p.iterdir():
            with open(filename, 'r', encoding='utf-8') as f:
                lines = [s.strip() for s in f.readlines() if s.strip() != '']
                self.__alllines.extend(lines)

    def search(self, string_list):
        '''
Chercher
Rechercher du texte enregistré dans la liste des chaînes de recherche

        Parameters
        ----------
        string_list : List[str]
Liste des chaînes de recherche
        
        Returns
        -------
        Dict[str, int]
Résultats de recherche
        '''
        result = {}
        for s in string_list:
            count = self.__search_one(s)
            result[s] = count
        return result

    def __search_one(self, string):
        '''
Rechercher une chaîne

        Parameters
        ----------
        string : str
Chaîne de recherche
        
        Returns
        -------
        int
Nombre de chaînes de recherche existantes
        '''
        l = len(string)
        count = 0
        for line in self.__alllines:
            for i in range(0, len(line)-l+1):
                if string == line[i:i+l]:
                    count += 1
        return count

if __name__ == "__main__":
    args = len(sys.argv)
    if args != 4:
        print("USAGE: python sample1.py text_folder strings_file result_file")
        sys.exit()
    text_folder = sys.argv[1]
    strings_file = sys.argv[2]
    result_file = sys.argv[3]
    #Lire le groupe de fichiers cible (fichiers dans le dossier)
    text_manager = TextManager(text_folder)
    #Lire la chaîne de recherche
    search_strings = []
    with open(strings_file, 'r', encoding='utf-8') as fi:
        search_strings = [s.strip() for s in fi.readlines() if s.strip() != '']
    #Exécution de la recherche: List[str] -> Dict[str, int]
    result = text_manager.search(search_strings)
    with open(result_file, 'w', encoding='utf-8') as fo:
        #Sortie dans l'ordre décrit dans le fichier de chaîne de recherche
        for s in search_strings:
            c = result.get(s, 0)
            fo.write("{}\t{}\n".format(s, c))

3. Recherchez le nombre de fichiers texte contenant la chaîne de caractères appropriée

Il faut beaucoup d'efforts pour revoir toute la logique, donc en tant que programmeur copier / coller, utilisez la logique actuelle telle qu'elle est. Commencez par diviser la partie qui a été lue à partir de plusieurs fichiers dans chaque fichier et enregistrez-la. Une certaine considération est accordée aux puces à grande vitesse.

class TextManager:
    """
Classe qui gère les informations dans les fichiers texte
    """
    def __init__(self, foldername):
        '''
Lisez et stockez les fichiers dans le dossier

        Parameters
        ----------
        foldername : str
Nom de dossier
        '''
        self.__alllines = []
        __alllines_extend = self.__alllines.extend
        p = Path(foldername)
        for filename in p.iterdir():
            __filelines = []
            __filelines_extends = __filelines.extend
            with open(filename, 'r', encoding='utf-8') as f:
                f_readlines = f.readlines
                lines = [s.strip() for s in f_readlines() if s.strip() != '']
                __filelines_extends(lines)
            self.__alllines.append(__filelines)

Ajoutez une boucle pour chaque fichier à la boucle for de la partie qui recherche une chaîne de recherche dans l'ensemble, augmentez le nombre du fichier de 1 lorsque la chaîne de recherche y est trouvée, et enfin la chaîne de recherche existait Renvoie le nombre et le nombre de fichiers dans lesquels la chaîne de recherche existait. Le commentaire print () est un rappel que je l'ai modifié tout en vérifiant s'il fonctionnait correctement.

    def __search_one(self, string):
        '''
Rechercher une chaîne

        Parameters
        ----------
        string : str
Chaîne de recherche
        
        Returns
        -------
        int
Nombre de chaînes de recherche existantes

        int
Nombre de fichiers dans lesquels la chaîne de recherche existait
        '''
        l = len(string)
        count = 0
        fcount = 0
        # print(string, count, fcount)
        for filelines in self.__alllines:
            # print(filelines)
            find = False
            for line in filelines:
                # print(line)
                for i in range(0, len(line)-l+1):
                    if string == line[i:i+l]:
                        count += 1
                        find = True
            if find:
                fcount +=1
            # print(string, count, fcount)
        # print(string, count, fcount)
        return count, fcount

Le résumé de la recherche correspond à l'obtention de deux valeurs de retour à partir d'une recherche individuelle.

    def search(self, string_list):
        '''
Chercher
Rechercher du texte enregistré dans la liste des chaînes de recherche

        Parameters
        ----------
        string_list : List[str]
Liste des chaînes de recherche
        
        Returns
        -------
        Dict[str, int]
Résultats de recherche

        Dict[str, int]
Nombre de fichiers de résultats de recherche
        '''
        result = {}
        fresult = {}
        for s in string_list:
            rtn = self.__search_one(s)
            result[s] = rtn[0]
            fresult[s] = rtn[1]
        return result, fresult

La section de sortie a le même support, elle est donc omise. Maintenant, vous pouvez facilement obtenir la bonne réponse, qui est l'exigence minimale pour la tâche.

4. Éliminez les correspondances de ponctuation inutiles

Actuellement, le même jugement est fait en décalant chaque caractère du début à la fin de la ligne lue par __search_one (). Le problème est de savoir comment accélérer cela, mais si vous supprimez des blocs qui ne contiennent pas de caractères non cibles dans la chaîne de caractères cible de la recherche, l'efficacité de la correspondance devrait augmenter. C'est pourquoi il est séparé par la partie lecture. C'est la partie de .split ('[,." "<< >> | [#]]'). D'autres parenthèses doivent être ciblées, mais comme la cible de l'affectation était le fichier importé d'Aozora Bunko, cela est suffisant pour le moment.

    def __init__(self, foldername):
        '''
Lisez et stockez les fichiers dans le dossier

        Parameters
        ----------
        foldername : str
Nom de dossier
        '''
        self.__alllines = []
        __alllines_append = self.__alllines.append
        p = Path(foldername)
        for filename in p.iterdir():
            __filelines = []
            __filelines_extends = __filelines.extend
            with open(filename, 'r', encoding='utf-8') as f:
                f_readlines = f.readlines
                lines = [s.strip().split('[、。「」《》|[#]]')
                         for s in f_readlines() if s.strip() != '']
                __filelines_extends(lines)
            __alllines_append(__filelines)

__Search_one () augmente également la boucle correspondant au découpage. Il devrait y avoir un moyen de l'écrire qui n'a pas besoin d'être augmenté, mais pour le moment, une boucle était nécessaire s'il s'agissait d'une méthode de défaite. Print () est utile pour vérifier. En fait, les données de test qui ont été distribuées avec l'échantillon à l'avance étaient en anglais, donc je les ai codées avec split () sans argument, mais cela ne fonctionne pas en japonais. Puisque le résultat est étrange, quand j'imprime, il a été haché caractère par caractère en japonais. Donc, une fois que je l'ai enlevé, j'ai correspondu au nombre de fichiers, puis j'ai à nouveau «divisé».

    def __search_one(self, string):
        '''
Rechercher une chaîne

        Parameters
        ----------
        string : str
Chaîne de recherche
        
        Returns
        -------
        int
Nombre de chaînes de recherche existantes

        int
Nombre de fichiers dans lesquels la chaîne de recherche existait
        '''
        l = len(string)
        count = 0
        fcount = 0
        # print(string, count, fcount)
        for filelines in self.__alllines:
            # print(filelines)
            find = False
            for line in filelines:
                # print(line)
                for sentence in line:
                    for i in range(0, len(sentence)-l+1):
                        if string == sentence[i:i+l]:
                            count += 1
                            find = True
                            # print(sentence, string, sep='\t')
            if find:
                fcount +=1
            # print(string, count, fcount)
        # print(string, count, fcount)
        return count, fcount

5. Examen de l'appariement

Il est très inefficace de comparer l'apparence des chaînes de caractères en les décalant caractère par caractère. Peu importe combien vous concevez le langage de script, la vitesse n'augmentera pas si vous le comparez vous-même. Si vous pouvez gérer avec les fonctions de la bibliothèque standard, c'est absolument plus rapide. Je connaissais la méthode de String in String, mais je l'ai rejetée car elle ne fonctionnerait pas si elle apparaissait plusieurs fois dans une cible. Je n'ai pas confiance en ma mémoire, alors je vais la chercher. [Recherche par google "correspondance de chaîne python"](https://www.google.com/search?q=python+%E6%96%87%E5%AD%97%E5%88%97+%E3%83 % 9E% E3% 83% 83% E3% 83% 81 & oq = python +% E3% 83% 9E% E3% 83% 83% E3% 83% 81 & aqs = chrome.2.69i57j0i7i30l2j0j0i7i30j0j0i7i30l2.11559j0j7 & sourceid = chrome & ie Oh, il y avait une fonction que je voulais. Rechercher une chaîne de caractères avec Python (déterminer si elle contient, obtenir la position, le nombre) Alors, réécrivez le processus au milieu de __search_one.

            for line in filelines:
                # print(line)
                for sentence in line:
                    # for i in range(0, len(sentence)-l+1):
                    #     if string == sentence[i:i+l]:
                    c = sentence.count(string)
                    if c > 0:
                        count += c
                        find = True
 ~~~

Cela accélère le temps de traitement d'un ordre de grandeur.

# 6.Examen du traitement
Lorsque vous pouvez trouver le nombre d'apparitions de la cible en un seul coup, 4.Ajouté dans`split`Est plutôt une cause de ralentissement car il ne fait qu'augmenter les boucles.
Je n'ai plus besoin de le sculpter, alors je vais le remettre.
ici`__search_one`Je n'écrirai qu'à l'intérieur.

```python
            for line in filelines:
                # print(line)
                c = line.count(string)
                if c > 0:
                    count += c
                    find = True

7.Dernière poussée

countPuisque vous pouvez connaître le nombre d'apparitions en une seule prise de vue, il est inutile de lire ligne par ligne. Dans ce problème, il n'y a pas de limite sur la quantité de mémoire utilisée, seule la mémoire installée de l'environnement de vérification est écrite. Si vous lisez le fichier entier, cela ne fait aucune différence que vous le lisez ligne par ligne ou par fichier. C'est pourquoi j'ai opté pour la lecture par lots de fichiers. J'aurais dû changer le nom de la variable en quelque chose de plus approprié, mais je n'y ai pas vraiment pensé.

    def __init__(self, foldername):
        '''
 Lisez et stockez les fichiers dans le dossier

        Parameters
        ----------
        foldername : str
 Nom de dossier
        '''
        self.__alllines = []
        __alllines_extend = self.__alllines.extend
        p = Path(foldername)
        for filename in p.iterdir():
            with open(filename, 'r', encoding='utf-8') as f:
                lines = f.read()
            self.__alllines.append(lines)

La partie de recherche réelle est simple

    def __search_one(self, string):
        '''
 Rechercher une chaîne

        Parameters
        ----------
        string : str
 Chaîne de recherche
        
        Returns
        -------
        int
 Nombre de chaînes de recherche existantes

        int
 Nombre de fichiers dans lesquels la chaîne de recherche existait
        '''
        count = 0
        fcount = 0
        # print(string, count, fcount)
        for lines in self.__alllines:
            c = lines.count(string)
            if c > 0:
                count += c
                fcount += 1
            # print(string, count, fcount)
        # print(string, count, fcount)
        return count, fcount

C'est 116 fois plus rapide que la première bonne réponse.

8.Sommaire

Puisqu'il s'agit d'un problème d'algorithme, nous aurions dû concevoir une méthode d'appariement. En réalité,countCertaines personnes ont écrit du code qui prendrait la moitié du temps du premier code sans utiliser. Cependant, même si vous concevez divers algorithmes dans le langage de script, vous ne pouvez pas battre les fonctions de la bibliothèque dont la substance est écrite en C. Si vous savez si la bonne bibliothèque existe, vous atteindrez rapidement la bonne réponse. En fait, il y avait une personne qui a obtenu un score d'ordre de grandeur en un clin d'œil en participant au milieu, et à partir de là j'ai pensé à diverses choses et j'ai cherché et j'ai atteint le point final. Je voudrais savoir quel type de méthode est un algorithme qui traite en deux fois moins de temps sans se déplacer avec la fonction de bibliothèque.

Recommended Posts

Ce que j'ai fait pour accélérer la tâche de recherche de chaînes
Ce que j'ai fait pour établir une connexion SSH à l'environnement VPS Ubuntu
Ce que j'ai fait pour accueillir le Python2 EOL en toute confiance
Ce que j'ai fait pour économiser la mémoire Python
Ce que j'ai fait pour garder une trace de l'humidité et de la température des archives
[Chez Coder] Ce que j'ai fait pour atteindre le rang vert en Python
[Python] Ce que j'ai fait pour faire un test unitaire
Ce que j'ai fait lors de la mise à jour de Python 2.6 vers 2.7
J'ai essayé de résumer les opérations de chaîne de Python
Ce que j'ai fait quand j'étais en colère de le mettre avec l'option enable-shared
J'ai essayé d'accélérer la création vidéo en traitant en parallèle
La ventilation est importante. Ce que j'ai fait pour garder une trace de la concentration de C02 dans la pièce
Accélérez la commande netstat
Mongodb Shortest Introduction (3) J'ai essayé d'accélérer même des millions
Ce que j'ai fait quand je voulais rendre Python plus rapide -Édition Numba-
Que faire lorsque vous obtenez "Je ne peux pas voir le site !!!!"
Numba pour accélérer en Python
H29.2.27 ~ 3.5 Résumé de ce que j'ai fait
J'ai essayé de calculer l'intégrale de probabilité (I à l'intégrale)
Projet Euler 4 Tentative d'accélération
Comment accélérer les calculs Python
[DRF] Extrait pour accélérer PrimaryKeyRelatedField
J'ai essayé de déplacer le ballon
J'ai essayé d'estimer la section.
FBX SDK De quelles compétences ai-je besoin pour créer un programme à l'aide de Python?
J'ai essayé de résumer moi-même le flux général jusqu'à la création de services.
Je veux convertir par lots le résultat de "chaîne de caractères" .split () en Python
Je veux épingler Spyder à la barre des tâches
Je veux sortir froidement sur la console
Comment accélérer la belle instanciation de soupe
J'ai essayé de résumer la commande umask
Je veux gérer la rime part1
J'ai essayé de reconnaître le mot de réveil
Ce à quoi j'ai fait référence en étudiant tkinter
Je veux gérer la rime part3
"Mentez ... Qu'as-tu fait?"
J'ai essayé de résumer la modélisation graphique.
[Pandas] Développer les chaînes de caractères en DataFrame
J'ai essayé d'estimer le rapport de circonférence π de manière probabiliste
J'ai essayé de toucher l'API COTOHA
Ce que j'ai fait avec les tableaux Python
Je veux afficher la barre de progression
Ce que j'ajoute toujours à mon ~ / .bashrc
Ce que j'étais accro à Python autorun
Je veux gérer la rime part2
Je veux gérer la rime part5
Je veux gérer la rime part4
Je souhaite visualiser l'état des transferts de la J League 2020, que dois-je faire?
[Django version up] La méthode de conversion en chaîne lors de l'enregistrement de TextFiled a changé.
Ce que j'ai fait quand je suis resté coincé dans le délai avec lambda python
AtCoder AGC 041 C - J'étais accro à la recherche complète de Domino Quality
Je souhaite définir un cycle de vie dans la définition de tâche d'ECS
Ce que vous voulez mémoriser avec la grammaire de base de "manipulation de chaînes" de python
Organiser les outils Python pour accélérer le mouvement initial des compétitions d'analyse de données
J'ai créé un outil pour sauvegarder automatiquement les métadonnées de l'organisation Salesforce
[Python] J'ai essayé d'obtenir le nom du type sous forme de chaîne de caractères à partir de la fonction type
Le programme Python est lent! Je veux accélérer! Dans ce cas ...
J'ai créé un programme pour rechercher des mots sur la fenêtre (développement précédent)