Insert hyphens into string

Hello,

just would like to know, if there is a smarter way to insert hyphens into a string:

set PlainKey to "1234567890ABCDEFGHIJ" as string
set first_block to the text 1 thru 5 of PlainKey
set second_block to the text 6 thru 10 of PlainKey
set third_block to the text 11 thru 15 of PlainKey
set fourth_block to the text 16 thru 20 of PlainKey

set ActivationKey to first_block & "-" & second_block & "-" & third_block & "-" & fourth_block

Cheers,

B.

You may use :

set PlainKey to "1234567890ABCDEFGHIJ"
tell PlainKey
	set ActivationKey to text 1 thru 5 & "-" & text 6 thru 10 & "-" & text 11 thru 15 & "-" & text 16 thru -1
end tell

Yvan KOENIG running El Capitan 10.11.3 in French (VALLAURIS, France) samedi 5 mars 2016 21:48:30

It seems like you want to add an hyphen after each 5 characters.

If you have AppleScript Toolbox installed and length of plainKey is unknown:

set PlainKey to "1234567890ABCDEFGHIJ"
set theItems to AST find regex ".{1,5}" in string PlainKey
joinItems(theItems, "-")

on joinItems(lst, sep)
	tell AppleScript
		set oldTIDs to text item delimiters
		set text item delimiters to sep
		set str to lst as string
		set text item delimiters to oldTIDs
	end tell
	return str
end joinItems

if you don’t have AppleScript Toolbox installed and still have an variabe plainKey length:

set PlainKey to "1234567890ABCDEFGHIJ"
set theItems to separateByLength(PlainKey, 5)
joinItems(theItems, "-")

on separateByLength(str, len)
	set lst to {}
	repeat with i from (count of str) div len to 1 by -1
		set pos to (((i - 1) * len) + 1)
		set beginning of lst to text pos thru (pos + (len - 1)) of str
	end repeat
	if (count of str) mod len is not 0 then
		set pos to (count of str) mod len * -1
		set end of lst to text pos thru -1 of str
	end if
	return lst
end separateByLength

on joinItems(lst, sep)
	tell AppleScript
		set oldTIDs to text item delimiters
		set text item delimiters to sep
		set str to lst as string
		set text item delimiters to oldTIDs
	end tell
	return str
end joinItems

The ‘trick’ provided by Yvan is what I am looking for. Just one line of code to alter the string. Perfect!

Or if you have Satimage OSAX installed, you could use:

set PlainKey to "1234567890ABCDEFGHIJ"
set ActivationKey to (change "(.{5})(?!$)" into "\\1-" in PlainKey with regexp) -- Requires Satimage OSAX.

Or without any extras, there is of course sed:

set PlainKey to "1234567890ABCDEFGHIJ"
set ActivationKey to (do shell script ("<<<" & quoted form of PlainKey & " sed -E 's/(.{5})(.{5})(.{5})/\\1-\\2-\\3-/'"))

-- Alternatively:
set PlainKey to "1234567890ABCDEFGHIJ"
set ActivationKey to (do shell script ("<<<" & quoted form of PlainKey & " sed -E 's/.{5}/&-/g ; s/-$//'"))

But Yvan’s method’s fine. :slight_smile:

Thanks for the feedback.

Yvan KOENIG running El Capitan 10.11.3 in French (VALLAURIS, France) dimanche 6 mars 2016 11:10:15

I see you already have an answer. Here’s my solution that has more lines of code, but provides a general purpose handler that will handle any key size and block size.

After I wrote this, I saw DJ’s post, which is essentially the same approach.

Just for fun, I’ll go ahead an add mine to the collection:


set PlainKey to "1234567890ABCDEFGHIJ" as string

set ActivationKey to getActKey(PlainKey, 5, "-")
log ActivationKey


