Is there a Repeat method for Strings?

Hi, is there a method such that if (for example) you want a string of n asterisks, you can say:

myString = “*”.Repeat(n)?

Or do you have to go around a loop? I’m not finding anything…

TIA

I have included that in my M_String module, based on the code from Joe Strout’s StringUtils.

2 Likes

Var s As String = RepeatStr("*", 3) // “***”

s = s.RepeatStr(2) // “******”

Module String_Utils

Public Function RepeatStr(str As String, numTimes As Integer) As String

  If str.Bytes < 1 Or numTimes < 1 Then Return ""
  
  Var alloc() As String
  
  For i As Integer = 1 to numTimes
    alloc.Add str
  Next
  
  Return String.FromArray(alloc, "")
  
End Function


Public Function RepeatStr(Extends str As String, numTimes As Integer) As String

  Return RepeatStr(str, numTimes)

End Function

End Module
1 Like

Compare that to the implementation in M_String and StringUtils. You’ll find that it’s much slower.

1 Like

I won’t, but if someone can get us the timing for a 10000 x=repeat("*", 30), of each, it can be at least interesting. :wink:

a method with a MemoryBlock could be fast.

Var m As New MemoryBlock(size)
.. write your char
return m.StringValue(0, m.Size)

I guess it’s worth revisiting now, but back when I was deciding how to write my version of Repeat, I tested various techniques, including arrays and MemoryBlocks, and the StringUtils way was the fastest without question.

MAYBE pumping those bytes in place into the MemoryBlock took some extra nanoseconds and at end just added complexity and no real advantage. So you gave up on it.

Public Function Repeat(z as integer, anz as integer) As string
  rem long string create
  rem asc(string),size
  Var mb As New MemoryBlock(anz)
  anz = anz-1
  For i As Integer = 0 To anz
    mb.Byte(i)=z
  Next
  Return mb
End Function

10000 views
For x As Integer = 0 To 10000
s = Repeat(s.Asc,num) // Chr() + size
Next
I have 3 system ticks

1 Like

how is this connected to the requested “repeat a string” ?

NO, you have to implement your own, look at the examples provided or sear the forem, this question has bein answered many times.

You cant write code like that, Xojo does not know that you have a string until you put it on a variable. So, the with the code you chose, you can create a global method that uses EXTENDS to write like that but with a variable

Dim MyString As String = “*”
MyString = MyString.Repeat(x)

The vast changes in the languaje are just aestetics instead of adding functionality.

Examble:
https://www.dropbox.com/s/g221x5hm6g3ancg/leerfeld-test.xojo_binary_project?dl=1

On average, this memory block based version seems even faster in ARM64 mac:

Public Function RepeatAsBin(Extends value As String, Count As Integer) As String
  If count <= 1 Then
    Return value
  End If
  
  #If Not DebugBuild Then
    #Pragma BackgroundTasks False
    #Pragma StackOverflowChecking False
    #Pragma NilObjectChecking False
    #Pragma BoundsChecking False
  #EndIf
  
  Var size As Integer = value.Bytes
  Var totalSize As Integer = (size * count)
  
  Var mb As New MemoryBlock(totalSize)
  mb.StringValue(0, size) = value
  
  Var lastIndex As Integer = (count - 1)
  Var index As Integer, offset As Integer
  For index = 1 To lastIndex
    // Skip the first item as it's the input value
    
    offset = (offset + size)
    mb.StringValue(offset, size) = value
    
  Next index
  
  Var result As String = mb
  result = result.DefineEncoding(Encodings.UTF8)
  Return result
  
End Function

Alternative pure string based version:

Public Function Repeat(Extends value As String, Count As Integer) As String
  If count <= 1 Then
    Return value
  End If
  
  #If Not DebugBuild Then
    #Pragma BackgroundTasks False
    #Pragma StackOverflowChecking False
    #Pragma NilObjectChecking False
    #Pragma BoundsChecking False
  #EndIf
  
  Var lastIndex As Integer = (count - 1)
  Var values(count) As String = Array(value) // init with first value and count
  
  Var index As Integer
  For index = 1 To lastIndex
    // Skip the first item as it's the input value
    values.AddAt(index, value)
  Next index
  
  Var result As String = String.FromArray(values, "")
  Return result
End Function

Depends on how you measure the time but i’m interested in knowing which is fastest.

1 Like

Probably the fastest (and the shortest) method in Xojo:

Public Function repeat_string_fast(string_ as string, times_ as integer) As string
    #pragma BoundsChecking        false
    #pragma StackOverflowChecking false
    #pragma NilObjectChecking     false

    if times_ <= 0 then return ""
    if times_  = 1 then return string_

    var a() as string
    a.ResizeTo(times_)

    return string.FromArray(a, string_)
End Function
7 Likes

How does that even makes the string repeat?
Trough the FromArray… ?

It uses the desired string as the joiner for an empty string array with as many places as you wish to repeat. Very clever. Interested in actual speed tests now.

2 Likes

Very clever indeed. Because it passes most of the job to the core native code, probably it’s possibly unbeatable.

I have one little suggestion to shortcut an edge case overhead as repeat("", 99999999):

Public Function repeat_string_fast(string_ as string, times_ as integer) As string
  #pragma BoundsChecking        false
  #pragma StackOverflowChecking false
  #pragma NilObjectChecking     false
  
  if times_ <= 0 Or string_.Bytes = 0 then return ""
  if times_  = 1 then return string_
  
  var a() as string
  a.ResizeTo(times_)
  
  return string.FromArray(a, string_)
End Function
1 Like

Exactly, it is the fastest method because it does nothing except feed string.FromArray() with the appropriate data.

Clever. Did you compare to the one in M_String/StringUtils?

Do you mean the string concatenation?

Honestly, this optmization thing makes only sense if you repeat many times and often. Here are some comparisons in milliseconds repeating a four-byte string 100,000 times:

  1. String = String + String 1,114.35 ms
  2. String = string.FromArray(<prefilled array with passend string>, "") 21.45 ms
  3. String = string.FromArray(<resized array with empty items>, String) 2.16 ms

If you repeat only three times you need to measure in microseconds to see a difference:

  1. String = String + String 87.19 µs
  2. String = string.FromArray(<prefilled array with passend string>, "") 5.48 µs
  3. String = string.FromArray(<resized array with empty items>, String) 2.7 µs
1 Like