Nützliche String-Funktionen (z. B. StringField_InsertString)

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

Nützliche String-Funktionen (z. B. StringField_InsertString)

Beitrag von AND51 »

Hallo!

Währned meine kleine Schwester mich in der Lobby von "Age of Empires 2" hat warten lassen, fing ich das hier an und führte es später for. Mittlerweile sind 4 kleine Funktionen entstanden, die ins Kapitel "Strings" gehören; und ich hoffe, dass dies Proceduren irgendwann in PB eingebaut werden, auch wenn sie von wem anders noch optimiert werden. :allright:

Wenn ihr noch weitere Proceduren habt, her damit, damit ich sie diesem Post anhängen kann! Dann sind die Procs nicht über den ganzen Thread verteilt.

Code: Alles auswählen

#Franz="Franz jagt im komplett verwahrlosten Taxi quer durch Bayern."
#Fuchs="The quick brown fox jumps over the lazy dog."
#Jack="Jackdaws loves my big sphinx of quarz."


; Eigenen Test-String festlegen:
#Teststring=#franz

Procedure.s StringField_InsertString(String$, InsertString$, Index=1, Separator$=" ") ; by AND51
	Protected n.l, position.q
	If Index > CountString(String$, Separator$) And Not Right(String$, Len(Separator$)) = Separator$
		String$+Separator$
	EndIf
	If FindString(String$, Separator$, 1)
		For n=1 To Index
			position=FindString(String$, Separator$, position+1)
		Next
		ProcedureReturn Left(String$, position)+InsertString$+Separator$+Right(String$, IntQ(Len(String$))-position)
	Else
		ProcedureReturn String$
	EndIf
EndProcedure

Debug StringField_InsertString(#Teststring, "PureBasic", 1, ".")

Procedure.s StringField_SwapFields(String$, FieldA, FieldB, Separator$=" ") ; by AND51
	If FieldA <> FieldB
		String$+Separator$
		If FieldA > FieldB
			Swap FieldA, FieldB
		EndIf
		Protected FieldA$=StringField(String$, FieldA+1, Separator$), FieldB$=StringField(String$, FieldB+1, Separator$)
		FieldA=FindString(String$, FieldA$, 1)
		FieldB=FindString(String$, FieldB$, 1)
		ProcedureReturn Left(Left(String$, FieldA-1)+FieldB$+Mid(String$, FieldA+Len(FieldA$), FieldB-FIeldA-Len(FieldA$))+FieldA$+Right(String$, Len(String$)-(FieldB+Len(FieldB$))+1), Len(String$)-Len(Separator$))
	Else
		ProcedureReturn String$
	EndIf
EndProcedure

Debug StringField_SwapFields(#Teststring, 7, 1)

Procedure.s StringField_RemoveField(String$, Index, Separator$=" ") ; by AND51
	Protected n.l=CountString(String$, Separator$), position.q
	If Index > n
		Index=n
	EndIf
	For n=1 To Index
		position=FindString(String$, Separator$, position+1)
	Next
	ProcedureReturn Left(String$, position)+Right(String$, Len(String$)-(position+Len(StringField(String$, Index+1, Separator$)))-Len(Separator$))
EndProcedure

Debug StringField_RemoveField(#Teststring, 3)

Procedure.s Space2(Length, Fill$) ; by AND51
	Protected space.s, n.l
	If Not Length % Len(Fill$) And RemoveString(Fill$, " ")
		space=Space(Length*Len(Fill$))
		ReplaceString(space, Space(Len(Fill$)), Fill$, 2)
	ElseIf RTrim(Fill$)
		For n=1 To Length
			space+Fill$
		Next
	Else
		space=Space(Length*Len(Fill$))
	EndIf
	ProcedureReturn space
EndProcedure

Debug Space2(34, "AB") ; 'In Place'-Ersetzung, wenn kein Rest bei Length/Len(Fill$)
Debug space2(3, "PureBasic")
Hinweis: Zum Einheitlichen Testen (fast) aller Proceduren sind die am Anfang befindlichen Konstanten gedacht!!!
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

> und ich hoffe, dass dies Proceduren irgendwann in PB eingebaut werden

zuerst kommt sowas, wenn es praktisch genug ist, ins codearchiv,
und dann hat es evtl. chancen, in die PBOSL oder in ts' Includes aufgenommen zu werden...

eine komplettübernahme in die grundlibs ist vielleicht etwas hochgegriffen... ;)

...just my 2 cents...
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

Beitrag von AND51 »

Hast Recht. Das 'irgendwann' mildert das 'hochgreifen' aber wiederum!

Soll die Community aber nicht davon abhalten, hier etwas zu posten <)
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

> Das 'irgendwann' mildert das 'hochgreifen' aber wiederum!
hast ja recht, die hoffnung stirbt zuletzt. ;)
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8807
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

