3
votes

Le moyen le plus rapide de trouver et de remplacer une ligne spécifique dans un gros fichier texte avec Python

J'ai un fichier numbers.txt qui se compose de plusieurs lignes de 100K, chacune composée de deux chiffres uniques séparés par : signe:

number = 407597693

with open("numbers.txt", "r+") as library:
                file = library.read()
            if (str(number) + ":") in file:
                lines = file.splitlines()
                with open("numbers_temp.txt", "a+") as library_temp:
                    for line in lines:
                        if (str(number) + ":") in line:
                            library_temp.write(
                                "\n" + str(number) + ":" + str(time.time() + 3600)
                            )
                        else:
                            library_temp.write("\n" + line)

                    library_temp.seek(0)
                    new_file = library_temp.read()

                    with open("numbers.txt", "w+") as library_2:
                        library_2.write(new_file)

                os.remove("numbers_temp.txt")

Mon objectif est de localiser une ligne avec le numéro 407597693 sur le côté gauche, puis de changer le numéro sur le côté droit en y ajoutant 3600 . Après cela, je dois réécrire le fichier numbers.txt avec toutes les modifications. Je dois effectuer la même opération (juste un nombre différent) sur le même fichier txt aussi vite que possible.

J'ai réussi à le faire fonctionner with open: opérations with open: file et for pour chaque ligne, en recherchant le nombre nécessaire, en modifiant la ligne, puis en réécrivant le fichier entier. Cependant, j'ai remarqué que l'exécution constante d'une telle opération prend du temps pour mon programme, environ 0,2-0,5 s, ce qui s'additionne avec le temps et ralentit considérablement tout.

Voici le code que j'utilise:

407597693:1604722326.2426915
510905857:1604722326.2696202
76792361:1604722331.120079
112854912:1604722333.4496727
470822611:1604722335.283259

J'apprécierais vraiment toute contribution sur la façon d'accélérer ce processus, merci d'avance!


3 commentaires

Je soupçonne que c'est un problème XY . Veuillez sauvegarder et décrire le cas d'utilisation réel. Je trouve intenable que l'objet soit de maintenir un fichier texte, faisant des mises à jour sur une seule ligne à la demande, dépendant de Python. Pouvez-vous apporter les modifications dans un lot? Devez-vous utiliser Python? Pourquoi un fichier texte est-il nécessaire? Cela semble bien mieux adapté à une application de base de données.


@Prune "Pouvez-vous apporter les modifications dans un lot?" - Veuillez clarifier cette question. "Devez-vous utiliser Python?" - Oui, puisque c'est la langue dans laquelle mon programme complexe est intégré. "Pourquoi un fichier texte est-il nécessaire?" - Étant donné que les utilisateurs doivent avoir un accès direct et facile aux informations + peuvent être consultées hors ligne + ne devraient pas nécessiter de logiciel supplémentaire pour lire et fonctionner.


Ce n'est pas censé être une réponse complète, mais j'ai eu un problème dans lequel j'ai dû extraire des opérations spécifiques dans des fichiers texte de 250 Mo avec des points limites particuliers indiquant une nouvelle structure d'enregistrement. J'ai obtenu une réponse beaucoup plus rapide en appelant d'abord le sous-processus grep pour localiser les lignes à l'avance, puis en utilisant Python pour ouvrir et seek ces lignes, plutôt que de scanner l'ensemble du fichier en Python pour ces points de limite. Une grande partie du contenu du fichier n'avait aucun intérêt entre les frontières.


3 Réponses :


1
votes

Je suppose que votre mémoire peut stocker tout le fichier. Cela devrait être plus rapide en utilisant regex:

import re
number = 407597693
with open("numbers.txt", "r") as f:
    data = f.read()
    # data = re.sub(f'({number}):(.*)', lambda x:f"{x.group(1)}:{float(x.group(2))+3600}", data)
    data = re.sub("^" + str(number) + ".*\n", str(number) + ":" + str(int(time.time()) + 3600) + "\n", data, flags=re.MULTILINE)
with open("numbers.txt", "w") as f:
    f.write(data)


2 commentaires

Votre réponse était partiellement correcte. Bien que l'utilisation de regex ait réduit le temps nécessaire pour terminer la tâche de 4 fois, votre code spécifique ne fonctionnait pas tout à fait pour moi. La partie que j'ai dû changer est: data = re.sub("^" + str(number) + ".*\n", str(number) + ":" + str(int(time.time()) + 3600) + "\n", data, flags=re.MULTILINE) . Je marquerai votre réponse comme une solution, mais peut-être voudrez-vous changer votre ligne de code pour la mienne puisque je l'ai testée, et cela semble beaucoup plus simple.


Oh ouais j'ai mis le f dans la corde, ça devrait être au début.



0
votes

Plutôt que de devoir exécuter plusieurs boucles, nous pouvons le faire en une seule boucle comme ci-dessous:

number = 407597693
numbers = ''
with open('numbers.txt', "r+") as inputfile:
    file = inputfile.read()

    if(file.find(str(number))) != -1 :
        for line in file.splitlines():
            if (line.find(str(number))) == 0:
                numbers += line.split(':')[0] + ':' + str(float(line.split(':')[1]) + float(3600)) + '\n'
            else:
                numbers += line + '\n'

    with open('numbers.txt', 'w') as updatedFile:
    updatedFile.writelines(numbers)

J'espère que cela aidera.


0 commentaires

2
votes

Vous pouvez ouvrir un fichier mappé en mémoire, utiliser une expression régulière pour trouver la ligne souhaitée et, avec un peu de chance, vous n'aurez à modifier qu'une seule page du fichier. J'utilise le module décimal afin que vous n'ayez pas de problèmes de conversion décimal en flottant binaire. Habituellement, le nouveau numéro et l'ancien numéro auront la même largeur et le contenu du fichier n'aura pas besoin d'être déplacé. Je montre un exemple Linux. Windows mmap.map est un peu différent mais devrait être facile à utiliser.

import mmap
import re
from decimal import Decimal

def increment_record(filename, findval, increment):
    with open(filename, "rb+") as fp:
        with mmap.mmap(fp.fileno(), 0) as fmap:
            search = re.search(rf"{findme}:([\d\.]+)".encode("ascii"), fmap, 
                    re.MULTILINE)
            if search:
                # found float to change. Use Decimal for base 10 precision
                newval = Decimal(search.group(1).decode("ascii")) + increment
                newval = f"{newval}".encode("ascii")
                delta = len(newval) - len(search.group(1))
                if delta:
                    # need to expand file and copy
                    fsize = fmap.size()
                    fmap.resize(fsize + delta)
                    fmap.move(search.end(1) + delta, search.end(1), 
                        fsize - search.end(1))
                # change just the number
                fmap[search.start(1):search.start(1) + len(newval)] = newval

# test parameters
filename = "test.txt"
findme = "76792361"
increment = 3600

testdata = """407597693:1604722326.2426915
510905857:1604722326.2696202
76792361:1604722331.120079
112854912:1604722333.4496727
470822611:1604722335.283259"""

open(filename, "w").write(testdata)

increment_record(filename, findme, increment)

print("changes:")
for old,new in zip(testdata.split("\n"), open(filename)):
    new = new.strip()
    if old != new:
        print((old,new))
print("done")


0 commentaires