Seite 1 von 2

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

Verfasst: 13.10.2006 00:31
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!!!

Verfasst: 13.10.2006 00:39
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...

Verfasst: 13.10.2006 00:41
von AND51
Hast Recht. Das 'irgendwann' mildert das 'hochgreifen' aber wiederum!

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

Verfasst: 13.10.2006 00:54
von Kaeru Gaman
> Das 'irgendwann' mildert das 'hochgreifen' aber wiederum!
hast ja recht, die hoffnung stirbt zuletzt. ;)

Verfasst: 13.10.2006 12:25
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) + "'"

Verfasst: 13.10.2006 13:16
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.

Verfasst: 13.10.2006 13:25
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

Verfasst: 13.10.2006 13:38
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:

Verfasst: 13.10.2006 13:41
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?

Verfasst: 13.10.2006 14:14
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: