Codeoptimierung (ASCII Zeichen <31 erkennen)

Mein bisheriger Code:
'Prüfung auf unzulässige Zeichen ASCII-Wert bis &h20

for j=0 to 31
  if InStr(s,chr(j))>0 then
    if j=9 then //TAB
      MeldungHinzu("Die Zeile enthält ein unzulässiges Tabulator Zeichen")
    else
      MeldungHinzu("Die Zeile enthält ein unzulässiges Zeichen.")
    end
  end
next

Heute eine Datei mit über 13 Millionen Zeilen überprüft und erkannt, dass ich hier Optimierungsbedarf habe :slight_smile:

Der Hinweis auf Tab könnte auch raus, das hält nur auf.
Vor das letzte “end” könnte ich noch ein Exit setzen, hilft aber wenig, da die Routine eher selten überhaupt Fehler feststellt.

Hat jemand eine deutlich schnellere Lösung?
Vielleicht Irgendwas mit Memoryblock oder so?

Am schnellsten mit C als Plugin/Library.
In Xojo flott mit Ptr.

Siehe
https://www.mbsplugins.de/archive/2021-09-05/Optimizing_a_Xojo_function/monkeybreadsoftware_blog_xojo

Unten die Schleife mit Ptr nehmen und if anpassen.

1 Like

Hallo Stefan,

du könntest alternativ auch erstmal folgendes prüfen. Jedes Zeichen durchgehen und prüfen, ob der der Wert <32 ist. Und in dieser Methode alle #pragma backgroundtask usw. ausschalten. Könnte mir vorstellen, dass es schneller ist als für jedes gesuchte Zeichen die Instr-Funktion aufzurufen.

Spielt Unicode eine Rolle bzw. welches Encoding haben die Zeichen?

Wow. Das bringt einiges!
Mein Problem ist, dass der zu prüfende Text UTF-8 codiert ist. Damit kommt eine byteweise Prüfung wohl doch nicht in Frage.
Ich präzisiere mein Problem sicherheitshalber mal.
Gegeben ist eine Textdatei, die zeilenweise auf Einhaltung von Vorgaben überprüft werden soll. Eine Vorgabe ist: Keine Zeichen mit einem Wert <&h20

Mein Favorit wäre derzeit Split in abgewandelter Variante.
Allerdings ist die Variante langsamer als meine bisherige.
Vorher (712Ticks):

for i=0 to 150000
  s=Zeilen(i)
  
  'Prüfung auf unzulässige Zeichen ASCII-Wert bis &h20
  for j=0 to 31
    if InStr(s,chr(j))>0 then
      if j=9 then //TAB
        MeldungHinzu
      else
        MeldungHinzu
      end
    end
  next
next

Nachher (900Ticks):

for i=0 to 150000
  s=Zeilen(i)
  'Prüfung auf unzulässige Zeichen ASCII-Wert bis &h20
  
  for j=0 to 31
    parts=s.Split(chr(j))
    if parts.Ubound>1 then
      MeldungHinzu
      exit
    end
  next
next

Das hat also nichts gebracht. Schade.
Ich glaube, auf die Weise wird das nichts. Binärprüfung wird wegen UTF8 wohl auch ausfallen wegen der variablen Größe der einzelnen Zeichen. Oder habe ich etwas übersehen?

Weitere Tipps sind willkommen.

Riesige Dateien würde ich Stück-weise einlesen.

Ich habe mal vor ewigen Zeiten ein Regex gemacht, um non-printable Zeichen herauszufischen:

dim theString as String
dim searchpattern as String = "[^\x{0009}\x{000A}\x{000D}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFF}]+"
dim rx as RegExMBS = New RegExMBS
if rx.Compile(searchPattern) and rx.Study() then
  theString = rx.ReplaceAll(theXML.ToString, "")
end if
2 Likes

Doch, das geht. Bei UTF-8 sind alle Sonderzeichen eh mit Codes >=128 codiert.

RegEx ist aber auch sehr flott.
Da musst du ja nur schauen, ob er was findet und wenn nicht, dann ist es okay.

Vielleicht mit Replace/ReplaceAll jedes unzulässiges Zeichen ersetzen und die Längen der Strings vergleichen.
Das kann man auf den ganzen Block anwenden oder Teil Abschnitte oder je Zeile.

Mein Problem ist, dass ich nicht wissen möchte, ob in der Gesamtdatei ein Zeichen kleiner &h20 ist sondern auch noch die Zeile angeben möchte.
Mir ist das auch erst aufgefallen, als sich ein Anwender mit einer Datei mit über 14Millionen Zeilen ankam.

Damit habe ich dann immer eine Schleife, die ich 14Millionen mal durchlaufen muss. Jedes Mal mit einer Prüfung auf unzulässige Zeichen. Derzeit mit jedem der 32 Zeichen separat.
Eine Reduzierung auf einen Durchlauf mit RegEx könnte da durchaus helfen.
Werde ich mal testen

Tolle Ideen alles!
Könntest du ein Testprojekt (aktueller, langsamer Stand) mit Testdaten zur Verfügung stellen, Stefan? Dann könnte man etwas spielen… :wink:

Ich habe mal diesen Code getestet:

'Prüfung auf unzulässige Zeichen ASCII-Wert bis &h1F (ohne &0hA und &h0D) 
dim i,bis,j,t as integer
dim theString,s,Messung(-1),Meldung(-1) as String
dim searchpattern as String = "[\x{0000}\x{0001}\x{0002}\x{0003}\x{0004}\x{0005}\x{0006}\x{0007}\x{0008}\x{0009}\x{000B}\x{000C}\x{000E}\x{000F}\x{0010}\x{0011}\x{0012}\x{0013}\x{0014}\x{0015}\x{0016}\x{0017}\x{0018}\x{0019}\x{001A}\x{001B}\x{001C}\x{001D}\x{001E}\x{001F}]"
dim rx as RegExMBS = New RegExMBS
if rx.Compile(searchpattern) then
  
  bis=UBound(Zeilen)
  t=System.Ticks
  for i=0 to 150000//bis
    s=Zeilen(i)
    if rx.Execute(s,0)>0  then
      Meldung.append "Die Zeile "+str(i)+" enthält ein unzulässiges Zeichen."
    end
  next
end if
Messung.append system.Ticks-t

Mein Test ergab für meine bisherigen Varianten etwa 900 bzw. 650 Ticks. Mit der RegEx Variante bin ich unter 50 Ticks gekommen. Das ist eine Verbesserung um mehr als Faktor 10.

Zum testen kann man den Code etwas abwandeln und folgendes einfügen:

dim Zeilen(150000) as String
Zeilen(5)="w"+chr(9)+"w"
Zeilen(15)="w"+chr(11)+"w"
Zeilen(25)="w"+chr(18)+"w"
Zeilen(35)="w"+chr(19)+"w"
Zeilen(45)="w"+chr(2)+"w"
Zeilen(55)="w"+chr(3)+"w"
Zeilen(65)="w"+chr(4)+"w"
Zeilen(75)="w"+chr(5)+"w"
Zeilen(85)="w"+chr(6)+"w"
Zeilen(95)="w"+chr(7)+"w"
Zeilen(115)="w"+chr(8)+"w"
Zeilen(225)="w"+chr(14)+"w"
Zeilen(335)="w"+chr(15)+"w"

Dank an alle, die hier mit Tipps beigesteuert haben!

Willst du \x{0009} nicht rauch nehmen? Das ist ja ein Tab Zeichen.

Ja, aber separat, weil ich da in der Meldung auf TAB hinweise. Das kommt häufiger vor und das möchte ich dann dem Anwender so melden, dass er es möglichst versteht.
Hätte ich für das Beispiel wieder reinnehmen sollen, habe ich vergessen.

Wieviel Zeichen stehen in eine Zeile, immer gleich?

Die Zeilen können unterschiedlich lang sein. Vorgesehen war mal (1995) maximal knapp 256 Zeichen. Das beachte ich aber nicht. Somit können die Zeilen auch deutlich länger sein.