2017-04-03 3 views
0

J'ai un très gros fichier texte à partir duquel je dois extraire des données. Je lis le fichier ligne par ligne et cherche des mots-clés. Comme je sais que les mots-clés que je cherche sont beaucoup plus proches de la fin du fichier qu'au début. J'ai essayé mots clés tac set fh [open « | nom de fichier tac »] Je reçois erreur: ne pouvait pas exécuter « tac »: pas de fichier ou répertoireComment lire le fichier de bout en bout (dans l'ordre inverse) dans TCL?

Ma taille du fichier est grand donc je ne suis pas en mesure pour stocker la ligne dans une boucle et l'inverser à nouveau. Veuillez suggérer une solution

Répondre

0

Le coût de l'inversion d'un fichier est en réalité assez élevé. La meilleure option à laquelle je peux penser est de construire une liste de décalages de fichiers des départs de lignes, puis d'utiliser un modèle seek;gets pour parcourir cette liste.

set f [open $filename] 

# Construct the list of indices 
set indices {} 
while {![eof $f]} { 
    lappend indices [tell $f] 
    gets $f 
} 

# Iterate backwards 
foreach idx [lreverse $indices] { 
    seek $f $idx 
    set line [gets $f] 

    DoStuffWithALine $line 
} 

close $f 

Le coût de cette approche est non négligeable (même si vous est arrivé d'avoir un cache des indices, vous auriez encore des problèmes avec elle), car il ne fonctionne pas avec la façon dont le système d'exploitation pré -Faites les données du disque.

1

tac est lui-même un programme assez simple - vous pouvez simplement implémenter son algorithme dans Tcl, au moins si vous êtes déterminé à lire littéralement chaque ligne dans l'ordre inverse. Cependant, je pense que cette contrainte n'est pas vraiment nécessaire - vous avez dit que le contenu que vous recherchez est plus susceptible d'être proche de la fin qu'au début, pas que vous deviez scanner les lignes dans l'ordre inverse. Cela signifie que vous pouvez faire quelque chose d'un peu plus simple. Grosso modo:

  1. Recherche d'un décalage près de la fin du fichier.
  2. Lire ligne par ligne comme d'habitude, jusqu'à ce que vous atteigniez des données que vous avez déjà traitées.
  3. Recherche d'un décalage un peu plus éloigné de la fin du fichier.
  4. Lire ligne par ligne comme d'habitude, jusqu'à ce que vous atteigniez des données que vous avez déjà traitées.
  5. etc.

De cette façon, vous n'avez pas vraiment de garder quelque chose de plus en mémoire que la seule ligne que vous traitez en ce moment, et vous traitez les données à la fin du fichier avant données plus tôt dans le fichier. Peut-être pourriez-vous améliorer un peu plus les performances en traitant strictement les lignes dans l'ordre inverse, mais je doute que ce soit important par rapport à l'avantage que vous obtenez en ne numérisant pas du début à la fin.

Voici un exemple de code qui implémente cet algorithme. Notez le peu de soin apporté pour éviter de traiter une ligne partielle:

set BLOCKSIZE 16384 
set offset  [file size $filename] 
set lastOffset [file size $filename] 

set f [open $filename r] 
while { 1 } { 
    seek $f $offset 

    if { $offset > 0 } { 
     # We may have accidentally read a partial line, because we don't 
     # know where the line boundaries are. Skip to the end of whatever 
     # line we're in, and discard the content. We'll get it instead 
     # at the end of the _next_ block. 

     gets $f 
     set offset [tell $f] 
    } 

    while { [tell $f] < $lastOffset } { 
     set line [gets $f] 

     ### Do whatever you're going to do with the line here 

     puts $line 
    } 

    set lastOffset $offset 
    if { $lastOffset == 0 } { 
     # All done, we just processed the start of the file. 

     break 
    } 

    set offset [expr {$offset - $BLOCKSIZE}] 
    if { $offset < 0 } { 
     set offset 0 
    } 
} 
close $f