[VB6] Regular Expressions parsing

Pagina: 1
Acties:

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

Topicstarter
(overleden)
Tja, beetje brakke titel, weet bij GoT niet hoe ik het anders in 1 zin moet samenvatten.

Het probleem:

Ik heb een bestand (een joekel mag ik wel zeggen) met data. Platte ASCII file. Daarin zitten records van 320 tekens lang. Maar wel allemaal achter elkaar, zonder carriage returns en dergelijke. Dus 1 hele lange string:

*blabla...bla*blabla...bla*blabla...bla

Nu begint ieder record met een "*", maar in de records zelf kunnen ook sterretjes zitten.

Het bestand wil ik nu (voorlopig, er moet nog meer mee gebeuren) "converteren". Ik wil de records (320 tekens ieder dus) scheiden met een CrLf. Dus de uiteindelijke file moet er zo uit zien:

*blabla....bla
*blabla....bla
*blabla....bla


Mijn idee/redenatie:
Omdat het soms om grote, soms om kleine bestanden gaat wil ik niet ieder record apart lezen. Ik lees heel het bestand in een string in 1 klap. Nu deed ik eerst een replace(myString,"*",vbCrLf) om de records te scheiden. Werkt (hoewel soms wat traag) prima. Maar: in de records kunnen ook sterretjes voorkomen. Die wil ik dus niet perongeluk replacen.

Als het om een éénmalige conversie ging, dan las ik het wel netjes in stapjes van 320 bytes in een string en outputte ik die weer. Maar deze conversie moet vaak en veel uitgevoerd worden op bestanden variërend van 300Kb tot 76Mb.

Een bestand van 76Mb in 1 klap in een string lezen is geen probleem (als je de code wil hebben wil ik 'm wel posten, maar ik ben niet op zoek naar posts/commentaar hierover).

De replace wil alleen nog wel eens eruit klappen op een 76Mb string (out of string space)...Wiedes. Maar ook in blokken van 4Mb of 10Mb of whatever kan ik mijn probleem oplossen. Gaat het hier ook niet om.

Ik wil naar een regular expression toe. Eentje die een * matched, dan 319 tekens verder leest en dan weer een * matched.

Ik wil dus mijn string in die regex frotten, regex.replace erop los laten en dan het resultaat wegschrijven.

Mijn "test" code:
Om dit idee te testen heb ik even wat code in elkaar geflanst. Zie hier:
Visual Basic 6:
1
2
3
4
5
6
7
8
9
10
11
Private Function RegTest(sTMP As String, sReplStr As String) As String
    Dim objRE As Object
    
    Set objRE = CreateObject("VBScript.RegExp")
    objRE.IgnoreCase = True
    objRE.Global = True
    objRE.Pattern = "\*[(\s|\S)]{4}"
    objRE.IgnoreCase = True
    RegTest = objRE.Replace(sTMP, sReplStr)
    Set objRE = Nothing
End Function


En de aanroep:

Visual Basic 6:
1
Debug.Print RegTest("*D*it is*een test*bl*aat", "@")


Wat dit zou moeten doen:
Tja...zou moeten...Wat ik graag zou willen ;) : Zoals ik mijn regex pattern nu heb geschreven lees ik er (volgens de regex help) het volgende:

"match een * en 4 willekeurige (non)whitespace tekens

Even uitgesplitst, zoals ik het redeneer:
• \* --> Match een sterretje (\ is escape teken)
• [(\s|\S)] --> Match een whitespace, tab etc (\s) of (|) een non-whitespace (\S)
• {4} --> en dat 4 tekens lang

Dat zou dus betekenen dat als ik mijn functie aanroep zoals in gegeven voorbeeld ik terug zou moeten krijgen: @D*it is@een test*bl@aat

Zoals je ziet verwacht ik dus alleen * gereplaced op plaatsen waar de positie een veelvoud van 4 is (later dus 320).

Wat ik echter terug krijg is het volgende: @ is@test@at
Alle sterretjes vervangen of foetsie...