Hab die Funktionen mal ganz anders programmiert: Mit Pointern.

Ich weiß nicht, ob sie schneller oder besser sind, aber sie funktionieren. Wer
Fehler findet, darf sie verbessern. :wink:

Code: Alles auswählen

Procedure.s StringField_InsertString(String.s, InsertString.s, Index.l, Seperator.s = " ")
  Protected result.s, *c.Character, *sep.Character, a.l = 0, Char.l = 1, ok = #False
  
  *c = @String
  *sep = @Seperator
  While *c\c
    If a = Index
      If Char = 1
        result + InsertString + Chr(*sep\c)
        ok = #True
      ElseIf PeekC(*c - SizeOf(Character)) = *sep\c
        result + InsertString + Chr(*sep\c)
        ok = #True
      EndIf
    EndIf
    
    result + Chr(*c\c)
    If *c\c = *sep\c : a + 1 : EndIf
    *c + SizeOf(Character)
    Char + 1
  Wend
  
  If Index > a : result + Chr(*sep\c) : EndIf 
  If Index => a And ok = #False : result + InsertString : EndIf
  
  ProcedureReturn result
EndProcedure

Procedure.s StringField_SwapFields(String.s, Index1.l, Index2.l, Seperator.s = " ")
  Protected *c.Character, *sep.Character, l1.l, l2.l, a.l = 1, *f1.Character, *f2.Character, result.s
  
  If Index1 = Index2 : ProcedureReturn String : EndIf
  If Index1 > Index2 : Swap Index1, Index2 : EndIf
  If Index1 < 1 Or Index2 < 1 : ProcedureReturn String : EndIf
  
  *c = @String
  *sep = @Seperator
  *f1 = *c
  While *c\c
    If *c\c = *sep\c
      a + 1
      If a = Index1 : *f1 = *c + SizeOf(Character) : EndIf
      If a = Index2 : *f2 = *c + SizeOf(Character) : EndIf
    Else
      If a = Index1 : l1 + 1 : EndIf
      If a = Index2 : l2 + 1 : EndIf
    EndIf
    *c + SizeOf(Character)
  Wend
  
  *c = @String
  While *c\c
    If *c = *f1
      a = l2
      While a
        result + Chr(*f2\c)
        *f2 + SizeOf(Character)
        a - 1
      Wend
      *f2 - SizeOf(Character) * l2
      *c + SizeOf(Character) * l1
    ElseIf *c = *f2
      While l1
        result + Chr(*f1\c)
        *f1 + SizeOf(Character)
        l1 - 1
      Wend
      *c + SizeOf(Character) * l2
    Else
      result + Chr(*c\c)
      *c + SizeOf(Character)
    EndIf
  Wend
  
  ProcedureReturn result
EndProcedure

Procedure.s StringField_RemoveField(String.s, Index.l, Seperator.s = " ")
  Protected result.s, a.l = 1, *c.Character, *sep.Character, ok = #False
  
  *c = @String
  *sep = @Seperator
  While *c\c
    If *c\c = *sep\c : a + 1 : EndIf
    If a <> Index
      If a <> 2 Or *c\c <> *sep\c Or Index <> 1
        result + Chr(*c\c)
      EndIf
    EndIf
    *c + SizeOf(Character)
  Wend
  
  ProcedureReturn result
