Page 1 of 2

ForEach for Strings

Posted: Tue Jul 16, 2024 3:06 pm
by jacdelad
Hi,
I don't know if somebody else agrees, but I would really appreciate to have a ForEach to be used with strings/substrings:

Code: Select all

MyString$="1|2|3|4|5|..."
Counter=CountString(MyString$,"|")+1
For Count=1 To Counter
  Temp$=StringField(MyString$,Count,"|")
  ;Do stuff with Temp$
Next
could be done shorter, more readable and more efficient as

Code: Select all

MyString$="1|2|3|4|5|..."
ForEach MyString$,"|",Temp$
  ;Do stuff with Temp$
Next
or

Code: Select all

MyString$="1|2|3|4|5|..."
ForEach MyString$,"|",@Temp$
  ;Do stuff with Temp$
Next
Since there are several scenarios which require all substrings of a string, this could come in handy.

Re: ForEach for Strings

Posted: Tue Jul 16, 2024 3:24 pm
by Quin
+1
String indexing like arrays would also be incredibly handy, but that may be a topic for a different day.

Re: ForEach for Strings

Posted: Tue Jul 16, 2024 4:46 pm
by NicTheQuick
Quin wrote: Tue Jul 16, 2024 3:24 pm String indexing like arrays would also be incredibly handy, but that may be a topic for a different day.
You at least can do that with some pointer magic:

Code: Select all

Structure CharArray
	c.c[0]
EndStructure

Define mystring.s = "Hello World"

Define *mystring.CharArray = @mystring
*mystring\c[0] = 'h'

Debug mystring

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 2:02 am
by BarryG
(Not what OP requested).

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 4:14 am
by AZJIO
everything has already been invented a long time ago
SplitL

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 5:12 am
by jacdelad
That's not the point and not the same as my feature request.

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 5:15 am
by BarryG
(Not what OP requested).

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 5:17 am
by jacdelad
No, I want an extended ForEach.

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 5:25 am
by BarryG
Okay, never mind. Sorry! I thought I was onto something for you. My bad.

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 5:51 am
by AZJIO
jacdelad wrote: Wed Jul 17, 2024 5:17 am No, I want an extended ForEach.
I think there is a common syntax used in many languages. An example of a custom syntax in PECMD that doesn't look like anything. It's like one person's own language.

By the way, if there are solutions using the existing syntax, then no one will come up with anything. But the function I proposed duplicates the memory by creating a list with the same contents. I tried to make pointers from the string and now the memory is not duplicated (probably).

Code: Select all

EnableExplicit

Procedure SplitL3(*c.Character, List StringList(), *jc.Character)
	Protected *t.Character = *c
	ClearList(StringList())
	While *c\c
		If *c\c = *jc\c
			*c\c = 0
			*c + SizeOf(Character)
			If *c\c
				AddElement(StringList())
				StringList() = *t
				*t = *c
			Else
				Break
			EndIf
		EndIf
		*c + SizeOf(Character)
	Wend
	AddElement(StringList())
	StringList() = *t
EndProcedure

Define St.s = "This is a test string to see if split and join are working."

NewList WordsList()
SplitL3(@St, WordsList(), @" ")

ForEach WordsList()
    Debug PeekS(WordsList())
Next

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 6:30 am
by idle
AZJIO wrote: Wed Jul 17, 2024 5:51 am
jacdelad wrote: Wed Jul 17, 2024 5:17 am No, I want an extended ForEach.
I think there is a common syntax used in many languages. An example of a custom syntax in PECMD that doesn't look like anything. It's like one person's own language.

By the way, if there are solutions using the existing syntax, then no one will come up with anything. But the function I proposed duplicates the memory by creating a list with the same contents. I tried to make pointers from the string and now the memory is not duplicated (probably).

Code: Select all

EnableExplicit

Procedure SplitL3(*c.Character, List StringList(), *jc.Character)
	Protected *t.Character = *c
	ClearList(StringList())
	While *c\c
		If *c\c = *jc\c
			*c\c = 0
			*c + SizeOf(Character)
			AddElement(StringList())
			StringList() = *t
			*t = *c
		EndIf
		*c + SizeOf(Character)
	Wend
	AddElement(StringList())
	StringList() = *t
EndProcedure

Define St.s = "This is a test string to see if split and join are working."

NewList WordsList()
SplitL3(@St, WordsList(), @" ")

ForEach WordsList()
    Debug PeekS(WordsList())
Next
that's a very good solution

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 8:30 am
by Mesa
A macro can do a foreach:

Code: Select all

Macro ForEachS(S,T)
  For Count=1 To CountString(S,"|")+1
    T=StringField(S,Count,"|")
  EndMacro
  
  MyString$="1|2|3|4|5|..."
  ForEachS(MyString$,Temp$)
  Debug Temp$
Next
M.

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 3:30 pm
by jacdelad
Ok, so without wanting to sound rude: I know how to do this with existing code elements, sure. If it was something outstanding, I or someone else would have posted it in Tipps&Tricks. This section is for wishes, so, as I see it, things that are either not doable at the moment or things that change/enhance PureBasic internally.