on getActKey(pPlainKey, pBlockSize, pDivStr)
	
	### RETURNS STRING IN BLOCKS SEPARATED BY DIV STR ###
	#   IF pPlainKey is not evenly divisible by Block Size
	#   then the remaining characters are appended to end
	
	set lenKey to length of pPlainKey
	set numBlocks to lenKey div pBlockSize
	set keyList to {}
	
	--- BUILD LIST OF FIXED-SIZE KEY BLOCKS ---
	set iStart to 1
	repeat with iBlock from 1 to numBlocks
		set end of keyList to text iStart thru (iStart + pBlockSize - 1) of pPlainKey
		set iStart to iStart + pBlockSize
	end repeat
	
	--- CONVERT KEY LIST TO STRING ---
	set AppleScript's text item delimiters to pDivStr
	set actKey to keyList as text
	
	--- APPEND REMAINING CHARACTERS (IF ANY) ---
	set remainNum to lenKey mod pBlockSize
	if remainNum > 0 then
		set remainStr to text -remainNum thru -1 of pPlainKey
		set actKey to actKey & pDivStr & remainStr
	end if
	
	return actKey
	
end getActKey


This is my favorite. Thanks Nigel.

Hi JMichaelTX.

Thanks for adding to the contributions ” and for liking my Satimage script. :slight_smile:

An alternative to your numBlocks/remainNum scheme would be to check for potential overrun during the repeat. This would be a trifle slower if the text had to be divided into hundreds or thousands of blocks, but wouldn’t make any difference here and would reduce the amount of code required.


set PlainKey to "1234567890ABCDEFGHIJ" as string

set ActivationKey to getActKey(PlainKey, 5, "-")
log ActivationKey


on getActKey(pPlainKey, pBlockSize, pDivStr)
	
	### RETURNS STRING IN BLOCKS SEPARATED BY DIV STR ###
	#   IF pPlainKey is not evenly divisible by Block Size
	#   then the last block will be shorter.
	
	set lenKey to length of pPlainKey
	set keyList to {}
	
	--- BUILD LIST OF KEY BLOCKS ---
	repeat with iStart from 1 to lenKey by pBlockSize
		set iEnd to iStart + pBlockSize - 1
		if (iEnd > lenKey) then set iEnd to lenKey
		set end of keyList to text iStart thru iEnd of pPlainKey
	end repeat
	
	--- CONVERT KEY LIST TO STRING ---
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to pDivStr
	set actKey to keyList as text
	set AppleScript's text item delimiters to astid
	
	return actKey
	
end getActKey

Nigel,
You are very much welcome.

Thank you for taking the time to review and improve my script. I really appreciate that!

Your use of the “by pBlockSize” and “if (iEnd > lenKey) then set iEnd to lenKey” is very clever. :cool:
I should have thought of that! :wink:

That simplifies and compacts the code.

As Nigel has mentioned, the code isn’t actually simplified or made more compact in runtime. Here an improved version with if-statement at the proper place: if no hyphen is needed then return string as original:

set PlainKey to "1234567890ABCDEFGHIJ" as string
set ActivationKey to getActKey(PlainKey, 5, "-")

on getActKey(pPlainKey, pBlockSize, pDivStr)
	-- get length of argument because we need it multiple times
	set lenKey to count pPlainKey
	
	-- if we don't need to place any hyphens, return string
	if lenKey ≤ pBlockSize then return pPlainKey
	
	-- put the text items seperated by length in this list
	set keyList to {}
	
	-- only repeat when we need to place 1 or more pDivStr
	-- this loop will get all the text items except the last one
	repeat with iStart from 1 to (lenKey - pBlockSize) by pBlockSize
		set end of keyList to text iStart thru (iStart + pBlockSize - 1) of pPlainKey
	end repeat
	
	-- the last text item is variable in length, therefore get it outside the loop.
	set end of keyList to text (iStart + pBlockSize) thru -1 of pPlainKey
	
	-- join the text items into a string and use pDivStr as separator
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to pDivStr
	set actKey to keyList as text
	set AppleScript's text item delimiters to astid
	
	return actKey
end getActKey

edit: Added comments in the code.

Actually, Nigel said:

IAC, when I said “That simplifies and compacts the code.”, I was referring to the text of Nigel’s code, in comparison with my code. IMO, his code is definitely simpler and more compact. Of course, you are entitled to your opinion.