EndProcedure

String.s = "Das ist ein Teststring"

Debug "'" + StringField_InsertString(String, "blöder", 0) + "'"
Debug "'" + StringField_InsertString(String, "blöder", 3) + "'"
Debug ""

Debug "'" + StringField_SwapFields(String, 1, 4) + "'"
Debug "'" + StringField_SwapFields(String, 3, 2) + "'"
Debug ""

Debug "'" + StringField_RemoveField(String, 1) + "'"
Debug "'" + StringField_RemoveField(String, 4) + "'"
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

Beitrag von AND51 »

Hi NtQ !

Ersteinmal Danke, dass du dir die Mühe machst. Leider sind deine Procs alle deutlich langsamer als meine!

Code: Alles auswählen

  ms    |   AND   |   NtQ
--------+---------+---------
Insert  |  1.973  |  15.742
Swap    |  1.702  |  16.654
Remove  |  1.372  |  15.072
  • Jeweils 100.000 Durchläufe, mit den Teststrings #Franz, #Fuchs, #Jack (aus meinem 1. Post) auf einem Intel P3 mit 550 MHz
Außerdem sollten die Index' alle angepasst werden. Die Feld-zählung beginnt bei mir beispielsweise immer bei 0. Bei dir jedoch bei 1. Da muss man mal schauen, was man nun nimmt.

Tipp: Du könntest sicherlich mehr Performance aus deinen Procs rausholen, wenn du Sachen wie

Code: Alles auswählen

; SwapFields

If Index1 = Index2 : ProcedureReturn String : EndIf 
If Index1 > Index2 : Swap Index1, Index2 : EndIf 
If Index1 < 1 Or Index2 < 1 : ProcedureReturn String : EndIf 
zu

Code: Alles auswählen

If Index1 = Index2 Or Index1 < 1 Or Index2 < 1 : ProcedureReturn String : EndIf 
If Index1 > Index2 : Swap Index1, Index2 : EndIf
zusammenfasen würdest.
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Benutzeravatar
Kiffi
Beiträge: 10711
Registriert: 08.09.2004 08:21
Wohnort: Amphibios 9

Beitrag von Kiffi »

> Leider sind deine Procs alle deutlich langsamer als meine!

Performance-Test mit eingeschaltetem Debugger können verfälschte
Ergebnisse liefern. Kannst Du das ganze noch einmal ohne Debugger
ausprobieren und die Ergebnisse dann hier posten?

Danke & Grüße ... Kiffi
a²+b²=mc²
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8807
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

Wo hast du eigentlich die Funktion IntQ() her? Und wozu brauchst du die?

Deine StringField_RemoveField()-Procedure funktioniert nicht richtig:

Code: Alles auswählen

Procedure.s StringField_RemoveField(String$, Index, Separator$=" ") ; by AND51
   Protected n.l=CountString(String$, Separator$), Position.q
   If Index > n
      Index=n
   EndIf
   For n=1 To Index
      Position=FindString(String$, Separator$, Position+1)
   Next
   ProcedureReturn Left(String$, Position)+Right(String$, Len(String$)-(Position+Len(StringField(String$, Index+1, Separator$)))-Len(Separator$))
EndProcedure

String.s = "Das ist ein Teststring"

Debug "'" + StringField_RemoveField(String, 0) + "'"
Debug "'" + StringField_RemoveField(String, 3) + "'"
Zu dem Geschwindigkeitsvergleich:
Meine sind tatsächlich langsamer, allerdings kommt es mir so vor als ob
du den Test mit Debugger gemacht hast. Dann sind meine ganz klar viel
langsamer.

Das sind meine Testergebnisse auf einem Pentium 4 Mobile 1.8 GHz:
NicTheQuick:
- Insert: 2213 ms
- Remove: 1623 ms
- Swap: 2203 ms