Korte samenvatting
Hoe kan ik in een string alleen tekens op een bepaalde positie (veelvoud van x, dus op b.v. iedere 320-ste positie) vervangen door een ander teken? En hoe schrijf ik dat regex pattern dan?

edit:

Oh, voor de syntax van regex gebruik ik de documentatie van MS
Ik ben dus ook niet op zoek naar http://www.regxlib.com/ en andere posts omdat deze allemaal met PHP-regex werken (en deze werkt wat anders zoals ik het begrijp (correct me if I'm wrong))

/me Waarschuwing: Lees alsjeblieft goed de post voordat je met suggesties komt, ik probeer deze draad "zo schoon mogelijk" te houden.

...en ondertussen stoei ik even verder op zoek naar de oplossing ;)

[ Voor 27% gewijzigd door RobIII op 26-11-2003 17:22 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Verwijderd

VB's replace functie heeft ook een Start en een Count argument. Je zou kunnen denken aan een dergelijke for-loop:

code:
1
2
3
4
5
Dim i As Integer

For i = 1 To StringSize Step 320
    Replace(MyString, "*", vbCrLf, i, 1)
Next


Ik weet niet zoveel van Regular expressions parsing, maar de bovenstaande methode lijkt mij de snelste. Ik weet trouwens zo net nog niet of het in dit geval wel sneller is om de hele file in een string te lezen. Misschien zou je eens kunnen benchmarken, om te zien of het niet sneller is om stukjes uit te lezen en er Carriage Returns tussen te zetten.

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

Topicstarter
(overleden)
Een paar opmerkingen:
• Met een integer ga ik er niet komen ;) /flauw
• De hele file in 1 klap inlezen is (gebenchmarked) sneller
• Er moet (uiteindelijk) nog meer gereplaced worden /me , en ik wil dat in 1 (of een paar) regex calls oplossen.

Verder zou je code wel degelijk moeten werken...Ware het dus niet dat ik niet door heel die string heen wil "loopen". Volgens mij moet de regex niet al te moeilijk te schrijven zijn, en dit is toch waar een regex juist zo sterk in is? :?

Overigens: Zijn weet iemand een tool om zo'n regex bij elkaar te kunnen klikken ofzo? Zo'n regex is (IMHO) niet echt simpel te schrijven, dit moet toch makkelijker kunnen m.b.v. een tooltje ofzo?
edit:
Hmmm...alvast 1 gevonden: http://www.phpedit.net/products/RegExpEditor/ ...is alleen weer voor PHP voor zover ik zie :( daar moet ik ze nog steeds zelf in schrijven...

/me Zo wil ik straks ook in bepaalde "velden" komma's gaan vervangen door punten. Maar dan dus weer alleen op bepaalde posities...

edit:
Je code werkt overigens ook niet... Je replaced namelijk 1 byte met 2 bytes... Dan ga je dus "scheef lopen" :+
Ik begrijp je idee en waar je naar toe wou, en ik weet dat het anders op te lossen is. Maar ik wil gewoon die p*kke regular expression voor mekaar krijgen :)


En me post is nu lekker duidelijk :P NOT :P

[ Voor 51% gewijzigd door RobIII op 26-11-2003 17:51 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

ik snap het niet helemaal:
Dat zou dus betekenen dat als ik mijn functie aanroep zoals in gegeven voorbeeld ik terug zou moeten krijgen: @D*it is@een test*bl@aat
je matched 4 tekens na je eerste *, maar ik zie nu toch meer dan 4 tekens tussen je @-tjes zitten; wou je zeggen dat het minimaal 4 tekens moeten zijn (en dus ook minimaal 320 tekens in je uiteindelijke bestand)? Uit je eerste verhaal meende ik op te maken dat het om een * met daarna precies 320 tekens zou gaan...

Verder wat betreft een klikkerdeklik proggie voor regexpen - voor simpele matches zal dat nog wel lukken, maar als je alles zo in elkaar zou kunnen klikken, dan waren programmeurs natuurlijk overbodig :P
Het is een beetje gewenning, maar zodra je de logica een beetje doorhebt schrijf en lees je een regexp net zo makkelijk als willekeurige andere code hoor ;)

