Très gros fichier texte à modifier. | |
Rockyboa | 10-Sep-2008/6:34:37+2:00 |
Bonjour, je crois que j'ai un travail pour rebol, mais j'ai un sérieux problème. Je croyais que read/lines ouvriat un fichier une ligne a la fois. Vu la grosseur (1900 Mo) je croyais pouvoir utiliser cette option. Mais non il semble que rebol charge le fichier en mémoire complètement et ma machine se met à swapper sans fin. L'objectif est justement déliminer des lignes de ce fichier selon une condition qui se retrouve deux lignes plus bas que la première ligne a supprimer dans le cas ou la condition est vrai, donc je dois être en mesure de bufferiser ces deux lignes avan de les écrire dans mon fichier allégé... Ouff difficile à expliquer! J'espère être asser clair. Quelqu'un a un pointeur pour m'aider un peu ou Rebol ne pourra tout simplement pas m'aider dans un tel cas. Merci Martin | |
Philippe | 10-Sep-2008/8:59:28+2:00 |
Salut, Tu peux aller voir cet article : http://www.rebol.com/article/0199.html Et aussi celui là : http://www.rebol.com/article/0281.html ===Philippe | |
cr8825 | 10-Sep-2008/13:25:34+2:00 |
hello, pourrais tu voir si cela te convient ??? foreach line read/lines/direct %tonfichier[ if (condition) [ write/lines/direct %unfichierout ] ] cela te permettra de ne pas toucher a ton fichier d'origine !! on ne sait jamais | |
cr8825 | 10-Sep-2008/13:25:47+2:00 |
hello, pourrais tu voir si cela te convient ??? foreach line read/lines/direct %tonfichier[ if (condition) [ write/lines/direct %unfichierout line ] ] cela te permettra de ne pas toucher a ton fichier d'origine !! on ne sait jamais | |
Rockyboa | 10-Sep-2008/16:56:47+2:00 |
Bonjour, Merci pour les pointeurs. Mon fichier texte a la forme suivante et les données que je dois supprimer ou conserver ont une forme très arbitraire sauf pour la condition de date qui se retrouve toujour 3 lignes plus bas: {AFPC_0 donnée1;donnée2;; donnée3;;;donée4; date; donnée5 } si la date est plus ancienne qu'une valeur fixe je dois supprimer l'entrée complète, sinon je dois copier tous les données. J'imagine que la meilleur approche serait de lire le bloc complet de le mettre en mémoire et ensuite de le copier dans mon nouveau fichier si la condition de date "plus récent que" est respecté. Martin | |
Philippe | 10-Sep-2008/23:50:34+2:00 |
Hello, Je pense que tu as une autre solution, moins rebolienne, mais tout aussi directe : splitter http://www.martinstoeckli.ch/splitter/ C'est un exe windows que j'ai utilisé dans le passé pour découper de gros fichiers de logs (plusieurs 100aines de Mo). Tu splittes ton fichier en bloc plus petits, en rectifiant les "bordures" pour avoir des blocs de données propres. Puis si ton format de date est à peu près standard, tu parses tes différents fichiers par bloc de AFPC_0 jusqu'à la date incluse, et tu testes le contenu du bloc avec la date cible que tu veux. ===Philippe | |
Rockyboa | 11-Sep-2008/2:43:36+2:00 |
Philippe, merci je vais regarder ça mais j'ai quelques autres opérations a faire dans mon fichier. La solution doit aussi être simple, car c'est le end user qui va utiliser le tout. cr8825, même problème avec le rafinement direct, la mémoire monte en fou et j'ai une erreur dans la console de rebol: Out of memory. Je vais donc lire les deux articles de Carl Merci | |
Rockyboa | 11-Sep-2008/9:34:21+2:00 |
Je me suis amusé pas mal ce soir... Je suis loin d'être un développeur, mais ca fonctionne. J'aime pas trop la façon dont je dois vérifier la présence des balises de block 2 fois, si qqun peut m'améliorer, ca serait apprécié. Martin REBOL [ Title: "Nettoie Facturation" DATE: 9-Sep-2008 File: %clean.r Author: "Martin Berard" Version: 1.0 ] if exists? %clean.adx [delete %clean.adx] readport: open/seek/binary %factures.ADX writeport: open/seek/binary %clean.adx size: 64 ; nombre d'octets du buffer block-facture-debut: to-binary "{0_CFPR" block-facture-fin: to-binary "0_CFPR}" block-lu: 0 position-index: 1 print "Esc to cancel" while [not tail? readport][ block-lu: block-lu + 1 data: copy/part at readport position-index (size * block-lu) ;print rejoin ["Block/Octets lus: " block-lu "/" block-lu * size] either none? find data block-facture-debut [ index-debut: none ;print "Début du block non trouvé" ][ index-debut: index? find data block-facture-debut ;print rejoin ["Début du block trouvé à l'index: " index-debut] either none? find data block-facture-fin [ index-fin: none ][ index-fin: index? find data block-facture-fin index-fin: index-fin + length? block-facture-fin ;print rejoin ["Block maintenant complet " index-debut "-" index-fin] ;print "Incription des données avant le block." append writeport copy/part data (index-debut - 1) ; Écrit jusqu'au premier block ;print to-string copy/part at data index-debut at data index-fin if true [ ; Condition à venir append writeport copy/part at data index-debut at data index-fin position-index: position-index + index-fin - 1 block-lu: 0 ] ] ] ] print "Écriture des données de queue" append writeport copy/part data close readport close writeport | |
Didec | 11-Sep-2008/10:23:57+2:00 |
C'étiat assez tentant de s'y essayer alors, voilà le résultat. Pour mes tests, j'ai créé un fichier texte du genre de celui décrit, mais à la place de la date j'ai simplement mis le mot "GOOD". Les accolades sont utilisées pour trouver le block de données à vérifier. Enjoy! Rebol [] ; fichier source fsrc: %big-file.txt ; fichier destination fdst: %temp.tmp ; fonction qui décode et vérifie que la date est bonne : A TOI DE LA FAIRE good-date: func [d] [ return "GOOD" = d ] copy-needed: func [fs fd /local ps pd raw data res sz b e bd ed dat] [ sz: 10000 ; taille du block lu raw: #{} ; données brutes lues ps: open/seek/binary fs ; ouverture fichier source pd: open/seek/binary/write fd ; ouverture fichier destination ; parcours du fichier source par tranche de données forskip ps sz [ print "READ----------------" ; lecture d'une tranche dans le fichier source et ajout à la tranche précédente append raw copy/part ps sz ; on utilise une autre variable pour lire la tranche en texte data: as-string raw ; tant qu'on a un block de données entier dans la tranche while [parse/all data [ thru "{" thru "}" to end ]] [ ; on recherche le block de données et la date qui s'y trouve parse/all data [ to "{" b: skip thru newline thru newline thru newline bd: to newline ed: (dat: copy/part bd ed) thru "}" e: (res: copy/part b e) ] ; si la date est bonne (en enlevant les "blancs" avant/après) if good-date trim dat [ print "GOOD !!!!!!!!!!!!!" ; on ajoute le block de données (et un saut de ligne) dans le fichier de destination append pd probe join res newline ] ; on enlève le block de la tranche de données lue remove/part data e ] ] close ps close pd ] copy-needed fsrc fdst halt | |
Rockyboa | 11-Sep-2008/16:34:30+2:00 |
DideC. Très bon. J'ai de quoi m'amuser avec ton truc, ce matin quand je me suis lever, je me suis rendu compte que ma boucle while sur mon readport restait fixe donc, je bouclais indéfiniment. Ca va m'averer très pratique, jusqu'à maintenant j'avais que des fichiers de taille respectable qu'on pouvait lire et traficoter en mémoire. encore une preuve qu'il est possible, avec rebol, de faire pratiquement tout. J'ai failli aller m'acheter une machine 64bits avec 8Go de mémoire J'espère que ca servira à d'autre. Encore merci Martin | |
Rockyboa | 12-Sep-2008/4:50:50+2:00 |
DideC, j'ai regarder ton code et, pour un débutant sur Rebol, c'est toute une leçon sur la commande parse que je reçois. J'ai jamais rien vu de pareil dans une autre forme de logiciel de développement. Deux ou trois petites questions concernant ton idée pour mon problème: Je ne suis pas certain de comprendre la différence entre as-string et pourquoi pas to-string pour transfomer raw en data? Peux-tu m'éclairer sur ton deuxieme parse, je le trouve pas trop évident Entre mes block de données que je garde et que je supprime, j'ai d'autre données que je dois conserver, incluant les newlines. Dans la deuxième commande parse, est-il possible d'extraire la position de référence de mon block data losrqu'il fait son to "{" et d'y éxecuter un append du block data à cette position sur mon fichier destination. Bonne journée. | |
Didec | 12-Sep-2008/12:02:52+2:00 |
Parse c'est le "killer app" du langage. Mais c'est pas simple et honnêtement, je suis pas du tout un cador dans le domaine. 'as-string redéfinie une chaine quelconque (les types email! ou url! par exemple sont des chaines) sans la copier. Ca marche aussi pour un binaire (ce ne sera plus le cas dans R3, à cause de l'unicode), il faut seulement penser aux sauts de lignes qui ne sont pas convertis (si on lit un fichier Unix avec Windows, en binaire les sauts sont des LF, en mode texte Rebol les convertis en CRLF ; si on le charge en binaire et qu'on utilise 'as-string, ils restent en LF). >> b == #{410206} >> a == "A^B^F" >> b: #{413025} == #{413025} >> a: as-string b == "A0%" >> head change a "B" == "B0%" >> b == #{423025} >> help as-string USAGE: AS-STRING string DESCRIPTION: Coerces any type of string into a string! datatype without copying it. AS-STRING is a native value. ARGUMENTS: string -- (Type: any-string) 'to-string fait de même, mais réalise une copie. Je t'encourage a étudier le dialecte 'parse dans la doc (il y en a une : http://rebol.com/docs/core23/rebolcore-15.html ). C'est un bon début même si ça ne suffit pas. Fait des tests dans la console, entraine toi. Dans mon deuxième block il y a des 'set-word (b: e: bd: ed. Dans le dialecte 'parse, cela signifie que je créé un mot qui pointe à l'endroit où se trouve le parsing dans la chaine source. Comme si je faisais : >> a: "un texte à lire" == "un texte à lire" >> b: at a 10 == "à lire" 'b pointe au 10ème caractère de la chaine que 'a pointe à son début. En l'occurence, je mémorise le début du block de données (avant le "{") dans 'b (pour Begin) et la fin (après le "}" dans 'e (pour End). Même chose avec 'bd et 'ed, pour la ligne contenant la date. Enfin, ce qui se trouve entre parenthèse, c'est du code Rebol qui est exécuté lorsque les règles précédentes du parsing ont été passée et que 'parse arrive là. | |
Didec | 12-Sep-2008/12:14:30+2:00 |
Je vais tenter d'expliquer en clair ma deuxième règle du parsing : [ to "{" ; aller jusqu'avant la chaine "{" (soit avant le "{" b: ; définir que le mot 'b pointe à l'endroit courant dans la chaine skip ; sauter 1 caractère thru newline ; aller jusqu'après la chaine CR+LF thru newline ; aller jusqu'après la chaine CR+LF thru newline ; aller jusqu'après la chaine CR+LF bd: ; définir que le mot 'bd pointe à l'endroit courant dans la chaine (soit le début de la troisième ligne) to newline ; aller jusqu'avant CR+LF ed: ; définir que le mot 'ed pointe à l'endroit courant dans la chaine (soit la fin de la 3ème ligne) (dat: copy/part bd ed) ; code Rebol : on copie le bout de chaine entre 'bd et 'ed et on affecte le résultat au mot 'dat thru "}" ; aller jusqu'après la chaine "}" (soit la fin du block de données) e: ; définir que le mot 'e pointe à l'endroit courant dans la chaine (res: copy/part b e) ; code Rebol : on copie le bout de chaine entre 'b et 'e et on affecte le résultat au mot 'res ] J'espère que c'est plus clair. Si j'étais bon en parsing, j'aurais surement fait le test de la date et l'ajout du résultat dans le fichier de sortie dans le block de règles de 'parse, mais c'est bien plus filou à faire. Mais ça permettrait d'intégrer les données que tu dois reproduire tel quel. La difficulté étant qu'il ne faut pas oublier qu'on traite les données par bout : on peut avoir le début d'un block "{" sans la fin (pas encore lue). Je vais voir si j'ai le temps d'y réfléchir. | |
Rockyboa | 14-Sep-2008/7:36:17+2:00 |
Salut, Avec tes bons conseils et pas mal de lecture et d'essaie sur la commande parse j'ai ceci qui fait exactement ce que je voulais. [/code] Rebol [] ; fichier source fs: %factures.ADX ; fichier destination fd: %clean.adx if exists? %clean.adx [delete %clean.adx] limite: 1/1/2006 good: 0 bad: 0 balise-in: "{0_CFPR" balise-out: "0_CFPR}" block-lu: 0 block-traite: 0 sz: 10240 ; taille du block lu en Octets raw: #{} ; données brutes lues ps: open/seek/binary/read fs ; ouverture fichier source pd: open/seek/binary/write fd ; ouverture fichier destination good-date: func ["Compare la date limite du block de données" d [string!] "Date du block" date-limit [date!] "Date péremption" ] [ annee: copy/part d 4 mois: copy/part at d 5 at d 7 jour: copy/part at d 7 at d 9 either date-limit > to-date (rejoin [jour "/" mois "/" annee]) [return false] [return true] ] ; parcours du fichier source par tranche de données print rejoin ["élimines tous les factures plus vieilles que " limite] forskip ps sz [ block-lu: block-lu + 1 append raw copy/part ps sz data: as-string raw while [parse/all data [ thru balise-in thru balise-out to end ]] [ ;Aussitot que j'ai au moins un block valide, traite les tous block-traite: block-traite + 1 parse/all data [ to balise-in debut: (append pd copy/part data -1 + index? debut) ; trouve la balise-in et assigne position a 'debut et copie tous ce qui était devant thru newline thru newline thru newline copy block-date to ";"; me positionne devant la date et assigne la ligne thru balise-out fin:] ; trouve la balise-out et assigne position a 'fin either good-date trim block-date limite [ good: good + 1 append pd copy/part at data index? debut at data index? fin ][ bad: bad + 1 parse/all fin [ some ["^M" | "^(line)"] fin:] ; Enlève les Linefeed et Enter après les mauvais block ] remove/part data fin ; enlève tous ce qui à été traité ] ; ferme le while ] ; ferme le forskip append pd raw ; ajoute le reste du fichier à la fin print rejoin ["Block-facture traités (Accepter/rejeter): " block-traite " (" good ")/(" bad ")"] close ps close pd halt [/code] Suggestions, ca prend environ 30 secondes a traiter au lieu de 3 heures avec le même equipement. Pas mal content du résultat. J'ai appris beaucoup sur parse... Qqun peut m'expliquer pourquoi parse "aaabbb" [some "a" "b"] me retourne false... Je m'attendais a autre chose. Bon je tranfère ca en fonction et je me fait un interface graphique. J'ai jamais encore travailler avec View. J'ai bien hate d'essayer. Martin | |
Didec | 15-Sep-2008/10:04:27+2:00 |
>> parse "aaabbb" [some "a" "b"] == false ; ta règle dit "U ou plusieurs 'a', suivi de 'b'" ; donc elle ne cherche qu'un seul 'b' : >> parse "aaab" [some "a" "b"] == true ; si tu veux permettre plusieurs 'b' en respecant l'ordre (les 'b' après les 'a'): >> parse "aaabbb" [some "a" some "b"] == true >> parse "ababbb" [some "a" some "b"] == false ; ou autrement, permettre plusieurs 'a' ou 'b' sans ordre particulier : >> parse "aaabbb" [some ["a" | "b"]] == true >> parse "ababbb" [some ["a" | "b"]] == true Une remarque sur ton code. Pour les 'copy, tu utilise l'index de 'debut ou 'fin, soit les positions exprimées en chiffres dans le buffer 'data. Mais 'copy accepte les positions "directes" exprimés par un pointeur dans la chaine ('data, 'debut et 'fin sont des pointeurs dans la chaine). Utiliser les positions, plutôt que les indexs dans le code est un peu plus lisibles, et plus efficace (moins d'instructions). >> data: {la chaine dans le buffer de données} == "la chaine dans le buffer de données" >> debut: at data 12 == "ans le buffer de données" >> fin: at data 26 == "de données" >> copy/part data debut == "la chaine d" >> copy/part data back debut == "la chaine " >> copy/part data index? back debut == "la chaine d" >> copy/part data index? at debut -2 == "la chaine " >> copy/part debut fin == "ans le buffer " >> copy/part back debut fin == "dans le buffer " >> copy/part back debut back fin == "dans le buffer" Quand aux perfs (30s vs 3h) c'est pas mal du tout . Le code reste optimisable et qq secondes peuvent surement être gagnées encore. Mais bon, l'essentiel est que cela fonctionne | |
guest2 | 16-Sep-2008/19:53:10+2:00 |
Attention à la taille du buffer qui peut influer sur les performances. Ca dépend du disque dur et du système d'exploitation employé. Sur ma machine du boulot, j'ai des perfs optimales avec un buffer de 8ko ou 16ko REBOL [] f: open/seek/binary %large.dta foreach len [64 128 256 1024 2048 4096 8192 10240 16384 32768 65536 131072] [ f/state/index: 0 ;*** Problème quand on emploie read-io : ;*** apparement c'est un bug, par défaut l'index est à 1 ;*** du coup, le premier octet n'est jamais lu buff: make binary! len + 1 ;*** Encore un bizarerie, si le buffer a exactement ;*** la taille voulue, read-io lit un octet de moins n: 0 recycle t: now/time/precise while [0 < read-io f buff len] [n: n + 1 clear buff f/state/index: f/state/index + len] print [len tab v: now/time/precise - t tab v / n tab n] ] close f halt buffer temps cumulé temps par read nombre de read 64 0:00:01.718 0:00:00.000005196 330581 128 0:00:00.875 0:00:00.000005293 165291 256 0:00:00.437 0:00:00.000005287 82646 1024 0:00:00.125 0:00:00.000006049 20662 2048 0:00:00.063 0:00:00.000006098 10331 4096 0:00:00.047 0:00:00.000009097 5166 8192 0:00:00.015 0:00:00.000005807 2583 10240 0:00:00.016 0:00:00.00000774 2067 * 16384 0:00:00.015 0:00:00.000011609 1292 32768 0:00:00.016 0:00:00.000024767 646 65536 0:00:00.015 0:00:00.000046439 323 131072 0:00:00.032 0:00:00.00019753 162 Dans le test ci-dessus, j'ai utilisé un fichier de 20 Mo On voit que sur ma machine ton buffer peut être étendu à 16ko au lieu de 10240 octets. Le gain n'est pas énorme mais bon ça vallait le coup de vérifier. Autre constatation, utiliser des buffers inférieurs à 8ko est carrément catastrophique. | |
guest2 | 16-Sep-2008/19:54:14+2:00 |
là c'est mieuxbuffer temps cumulé temps par read nombre de read 64 0:00:01.718 0:00:00.000005196 330581 128 0:00:00.875 0:00:00.000005293 165291 256 0:00:00.437 0:00:00.000005287 82646 1024 0:00:00.125 0:00:00.000006049 20662 2048 0:00:00.063 0:00:00.000006098 10331 4096 0:00:00.047 0:00:00.000009097 5166 8192 0:00:00.015 0:00:00.000005807 2583 10240 0:00:00.016 0:00:00.00000774 2067 * 16384 0:00:00.015 0:00:00.000011609 1292 32768 0:00:00.016 0:00:00.000024767 646 65536 0:00:00.015 0:00:00.000046439 323 131072 0:00:00.032 0:00:00.00019753 162 | |
guest2 | 16-Sep-2008/22:14:24+2:00 |
Bon j'avais du temps à perdre. (Chez moi cette version est presque 10 fois plus rapide) REBOL [] ; fichier source fs: %factures.ADX ; fichier destination fd: %clean.adx if exists? %clean.adx [delete %clean.adx] limite: 20060101 good: 0 bad: 0 balise-in: "{0_CFPR" balise-out: "0_CFPR}" block-lu: 0 block-traite: 0 len: 16 * 1024 ;*** taille du block lu en Octets raw: make string! len + 1 ;*** données brutes lues out: make string! len + 1 ;*** données en sortie ps: open/seek/read fs ;*** ouverture fichier source pd: open/seek/write fd ;*** ouverture fichier destination ps/state/index: 0 pd/state/index: 0 ;*** parcours du fichier source par tranche de données print ["élimines tous les factures plus vieilles que " limite] while [0 < read-io ps raw len] [ block-lu: block-lu + 1 parse/all data: raw [ any [ [to balise-in | to end] debut: (insert tail out copy/part data debut) balise-in 3 3 [thru newline] copy block-date to ";" thru balise-out fin: ( block-traite: block-traite + 1 either limite <= to integer! trim block-date [ good: good + 1 insert tail out copy/part debut fin ][ bad: bad + 1 parse/all fin [some crlf fin:] ;*** Enlève les Linefeed et Enter après les mauvais block ] ) :fin data: debut: ] ] write-io pd out length? out pd/state/index: pd/state/index + length? out clear out ps/state/index: ps/state/index - 1 + index? debut clear raw ] close ps close pd print rejoin ["Block-facture traités (Accepter/rejeter): " block-traite " (" good ")/(" bad ")"] halt | |
guest2 | 16-Sep-2008/23:00:46+2:00 |
et c'est encore optimisable, il suffit de tester la longueur du buffer out avant de faire le write-io pour éviter d'écrire de trop petits blocks en une seule fois. | |
Didec | 17-Sep-2008/9:26:49+2:00 |
Intéressant. Il doit y avoir une relation entre la taille du buffer et celle des clusters du disque (ainsi appelé sous Windows du moins), c'est-à-dire le block d'information de base sur le disque. Si ça n'a pas changé, les disques utilisent des blocks de 512o pour stocker les données, mais les OS (Win) utilisent un ensemble de ces blocks (2, 4, 8, 16 ou plus) appelé "cluster". Un fichier occupant toujours au moins un cluster, même si ça taille est inférieur à celle du cluster, d'où de la place disque perdue. Sur ma machine le cluster est à 4Ko (4096o) et il est sans aucun doute préférable de faire des lectures avec un buffer de taille multiple de cette valeur. | |
Rockyboa | 17-Sep-2008/20:26:44+2:00 |
Qu'est ce que je m'amuse.. Bon je suis entrain de creer un interface VID, et encore une fois c'est la premiere fois... J'aimeais bien avoir un progress en fonction de la grosseur lu / grosseur total. Grace a help sur mon objet progress j'ai découvert que le mot data est responsable, étrangement initialisé en integer! je m'attendais donc à des valeurs entre 0 et 100 mais non! Est-ce que je pourrais repasser une valeur à mon progress directement de ma fonction et refraichir mon objet progress? Martin | |
Didec | 18-Sep-2008/9:24:55+2:00 |
Pour un progress c'est une valeur entre 0 et 1 (100%), comme les sliders et scroller d'ailleurs.view layout [ p: progress btn "+" [set-face p min 1 p/data + 0.1] btn "-" [set-face p max 0 p/data - 0.1] slider 200x16 [set-face p face/data] ] | |
Login required to Post. |