As I wrote in my initial post, this is nothing completely new. I also know how to do this with existing functions. Your ideas are great as well. BUT, my request/question, was and is to enhance the command "ForEach". By your logic we don't need ForEach at all. Going through a list doesn't even need an auxiliar variable. But the command is here and I use it regularly. Is just want to use it with strings too, because I regularly go through string parts. I don't care how it would be done internally, I just want it to work with ForEach. That's why it is a request, not a trick or a question. Period.

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 4:55 pm
by tored
Would be great if possible to hook into ForEach with an iterator, that way we can use ForEach with whatever collections we have.

Re: ForEach for Strings

Posted: Wed Jul 17, 2024 6:30 pm
by tored
tored wrote: Wed Jul 17, 2024 4:55 pm Would be great if possible to hook into ForEach with an iterator, that way we can use ForEach with whatever collections we have.
Proof of concept

Edit: Fixed incorrect current() for IntArrayIterator

Code: Select all

EnableExplicit

DeclareModule Iterator
  EnableExplicit
  
  Interface Iterator
    current()
    key()
    nxt()
    rewind()
    valid()
    free()
  EndInterface

  Macro Loop(it)
    it\rewind()
    While it\valid()
  EndMacro
  
  Macro EndLoop(it)
    it\nxt()
    Wend
  EndMacro
EndDeclareModule  

Module Iterator
  
EndModule

DeclareModule IntArrayIterator
  EnableExplicit
  
  Declare New(Array arr.i(1))
EndDeclareModule

Module IntArrayIterator
  
  Structure IntArray
    i.i[0]
  EndStructure
  
  Structure This
    *vt
    *arr.IntArray
    size.i
    pos.i
  EndStructure  
  
  Procedure Current(*this.This)
    ProcedureReturn @*this\arr\i[*this\pos]
  EndProcedure
  
  Procedure Key(*this.This)
    ProcedureReturn *this\pos
  EndProcedure  
  
  Procedure Nxt(*this.This)
    *this\pos + 1
  EndProcedure
  
  Procedure Rewind(*this.This)
    *this\pos = 0
  EndProcedure
  
  Procedure Valid(*this.This)
    ProcedureReturn Bool(*this\pos =< *this\size)
  EndProcedure 
  
  Procedure Free(*this.This)
    FreeMemory(*this)
  EndProcedure  
  
  DataSection
    vt:
    Data.i @Current()
    Data.i @Key()
    Data.i @Nxt()
    Data.i @Rewind()
    Data.i @Valid()
    Data.i @Free()
  EndDataSection
  
  Procedure New(Array arr.i(1))
    Protected *this.This
    *this = AllocateMemory(SizeOf(This))
    If *this
      *this\vt = ?vt
      *this\arr = @arr()
      *this\size = ArraySize(arr())
    EndIf  
    ProcedureReturn *this  
  EndProcedure
EndModule

DeclareModule SplitStringIterator
  EnableExplicit
  
  Declare New(*str, delim.s)
EndDeclareModule  

Module SplitStringIterator
  
  Structure This
    *vt
    *s.String
    size.i
    pos.i
    delim.s
    tmp.s
  EndStructure  
  
  Macro SetTmp(this)
    this\tmp = StringField(this\s\s, this\pos + 1, this\delim)
  EndMacro  
  
  Procedure Current(*this.This)
    ProcedureReturn @*this\tmp
  EndProcedure
  
  Procedure Key(*this.This)
    ProcedureReturn *this\pos
  EndProcedure  
  
  Procedure Nxt(*this.This)
    *this\pos + 1
    SetTmp(*this)
  EndProcedure
  
  Procedure Rewind(*this.This)
    *this\pos = 0
  EndProcedure
  
  Procedure Valid(*this.This)
    ProcedureReturn Bool(*this\pos =< *this\size)
  EndProcedure 
  
  Procedure Free(*this.This)
    FreeMemory(*this)
  EndProcedure  
  
  DataSection
    vt:
    Data.i @Current()
    Data.i @Key()
    Data.i @Nxt()
    Data.i @Rewind()
    Data.i @Valid()
    Data.i @Free()
  EndDataSection
  
  Procedure New(*str, delim.s)
    Protected *s.String = @*str
    Protected *this.This
    *this = AllocateMemory(SizeOf(This))
    If *this
      *this\vt = ?vt
      *this\s = *s
      *this\size = CountString(*s\s, delim)
      *this\delim = delim
      SetTmp(*this)
   EndIf  
    ProcedureReturn *this  
  EndProcedure
EndModule
  
UseModule Iterator

Dim arr(1)
arr(0) = 2
arr(1) = 17

Define *it.Iterator = IntArrayIterator::New(arr())

Loop(*it)
  Debug Str(*it\key()) + ":" + Str(PeekI(*it\current()))
EndLoop(*it)
*it\free()

Define s.s = "one;two;three"
*it = SplitStringIterator::New(@s, ";")

Loop(*it)
  Debug PeekS(*it\current())
EndLoop(*it)
*it\free()