Très gros fichier texte à modifier.
Rockyboa10-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
Philippe10-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
cr882510-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
cr882510-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
Rockyboa10-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
Philippe10-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
Rockyboa11-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
Rockyboa11-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
Didec11-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
Rockyboa11-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
Rockyboa12-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.
Didec12-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à.
Didec12-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.
Rockyboa14-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
Didec15-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
guest216-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.
guest216-Sep-2008/19:54:14+2:00
là c'est mieux
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
guest216-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
guest216-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.
Didec17-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.
Rockyboa17-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
Didec18-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.


Powered by RebelBB and REBOL 2.7.8.4.2