AND51:
- Insert: 541 ms
- Remove: 290 ms
- Swap: 531 ms
Mit folgendem

Code: Alles auswählen

Procedure.s StringField2_InsertString(String$, InsertString$, Index=1, Separator$=" ") ; by AND51
   Protected n.l, Position.q
   If Index > CountString(String$, Separator$) And Not Right(String$, Len(Separator$)) = Separator$
      String$+Separator$
   EndIf
   If FindString(String$, Separator$, 1)
      For n=1 To Index
         Position=FindString(String$, Separator$, Position+1)
      Next
      ProcedureReturn Left(String$, Position)+InsertString$+Separator$+Right(String$, Len(String$)-Position)
   Else
      ProcedureReturn String$
   EndIf
EndProcedure
Procedure.s StringField2_SwapFields(String$, FieldA, FieldB, Separator$=" ") ; by AND51
   If FieldA <> FieldB
      String$+Separator$
      If FieldA > FieldB
         Swap FieldA, FieldB
      EndIf
      Protected FieldA$=StringField(String$, FieldA+1, Separator$), FieldB$=StringField(String$, FieldB+1, Separator$)
      FieldA=FindString(String$, FieldA$, 1)
      FieldB=FindString(String$, FieldB$, 1)
      ProcedureReturn Left(Left(String$, FieldA-1)+FieldB$+Mid(String$, FieldA+Len(FieldA$), FieldB-FieldA-Len(FieldA$))+FieldA$+Right(String$, Len(String$)-(FieldB+Len(FieldB$))+1), Len(String$)-Len(Separator$))
   Else
      ProcedureReturn String$
   EndIf
EndProcedure
Procedure.s StringField2_RemoveField(String$, Index, Separator$=" ") ; by AND51
   Protected n.l=CountString(String$, Separator$), Position.q
   If Index > n
      Index=n
   EndIf
   For n=1 To Index
      Position=FindString(String$, Separator$, Position+1)
   Next
   ProcedureReturn Left(String$, Position)+Right(String$, Len(String$)-(Position+Len(StringField(String$, Index+1, Separator$)))-Len(Separator$))
EndProcedure


Procedure.s StringField_InsertString(String.s, InsertString.s, Index.l, Seperator.s = " ")
  Protected result.s, *c.Character, *sep.Character, a.l = 0, Char.l = 1, ok = #False
  
  *c = @String
  *sep = @Seperator
  While *c\c
    If a = Index
      If Char = 1
        result + InsertString + Chr(*sep\c)
        ok = #True
      ElseIf PeekC(*c - SizeOf(Character)) = *sep\c
        result + InsertString + Chr(*sep\c)
        ok = #True
      EndIf
    EndIf
    
    result + Chr(*c\c)
    If *c\c = *sep\c : a + 1 : EndIf
    *c + SizeOf(Character)
    Char + 1
  Wend
  
  If Index > a : result + Chr(*sep\c) : EndIf 
  If Index => a And ok = #False : result + InsertString : EndIf
  
  ProcedureReturn result
EndProcedure
Procedure.s StringField_SwapFields(String.s, Index1.l, Index2.l, Seperator.s = " ")
  Protected *c.Character, *sep.Character, l1.l, l2.l, a.l = 1, *f1.Character, *f2.Character, result.s
  
  If Index1 = Index2 : ProcedureReturn String : EndIf
  If Index1 > Index2 : Swap Index1, Index2 : EndIf
  If Index1 < 1 Or Index2 < 1 : ProcedureReturn String : EndIf
  
  *c = @String
  *sep = @Seperator
  *f1 = *c
  While *c\c
    If *c\c = *sep\c
      a + 1
      If a = Index1 : *f1 = *c + SizeOf(Character) : EndIf
      If a = Index2 : *f2 = *c + SizeOf(Character) : EndIf
    Else
      If a = Index1 : l1 + 1 : EndIf
      If a = Index2 : l2 + 1 : EndIf
    EndIf
    *c + SizeOf(Character)
  Wend
  
  *c = @String
  While *c\c
    If *c = *f1
      a = l2
      While a
        result + Chr(*f2\c)
        *f2 + SizeOf(Character)
        a - 1
      Wend
      *f2 - SizeOf(Character) * l2
      *c + SizeOf(Character) * l1
    ElseIf *c = *f2
      While l1
        result + Chr(*f1\c)
        *f1 + SizeOf(Character)
        l1 - 1
      Wend
      *c + SizeOf(Character) * l2
    Else
      result + Chr(*c\c)
      *c + SizeOf(Character)
    EndIf
  Wend
  
  ProcedureReturn result
