The String$( ) function is useful but limited to only the first character of a string to be concatenated. For example, String$(10,"Me") returns MMMMMMMMMM.
Is there a similar VB6 function or combination of functions that will return MeMeMeMeMeMeMeMeMeMe without looping to obtain this concatenation?
The only thing I can think of is Replace(String$(10, "M"), "M", "Me").
As far as I know your way is the best way. But why cant you have a loop? Is there any reason why you couldn't just do:
Code:
Private Sub Form_Load()
MsgBox Repeat(10, "Me")
End Sub
Public Function Repeat(ByVal Repetitions As Integer, ByVal Str As String)
Dim i As Integer
For i = 1 To Repetitions
Repeat = Repeat & Str
Next
End Function
Public Function Repeat(ByVal number As Long, ByVal characters As String) As String
Dim lngCharacters As Long
Dim lngTotal As Long
Dim lngIndex As Long
lngCharacters = Len(characters)
lngTotal = number * lngCharacters
Repeat = String$(lngTotal, 0)
For lngIndex = 1 To lngTotal Step lngCharacters
Mid$(Repeat, lngIndex, lngCharacters) = characters
Next
End Function
This even handles Unicode values that String$() doesn't itself.
Public Function Repeat(ByVal number As Long, ByVal characters As String) As String
Dim b() As Byte, blen As Long, i As Long
blen = LenB(characters)
b = characters
i = blen * Abs(number) - 1
If i > -1 Then
ReDim Preserve b(i)
For i = blen To i
b(i) = b(i Mod blen)
Next i
Repeat = b
End If
End Function
This is a unicode version, an ascii version would be faster. It's definitely faster than using & (which copies the whole string) and probobly a bit faster than using mid$. Would need to benchmark to be sure. I imagine a safearray hack would be fastest but that involves Api and quite a bit more code.
Last edited by Milk; May 27th, 2008 at 05:50 PM.
Reason: modified to handle negative number and null string characters
CodeDoc,
Your solution is extremely simple although it is slow:
Code:
Function RepeatStr(Number As Long, Str As String) As String
If Number > 0 Then
RepeatStr = Replace(String$(Number, 0), vbNullChar, Str)
End If
End Function
The function below is extremely fast for large Number.
I have tested with Number = 10^7 and Str = "Hello!", it took 1.2 seconds for a result of a string of 60,000,000 bytes, while Milk's version took 7.2 seconds (6 times slower).
Your simple version took more than 15 minutes (907 seconds) to complete. However for a small Number, I love to use that.
Code:
Function RepeatStr(Number As Long, Str As String) As String
If Number > 0 And Len(Str) > 0 Then
Dim i As Long, k As Long
RepeatStr = Str
k = Int(Log(Number) / Log(2))
For i = 1 To k
RepeatStr = RepeatStr & RepeatStr
Next
k = Number - 2 ^ k
'RepeatStr = RepeatStr & RepeatStr(k, Str)
'-- To avoid the recursive call, the above line is replaced by:
If k > 0 Then RepeatStr = RepeatStr & Left$(RepeatStr, k * Len(Str))
End If
End Function
Another way to write the above function without using Log(), it looks better:
Code:
Function RepeatStr(Number As Long, Str As String) As String
If Number > 0 And Len(Str) > 0 Then
Dim n As Long
RepeatStr = Str
n = 1
Do Until n * 2 > Number
n = n * 2
RepeatStr = RepeatStr & RepeatStr
Loop
'RepeatStr = RepeatStr & RepeatStr(Number - n, Str)
'-- To avoid the recursive call, the above line is replaced by:
If n < Number Then RepeatStr = RepeatStr & Left$(RepeatStr, (Number - n) * Len(Str))
End If
End Function
Last edited by anhn; May 28th, 2008 at 11:34 PM.
Don't forget to use [CODE]your code here[/CODE] when posting code
If your question was answered please use Thread Tools to mark your thread [RESOLVED]
Actually, 2 versions I posted are nearly identical.
The first uses Log() to calculate before hand the number of round to loop,
The second just loops until n*2 > Number.
Don't forget to use [CODE]your code here[/CODE] when posting code
If your question was answered please use Thread Tools to mark your thread [RESOLVED]
Hm, it's that much faster even with string concatenation?
Since the length of the final string can be determined by length of Str and Number of times to be repeated, couldn't you just declare the return string as Space$(FinalLength) and use Mid$(ReturnString, x, x) = "" instead of concatenation? That might make it even faster. But I don't know.
I think the key is in a$ = a$ & a$ that doubles the string each time. It only takes a small number of copies to get an exponential increase in string. Digirev, you might well have a point, & is innately slow so predefining length and filling should be faster. It's time I hit the sack so I'm not going to faff around with this any more
Hm, it's that much faster even with string concatenation?
Since the length of the final string can be determined by length of Str and Number of times to be repeated, couldn't you just declare the return string as Space$(FinalLength) and use Mid$(ReturnString, x, x) = "" instead of concatenation? That might make it even faster. But I don't know.
Yes, that is another good way @dilettante posted in post#3, and it is about 50% slower than my version.
@Milk, can you also benchmark that?
Don't forget to use [CODE]your code here[/CODE] when posting code
If your question was answered please use Thread Tools to mark your thread [RESOLVED]
I'd go with dilettante's method, though I confess that anhn's method is over my head. What makes it faster? Then again, is it even faster? dilettante's Mid$() method is, generally speaking, more efficient than Milk's byte array method.
Hey Logo, I see you're in here. Please explain, I'm begging ya! (heh)
it's 4x faster than mine. I'm quiet sad now, I'm going to bed.
ellis: Anhn is being cunning with exponentials that is the key, mid$ is crap with single bytes but better with multiple bytes.
anhn uses Log to determine how many concatenations are needed and a recursive call to get the exact number of reps.
anhn's method clearly has fewer steps, but the slowness of concatenation makes the end result only marginally faster. (Or so it seems, I didn't compare them significantly)
A blend of the two should prove to be faster still:
Code:
Function Repeat(ByVal Number As Long, ByVal Text As String) As String
Dim lngLength As Long
Dim n As Long
If Number < 1 Then Exit Function
n = Len(Text)
lngLength = Number * n
Repeat = Space(lngLength)
Mid(Repeat, 1, n) = Text
Do While n < lngLength
Mid(Repeat, n + 1, n) = Mid$(Repeat, 1, n)
n = n + n
Loop
End Function
Last edited by Logophobic; May 27th, 2008 at 09:17 PM.
The exponential increase (doubling) can be implemented with copymemory API (adjust start,destination positions and number of bytes to copy) and manipulation of pre-sized byte array if you wish not to do it through repetitive concatenation (which is slow due to repetitive memory alloc/dealloc) or if your concerned with stack use of recursion.
Last edited by leinad31; May 27th, 2008 at 10:58 PM.
Very clever, anhn. I'll have to remember that technique.
Thanks!
That technique came to my head as I remembered long time ago I used an example to build quickly an out-of-string-space string to test the error by doubling the string size.
*--
Logophobic's method is faster than dilettante's method but for now my method is still the champion.
I hope a smarter guy can beat it with a smarter technique.
Not sure how fast with CopyMemory API as leinad31 mentioned.
Don't forget to use [CODE]your code here[/CODE] when posting code
If your question was answered please use Thread Tools to mark your thread [RESOLVED]
Logophobic's method is faster than dilettante's method but for now my method is still the champion.
That seemed odd to me, so i threw together a benchmark to compare the three. (Sorry, Milk; your version seemed to return twice the expected characters.)
Note that IDE results are unreliable; only the compiled exe results are meaningful. (For those who remember me from last year, I have since upgraded to a much faster computer.)
It's nice! But you should test with large and few different inputs.
My method will have optimum speed if Number = 2n (no recursive call needed).
223 will faster than 223-1 !!!
Don't forget to use [CODE]your code here[/CODE] when posting code
If your question was answered please use Thread Tools to mark your thread [RESOLVED]
Private Function leinad31(Number As Long, Str As String) As String
Dim src_ptr As Long
Dim dest_ptr As Long
Dim totlen As Long
Dim getlen As Long
Dim byt() As Byte
If Number > 0 And LenB(Str) > 0 Then
byt = Str
getlen = LenB(Str)
totlen = getlen * Number
ReDim Preserve byt(totlen)
src_ptr = VarPtr(byt(0))
dest_ptr = src_ptr + getlen
Do Until getlen * 2 > totlen
CopyMemory ByVal dest_ptr, ByVal src_ptr, getlen
dest_ptr = dest_ptr + getlen
getlen = getlen * 2
Loop
If getlen < totlen Then
CopyMemory ByVal dest_ptr, ByVal src_ptr, totlen - getlen
End If
leinad31 = byt
Else
leinad31 = vbNullString
End If
End Function
I used byte array instead of string 'cause of VarPtr.
Last edited by leinad31; May 28th, 2008 at 12:47 AM.
I wrote a version that populates a string array and then Join()s it to create the repeated string. It takes a whopping 50 seconds to run, so that's a nonstarter.
Here's the CopyMemory prototype (used by leinad's version) for those who want to play along at home:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Last edited by Ellis Dee; May 28th, 2008 at 12:55 AM.
Thanks but the exponential growth is the greatest time saver. CopyMemory simply optimizes that (reduce malloc, dealloc). BTW, I tried using a String working variable with StrPtr() variation. Benchmark was 0.60670, so byte array manipulation is still faster.
Non-standard implementation of prototype may lead to errors/confusion later on, more so if API is declared public. Rule of thumb may be better. If you know memory address value and have it stored in a variable, e.g. src_ptr = VarPtr(byt(0)), then pass the address using ByVal. Otherwise API uses memory location of variable instead of address contained in variable.
Strictly speaking that's correct but there's no harm in making things easy for yourself. When using RtlMoveMemory in anger I often have more than one prototype and name them accordingly.
eg If I use RtlMoveMemory for copying String data I'd use:
Private Declare Sub CopyString Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Any, ByVal Source As Any, ByVal Length As Long)
which avoids confusion and 'silly' mistakes when using it.
I suppose it's a matter of style and experience (the number of times I've blown up the IDE, without saving the project, are uncountable, so recognising my frequent mistake, this is the solution I have chosen for myself)
[wakes with hangover and tentatively looks back at thread]
Originally Posted by Ellis Dee
<snip> (Sorry, Milk; your version seemed to return twice the expected characters.)
oopsy, mixing my unicode with ansii, well at least that effectively doubles it's speed
Originally Posted by leinad31
@Milk, performance will be a bit better without Mod.
I think your rtlMoveMemory version of Anhns logic makes that rather moot.
Just out of interest has anyone ever compared rtlMoveMemory with rtlCopyMemory, I understand that MoveMemory can handle overlaps, does that make CopyMemory faster?
This thread has unexpectedly produced some of the most interesting code that I have seen in years, and I sometimes forget how much power that VB6 maintains in looping and string concatenation. Australian programmers also never cease to amaze me.
The idea of nonlinear and perhaps exponential concatenation (rather than linear) as illustrated so concisely by Logophobic, never even entered my feeble mind. That's truly remarkable. The only thing that would worry me is that suppose a huge string lacks by only one byte what it needs before the last iteration is performed (a worst case scenario). Suddenly you have to lop off half the final string and memory is thus wasted in the interim.
For example, look at how fast this simple code runs to produce a 50,000-byte string from an initial 3-banger:
Code:
Dim MyString As String
Const StrLen = 50000
Private Sub Form_Load()
MyString = "abc"
While Len(MyString) < StrLen
MyString = MyString & MyString
Wend
MyString = Left$(MyString, StrLen)
End Sub
Any If ... Then lookahead prior to the final concatenation would probably slow the routine down, but might be wise even in this day of cheap memory. Still, I wish to thank all of you and will mark the post resolved (in spades).
Last edited by Code Doc; May 28th, 2008 at 01:40 PM.
I tried using a String working variable with StrPtr() variation. Benchmark was 0.60670, so byte array manipulation is still faster.
Try it again:
Code:
Private Function Logophobic(Number As Long, Str As String) As String
Dim src_ptr As Long
Dim dest_ptr As Long
Dim totlen As Long
Dim getlen As Long
If Number > 0 And LenB(Str) > 0 Then
Logophobic = String(Number * Len(Str), 0)
getlen = LenB(Str)
totlen = LenB(Logophobic)
src_ptr = StrPtr(Logophobic)
CopyMemory ByVal src_ptr, ByVal StrPtr(Str), getlen
dest_ptr = src_ptr + getlen
Do Until getlen * 2 > totlen
CopyMemory ByVal dest_ptr, ByVal src_ptr, getlen
dest_ptr = dest_ptr + getlen
getlen = getlen * 2
Loop
If getlen < totlen Then
CopyMemory ByVal dest_ptr, ByVal src_ptr, totlen - getlen
End If
Else
Logophobic = vbNullString
End If
End Function
In anhn's code, the performance degradation from the function call and recursion was offset by the exponential growth approach. I was trying to point out that for the linear growth implementations, your code would have been fast if you avoided Mod and implemented something similar to link.
Logophobic's last code is pretty similar to what I had in my clsBSTR which has a feature called Replicate. It is slower than Logophobic's code only because it is a class module and as such has some additional cost in execution when repeated many times
Edit
As a side note, dilittante's code does better if you lessen repetitions and use longer strings.
Here is an updated version of the benchmarker, also ticked all the "advanced optimizations" checkboxes:
Note that I updated the codes to what were last posted in the posts, anhn and Logophobic both had new versions so I just put them in.
Advanced optimizations in this case are most likely to speed up my code that is dependant on some heavy array work in some parts of it. Mostly not involved in this occasion. As a side note though, my code in that class isn't maxed out in optimizations, it is more of a "generic fast coding" style.
leinad's code is slower because it makes one extra move of data when copying from byte array to string.
Edit!
And actually, now that I said that line about byte arrays, it was one of the reasons why I ended up creating the class