Replicating a character or a string a given number of times is a task that comes up repeatedly in scripting. The handler presented in this post is a Vanilla AppleScript solution that achieves logarithmic execution efficiency through a combination of the progressive binary expansion of the input string into a list of intermediate strings and a memoization-like selective incorporation of the saved intermediate strings into the final replicated string. It can handle large replication numbers efficiently, for example, replicating an input string 100,000 times in < 0.002 seconds on my machine.
Handler:
on replicateString(theString, nReps)
-- Validate the input arguments
if theString's class ≠ text then error "The first input argument is not a text string."
tell nReps to if ({its class} is not in {integer, real}) or ((it mod 1) ≠ 0) then error "The second input argument is not an integer."
-- Handle the special cases where the input string = "" or the number of replications is ≤ 0
if (theString's length = 0) or (nReps ≤ 0) then return ""
-- Repeatedly double the input string until the next doubling would exceed the target number of replications, and save the intermediate replication counts and their associated strings
set {nRepsEntries, replicatedStringEntries} to {{1}, {theString}}
repeat
tell 2 * (nRepsEntries's last item)
if it > nReps then exit repeat
set {end of nRepsEntries, end of replicatedStringEntries} to {it, replicatedStringEntries's last item & replicatedStringEntries's last item}
end tell
end repeat
-- Loop through the saved intermediate replication counts and their associated strings in reverse order beginning with the second-to-last list item, add to the last list item those intermediate values that do not cause the target number of replications to be exceeded, and quit when the target number of replications has been reached
if nRepsEntries's last item < nReps then
repeat with i from ((nRepsEntries's length) - 1) to 1 by -1
tell ((nRepsEntries's last item) + (nRepsEntries's item i)) to if it ≤ nReps then set {nRepsEntries's last item, replicatedStringEntries's last item} to {it, (replicatedStringEntries's last item) & (replicatedStringEntries's item i)}
if nRepsEntries's last item = nReps then exit repeat
end repeat
end if
-- Return the replicated string, which is the last item in the list of intermediate replicated strings
return replicatedStringEntries's last item as text
end replicateString
replicateString("x", 10) --> "xxxxxxxxxx"
replicateString("123", 5) --> "123123123123123"
Execution speed tests were performed comparing the current handler with the following ASObjC string replication command:
(((current application's NSString)'s stringWithString:"")'s stringByPaddingToLength:(nReps * (theString's length)) withString:(theString) startingAtIndex:(0)) as text
Each solution was stored as a handler in ~/Library/Script Libraries and executed by calling the handler from the test script. The mean of 1000 trials for each data point was calculated, and the median of three such trials was taken as the final data point. The following are the execution times for theString = “x” and varying nReps (# replications of “x”):
nReps = 1:
current handler = 0.000015 secs, ASObjC = 0.00018 seconds
→ current handler ~12x faster
nReps = 10:
current handler = 0.000047 secs, ASObjC = 0.00018 seconds
→ current handler ~3.8x faster
nReps = 100:
current handler = 0.000074 secs, ASObjC = 0.00017 seconds
→ current handler ~2.3x faster
nReps = 1000:
current handler = 0.00012 secs, ASObjC = 0.00019 seconds
→ current handler ~1.6x faster
nReps = 10,000:
current handler = 0.00030 secs, ASObjC = 0.00029 seconds
→ current handler ≈ ASObjC
nReps = 100,000:
current handler = 0.0019 secs, ASObjC = 0.0019 seconds
→ current handler ≈ ASObjC
nReps = 1,000,000:
current handler = 0.032 secs, ASObjC = 0.019 seconds
→ ASObjC ~1.7x faster
As is seen, the current handler outperforms the ASObjC solution in execution time for # replications in the range of 1 to 1000, which likely comprises most scripting situations where string replication is required. For the unusual situations where greater replication values are needed, the current handler matches the ASObjC solution for # replications in the range of 10,000 to 100,000 and is outperformed by ASObjC by a modest amount for # replications in the range of 1,000,000.