EndProcedure
Procedure.s StringField_RemoveField(String.s, Index.l, Seperator.s = " ")
  Protected result.s, a.l = 1, *c.Character, *sep.Character, ok = #False
  
  *c = @String
  *sep = @Seperator
  While *c\c
    If *c\c = *sep\c : a + 1 : EndIf
    If a <> Index
      If a <> 2 Or *c\c <> *sep\c Or Index <> 1
        result + Chr(*c\c)
      EndIf
    EndIf
    *c + SizeOf(Character)
  Wend
  
  ProcedureReturn result
EndProcedure

#Max = 100000
#CRLF = Chr(13) + Chr(10)

String.s = "Franz jagt im komplett verwahrlosten Taxi quer durch Bayern."
res.s
Times.s

time_ntq_1.l = ElapsedMilliseconds()
For a = 1 To #Max
  res = StringField_InsertString(String, "ganz", 8)
Next
time_ntq_1 = ElapsedMilliseconds() - time_ntq_1

time_ntq_2.l = ElapsedMilliseconds()
For a = 1 To #Max
  res = StringField_RemoveField(String, 4)
Next
time_ntq_2 = ElapsedMilliseconds() - time_ntq_2

time_ntq_3.l = ElapsedMilliseconds()
For a = 1 To #Max
  res = StringField_SwapFields(String, 7, 8)
Next
time_ntq_3 = ElapsedMilliseconds() - time_ntq_3


time_and_1.l = ElapsedMilliseconds()
For a = 1 To #Max
  res = StringField2_InsertString(String, "ganz", 8)
Next
time_and_1 = ElapsedMilliseconds() - time_and_1

time_and_2.l = ElapsedMilliseconds()
For a = 1 To #Max
  res = StringField2_RemoveField(String, 3)
Next
time_and_2 = ElapsedMilliseconds() - time_and_2

time_and_3.l = ElapsedMilliseconds()
For a = 1 To #Max
  res = StringField2_SwapFields(String, 6, 7)
Next
time_and_3 = ElapsedMilliseconds() - time_and_3


Times = "NicTheQuick:" + #CRLF + "- Insert: " + Str(time_ntq_1) + " ms" + #CRLF + "- Remove: " + Str(time_ntq_2) + " ms" + #CRLF + "- Swap: " + Str(time_ntq_3) + " ms" + #CRLF + #CRLF
Times + "AND51:"       + #CRLF + "- Insert: " + Str(time_and_1) + " ms" + #CRLF + "- Remove: " + Str(time_and_2) + " ms" + #CRLF + "- Swap: " + Str(time_and_3) + " ms" + #CRLF


MessageRequester("Zeiten: ", Times)

; SetClipboardText(Times)
Ist aber interessant zu sehen, was die Stringfunktionen von PB alles so
rausholen. Aber ich denke mit ASM kann man die Funktionen noch einiges
optimieren. :allright:
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

NicTheQuick hat geschrieben:Wo hast du eigentlich die Funktion IntQ() her?
aus PB 4.0?

sag mal, Nico, kann es sein, dass du noch nich up-to-date bist?
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8807
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

*Kopfkratz*

Weiß ja auch nicht. Mir kommt es grad so vor als ob ich PB 3.97
(Durchschnitt von 3.94 und 4.00) drauf hab. Vielleicht sollte ich den PB 4.00-
Installer nochmal drüber laufen lassen. :mrgreen:
Antworten