qua jouw regexp klopt er iets niet:

code:
1
[(\s|\S)]


dit is complete onzin omdat je met je blokhaken een characterclass aanduidt. Als je gewoon op alles wilt matchen kan je gewoon een . (dot) gebruiken - die matched alles behalve newlines, of desnoods [.\n] of met de /s modifier indien ondersteund.

Dit werkt bij mij gewoon (javascript):

JavaScript:
1
2
3
4
var a = '*D*it is*een test*bl*aat';
var re = /\*(.{4})/g
var b = a.replace(re,'@$1');
alert(b); // '@D*it is@een test@bl*aat';

Intentionally left blank


  • Spider.007
  • Registratie: December 2000
  • Niet online

Spider.007

* Tetragrammaton

ik had een lange reply getikt maar bedenk me zojuist dat ik je 1e voorstel van je regexp niet begrijpt.... Kun je nogmaals een omschrijving van de regexp geven?

edit:
de post van crisp bevat alles uit mijn lange reply al.. hij heeft echter hetzelfde vraagstuk als ik heb.. wat wil je nu precies bereiken met die regexp :?

[ Voor 35% gewijzigd door Spider.007 op 26-11-2003 18:57 ]

---
Prozium - The great nepenthe. Opiate of our masses. Glue of our great society. Salve and salvation, it has delivered us from pathos, from sorrow, the deepest chasms of melancholy and hate


  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

aanvulling: ik lees nu dat je wilt dat de volgende match bij een veelvoud van 4 begint; in perl en javascript kan je daarvoor de lastIndex property opvragen binnen een lus en eventueel aanpassen; geen idee of VB die functionaliteit ook ondersteund...

Intentionally left blank


  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

Topicstarter
(overleden)
Hmmm...Mijn voorbeeld klopt niet helemaal nee ;)
Ik bedoel dus idd op precies 320 tekens zoals je al zegt crisp..

Even over je javascript: /g is global he? Dus dan hoef ik in VB alleen maar "/\*(.{4})" als pattern te gebruiken, toch? (en i.p.v. die 4 dus straks 320). Overigens mis ik de ' om je re heen... dat snap ik nou nooit met die regular expressions :? hoewel ik javascript toch best aardig beheers...

Ik ga het morgen proberen, heb helaas nu geen tijd...Je voorbeeld lijkt iig te kloppen...Mijn voorbeeld had niet netjes op iedere 4e positie een *, maar dat is straks in het bestand 100% zeker zo. Het ging er mij om dat ik duidelijk kon zien dat 'ie op iedere 4e positie zou matchen en andere niet.