Thanks for taking the time to review and to continue to improve my script (as improved by Nigel). Pulling the last block outside of the repeat loop is very clever. :cool:

So, can I get you guys to review and improve all my scripts? :smiley:

Just kidding of course, but that would be awesome. :cool:

We could always start with only one :wink:

Well . ye-e-ess … :wink:

My version expends one integer comparison per block checking for the unlikely (but not impossible) circumstance that the lenth of pPlainKey isn’t an exact multiple of pBlockize. If the last block is indeed short, the existing value of one variable is shared with another variable. Since the number of blocks is expected to be about four and odd lengths are not expected to occur, this gives your repeat an advantage of four fewer integer operations overall.

Your repeat requires an additional integer calculation to set up, but this is counterbalanced by the need for one fewer operations in the calculation of the range for the last block.

However, your version also needs to perform an integer comparison to check for the vanishingly small (but not zero) possibility that pPlainKey’s length is shorter than the block length. This means that your version has an expected advantage over mine of just three fewer integer operations per complete run ” although since you’ve chosen to implement this check as a compound comparison instead of a simple comparison followed by an ‘else’ statement, the actual time advantage is probably only the equivalent of between two and three integer ops.

Both our versions could be “improved” by precalculatiing pBlockSize - 1, which would reduce my version’s workload by three integer operations and yours’s by two, leaving your version with an overall time advantage of between just one and two integer operations per entire run of the script. I rest my case, your honour. :wink:

I see you’ve swapped round the ‘if lenKey’ and ‘set keyList’ lines since last night. There may also be a minute (but likewise undetectable) speed advantage if you use lenKey instead of -1 in the range for the last block. The integer subtraction in the set-up for the repeat can be eliminated by looping with the range-end variable instead of with the range-start one ” although then you need check before adding any short last block:

set PlainKey to "1234567890ABCDEFGHIJ"
set ActivationKey to getActKey(PlainKey, 5, "-")

on getActKey(pPlainKey, pBlockSize, pDivStr)
	set lenKey to count pPlainKey
	
	set keyList to {}
	set diff to pBlockSize - 1
	repeat with iEnd from pBlockSize to lenKey by pBlockSize
		set end of keyList to text (iEnd - diff) thru iEnd of pPlainKey
	end repeat
	
	set oddBlockLen to lenKey mod pBlockSize
	if (oddBlockLen > 0) then set end of keyList to text -oddBlockLen thru lenKey of pPlainKey
	
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to pDivStr
	set actKey to keyList as text
	set AppleScript's text item delimiters to astid
	
	return actKey
end getActKey

This is probably beating on a dead horse at this point, but I have two options that are somewhat different than above.

set {text item delimiters, theList, setoff} to {"-", {}, 5}

tell "1234567890ABCDEFGHIJ" to repeat with num from 1 to count by setoff
	try
		set theList's end to text num thru (text (num + (setoff - 1)))
	on error --odd length
		set theList's end to text num thru -1
	end try
end repeat

theList as text

Possibly of interest to Nigel, as I used an alternative shell command/regex to his sed method:


set {theText, text item delimiters} to {"1234567890ABCDEFGHIJz", "-"}
(do shell script "ruby -e  'puts " & quote & theText & quote & ".scan /(.{5}|.+)/' ")'s paragraphs as text

I wasn’t aware of that. I know that that AppleScript will access each item in the list no matter which item your want[1]. I assumed that getting an item by positive or negative index have no difference in performance as a result. Thanks for pointing this out, even if the difference is minimal.

[1] There was an question in the AppleScript mailing list asking/pointing out that larger lists become slower even when you want the first or last item from it. AppleScript engineers told that AppleScript lists are actually vectors and not lists. The size of a vector doesn’t affect its speed which makes lists in AppleScript behave odd. The answer by AppleScript engineers (Chris) was that no matter which item you want from the list, the entire list needs to be counted and each item is validated before the item(s) is returned from it

Thanks, Marc. Interestingly, this took several seconds to execute the first time I tried it, but it’s been fairly instantaneous since.