Oh, en
. Matches any single character except a newline character.
had ik over het hoofd gezien in de MS documentate |:( dus vandaar mijn ranzige (\s|\S) oplossing :+

[ Voor 33% gewijzigd door RobIII op 26-11-2003 20:17 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

vergeet niet dat een replace je gehele match overschrijft; vandaar dat ik de .{4} capture en in de replace meeneem. / / is trouwens in JS een RegExp constructor, vandaar dat daar geen quotes omheen gaan, ik had het ook zo kunnen neerzetten:

JavaScript:
1
var re = new RegExp('\*(.{4})', 'g');


note trouwens ook het escape-teken voor de * - die heeft anders een speciale betekenis.

Al met al, zeker als je capturing gebruikt, ben ik echter bang dat de performance op een string van enkele megabytes niet zo geweldig zal zijn. Over het algemeen zijn de normale stringfuncties toch een stuk sneller - tenzij je er uiteindelijk heel ingewikkelde dingen mee wilt gaan doen...

Intentionally left blank


  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

Topicstarter
(overleden)
Laatste klein probleempje... Ik heb het nou werkend (met dank aan crsip _/-\o_ ) maar ik moet nog 1 regex verzinnen:

In diezelfde 320 chars moet ik alle punten vervangen door komma's, behalve tussen de posities (bijvoorbeeld) 160 en 183. Ik snap er werkelijk geen fluit van hoe ik dat nou weer voor elkaar krijg...

Enige hulp zou dan ook zeer welkom zijn.

Ik weet iig dat ik met ^ iets uitsluit, maar hoe geef ik dan zo'n bereik aan?

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


  • Limhes
  • Registratie: Oktober 2001
  • Laatst online: 09-04 16:10
RobIII schreef op 28 november 2003 @ 12:31:
Laatste klein probleempje... Ik heb het nou werkend (met dank aan crsip _/-\o_ ) maar ik moet nog 1 regex verzinnen:

In diezelfde 320 chars moet ik alle punten vervangen door komma's, behalve tussen de posities (bijvoorbeeld) 160 en 183. Ik snap er werkelijk geen fluit van hoe ik dat nou weer voor elkaar krijg...

Enige hulp zou dan ook zeer welkom zijn.

Ik weet iig dat ik met ^ iets uitsluit, maar hoe geef ik dan zo'n bereik aan?
Waarom in gods naam met een regexp?

code:
1
2
3
4
5
6
7
8
for each string
  for i=1 to 160
    if i="." then string(i)=","
  next
  for i=183 to 320
    if i="." then string(i)=","
  next
end

[ Voor 5% gewijzigd door Limhes op 28-11-2003 13:24 ]


  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

Topicstarter
(overleden)
Limhes schreef op 28 november 2003 @ 13:23:
[...]

Waarom in gods naam met een regexp?
<knip>
Doe me een lol, en lees alsjeblieft eerst de startpost even, en met name dan de waarschuwing onderaan diezelfde post...

Ik ga op deze manier toch geen 76 Mb doorlopen?

En wat is er mis met een regexp? Dat ding hebben ze niet voor niks uitgevonden...

[ Voor 32% gewijzigd door RobIII op 28-11-2003 15:20 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


  • Limhes
  • Registratie: Oktober 2001
  • Laatst online: 09-04 16:10
RobIII schreef op 28 november 2003 @ 15:20:
[...]

Doe me een lol, en lees alsjeblieft eerst de startpost even, en met name dan de waarschuwing onderaan diezelfde post...

Ik ga op deze manier toch geen 76 Mb doorlopen?

En wat is er mis met een regexp? Dat ding hebben ze niet voor niks uitgevonden...
Wat denk je dat een regexp doet? Denk je dan niet dat die met stringfuncties werkt? Wat denk je dat meer overhead heeft?

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

Topicstarter
(overleden)
Limhes schreef op 28 november 2003 @ 15:38:
[...]


Wat denk je dat een regexp doet? Denk je dan niet dat die met stringfuncties werkt? Wat denk je dat meer overhead heeft?
Met het gevaar dat dit op een hoop over-en-weer ge-flame gaat opleveren:

Een regexp is vele malen sneller. De replace die ik eerst had, en heb vervangen door de regexp is bijna een factor 10 sneller geworden.

Je kunt zeggen wat je wil, maar dit is nou waar een regexp voor is gemaakt/bedoeld.

Wil je hier over verder gaan, verdiep je dan eerst eens even in de stof...

En JA, als je je "pseudo code" in assembly schrijft ofzo dan issie' (misschien) wel sneller dan een regexp, maar dan nog zijn er efficiëntere methodes om een replace uit te voeren dan jouw manier. Ga je jouw code in VB schrijven dan wens ik je vast een fijne kerst, want voor die tijd issie niet klaar met een bestand van 76Mb ;)

Van een TU/e-er (hoewel elektro dus) had ik eerlijk gezegd meer verwacht... NOFI

En om het dan af te maken: 't zal me worst wezen, ik wil het gewoon met een regexp oplossen. Als ik een boom om wil zagen met een flostouwtje moet ik dat toch ook zelf weten? zie ook deze post.

Nu is me topic toch nog vervuild.....

[ Voor 22% gewijzigd door RobIII op 28-11-2003 17:17 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

RobIII schreef op 28 november 2003 @ 12:31:
Laatste klein probleempje... Ik heb het nou werkend (met dank aan crsip _/-\o_ ) maar ik moet nog 1 regex verzinnen:

In diezelfde 320 chars moet ik alle punten vervangen door komma's, behalve tussen de posities (bijvoorbeeld) 160 en 183. Ik snap er werkelijk geen fluit van hoe ik dat nou weer voor elkaar krijg...

Enige hulp zou dan ook zeer welkom zijn.

Ik weet iig dat ik met ^ iets uitsluit, maar hoe geef ik dan zo'n bereik aan?
Dat gaat je niet lukken in 1 en dezelfde regexp; je zult dan dat gedeelte moeten capturen en opnieuw door een regexp sturen. Wederom ontbreekt het mij hier aan inzicht hoe dat in VB op te lossen is, maar in javascript kan je de captures gewoon doorgeven aan een functie (in php zou je daarvoor de e-modifier moeten gebruiken of replace_callback):

JavaScript:
1
2
content = content.replace(/\*(.{159})(.{24})(.{137})/g,
  function($0,$1,$2,$3){return $1+$2.replace(/\./g,',')+$3+'\r\n';});


mogelijk dat het volgende scenario nog sneller is: elke 320 karakters in een array-element zetten, dan voor elk array-element die puntjes vervangen door komma's en op het laatst een join van alle array-elementen doen met \r\n als koppelteken.
In elk geval denk ik dat je sowieso even wat testcases zou moeten maken om te bepalen wat sneller is. Het klopt inderdaad dat een regExp sneller kan zijn dan gewone stringfuncties, maar vaak valt er dan wel weer een andere constructie te verzinnen die nog sneller is :)

[ Voor 27% gewijzigd door crisp op 28-11-2003 22:55 ]

Intentionally left blank


  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

Topicstarter
(overleden)
Ik kom er niet uit, die regular expressions zijn ook zoooo onleesbaar... :'(

Dit heb ik tot nu toe:
Visual Basic 6:
1
2
3
4
5
6
7
    'Records scheiden met een CrLf (* of X aan het begin is record)
    objRE.Pattern = "\*|\X(.{319})"
    sTMP = objRE.Replace(sTMP, vbCrLf & "$1")
    
    'Puntjes vervangen door komma's
    objRE.Pattern = "\."
    sTMP = objRE.Replace(sTMP, ",")


Maar nu worden dus alle puntjes vervangen door komma's (dit is voorlopig goed, maar ik wil het netjes oplossen). Ik kom er met de documentatie van MS echt niet uit, ik zit al 2 dagen te proberen.

Zoals je ziet vind ik het geen probleem om die regex replace method nog een keer of 2 aan te roepen ofzo als het nodig is. Het gaat me er dus om dat ik alleen de puntjes replace in (b.v.) de posities 1 t/m 115 en 130 t/m 320. Maar dus de puntjes laat staan tussen 116 en 129. Ik hoef het dus niet per sé in 1 pattern te vangen... Iemand die me hier mee op weg kan helpen?

(Overigens is mijn dank aan crisp onmeetbaar! _/-\o_ )

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

Ik zal vanavond eens de VB documentatie mbt regexpen doorkijken om te kijken wat de mogelijkheden zijn.

Intentionally left blank


  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

mmmz, gezien de MSDN documentatie concludeer ik dat in VB de regexp implementatie maar zeer summier is.
Ik kan geen voorbeelden vinden van een functie als replacement argument, je zou wel met de Execute method een collectie matches kunnen aanmaken en daar doorheen itereren, maar dan moet je later die matches weer aan elkaar gaan plakken wat de snelheid niet ten goede zal komen (dan kan je net zo goed alleen string methods gaan gebruiken)...

Intentionally left blank

Pagina: 1