increment a string that has leading zeros

We don’t know how big integer values get, you do know, but using a do shell script has the same boundaries as my first example. meaning that integer values become 2 ^ 53 (2 ^ 63 for bash) or larger it will lose it’s precision.

Hello.

First of all, let me say that when I wanted to break up the number, then it was because I assumed the number to be used in a series for catalouging something, and not say a “running-number” used to say; indexing a transaction.

As such, I saw that the number be used more as a symbolic number to denote, some kind of order, and then I felt, and still feel that it is better to use another datastructure than an integer to represent that “model-object”.

By the way: the Ariadne incident, (we should all be happy for the fact that there weren’t humans on board) due to using a library built for 32-bit on a 64 bit platform actually is something that I (like many others) have learned something from, when it comes to having old software run on a new platform.

Back to topic: I think Nigel’s solution is the best when done in AppleScript since he throws an error, I have just tried Nik’s expr, and that works with large integers, and is probably the best solution if the numbers that needs to be incremented is more likely to be above the 530 million magnitude, there is however a certain price to pay with regards to speed.

I haven’t tried this, but maybe the seq command are up for the job, and can deliver faster, but then again: you have to precompute the end value.

Why not make a composite solution Nik, where you implement your do shell script solution where Nigel’s throws an error?

Then you have the best of both worlds. :slight_smile:

Great suggestions guys. I’ll try and digest all of you points on a clear mind and come up with a final solution.

Thanks again for all of your help,
Nik

The main difference between Nigel’s version and mine is that I’ll make “99” + 1 just into “100” while Nigel’s version will throw an error. However Nigel’s script, like mine, can handle larger numbers than bash, so there is still gap in best of both worlds. To make Nigel’s script work like mine you can simply add a 1 to the beginning of the string when s is not 0 instead of throwing an error, but that only works when you increment the value by 1.

Do you know the reason? If you take a look at Nik’s script you’ll see that Nik doesn’t want the error to be thrown for that particular reason.

Hello.

It seems to me that expr handles numbers greater than 2^53:

Below is 2^53 + 5 * 10^9 (billions using the english name for it.)

It’s an 64 bit integer limitation but it’s still an limitation. But if the TS is okay with these boundaries, or if the given number is a 64 bit integer (which I doubt because it has leading zeros), then the problem is solved. Still I think it’s good to show a version like mine and Nigel that has no boundaries whatsoever, only the length of an string or list meaning the same boundaries as the given value type.

I agree totally, about the goodness to see the solution. On the other hand, you should get about 18 digits or numbers up around this magnitude: (2^62) 4611686018427387904 and seq works in this area, I think it ceases to work at 2^63 -1.

IMHO the OP should have enough options to implement a suitable solution. :slight_smile:

Edit

bc is the option we haven’t touched, I can’t recall it, but maybe if you set the scale factor great enough, you can have arbitrarily large numbers with leading zeroes returned. Another path well worth investigating.

I think so too :slight_smile:

Edit: I’ve updated my post #7 so it only works with string objects to stick to the same boundaries as the given string value. And removed some duplicate code.

That was before I came to think of bc and the scale of numbers that prepends the numbers with leading zeroes if I am not remembering it totally wrong. :smiley:

Have a good evening DJ.

Here’s an alternative using an ASObjC library. It uses the NSDecimalNumber class, which is designed to work around the problems DJ mentioned. It still has a limit, but it’s 38 digits (so input should be limited to 37 digits, excluding leading zeros).

Put this in the lib:

use framework "Foundation"

on incrementString:numberString
	set theLength to length of numberString -- needed to calculate number of leading zeros
	set theNum to current application's NSDecimalNumber's decimalNumberWithString:numberString -- make decimal number
	set theNum to theNum's decimalNumberByAdding:(current application's NSDecimalNumber's one()) -- add 1
	set newString to theNum's description() -- turn back to string
	set newStringLength to newString's |length|() as integer
	set leadingZeros to current application's NSString's |string|() -- build leading zero string
	set leadingZeros to leadingZeros's stringByPaddingToLength:(theLength - newStringLength) withString:"0" startingAtIndex:0
	return (leadingZeros's stringByAppendingString:newString) as text -- add leading zeros to string
end incrementString:

And call it like this:

use theLib : script "<name of lib>" 

set myVar to "00336203400000000000000000000000987654321"
theLib's incrementString:myVar
--> "00336203400000000000000000000000987654322"

Hello.

That is a lot faster than bc, which can handle integers up to 40 digits. Shane’s solution gets my vote, now that I have seen it. (I guess blend3 is familiar with using an ASOC library.)

Agreed. Here’s an amended ASObjC solution:

use framework "Foundation"

on incrementString:numberString
	set theBits to {}
	repeat
		set theLength to length of numberString
		if theLength > 38 then
			set lastBit to text -37 thru -1 of numberString
			set numberString to text 1 thru -38 of numberString
			set lastBitLength to 37
			set lastString to false
		else
			set lastBit to numberString
			set numberString to ""
			set lastBitLength to theLength
			set lastString to true
		end if
		set theNum to current application's NSDecimalNumber's decimalNumberWithString:lastBit -- make decimal number
		set theNum to theNum's decimalNumberByAdding:(current application's NSDecimalNumber's one()) -- add 1
		set newString to theNum's description() as text -- turn back to string
		set newStringLength to length of newString
		if newStringLength > lastBitLength then -- need to carry one
			set carryOne to true
			set beginning of theBits to text 2 thru -1 of newString
		else
			set carryOne to false
			set leadingZeros to current application's NSString's |string|() -- build leading zero string
			set leadingZeros to leadingZeros's stringByPaddingToLength:(lastBitLength - newStringLength) withString:"0" startingAtIndex:0
			set beginning of theBits to (leadingZeros's stringByAppendingString:newString) as text -- add leading zeros to string
		end if
		if lastString then exit repeat
		if not carryOne then
			set beginning of theBits to numberString
			exit repeat
		end if
	end repeat
	set saveTID to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {""}
	set theBits to theBits as text
	set AppleScript's text item delimiters to saveTID
	return theBits
end incrementString:

Called like:

use theLib : script "<lib name>" 
use scripting additions

set myVar to "0999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
set x to theLib's incrementString:myVar
-->"1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

It could probably be tidied up a bit…

Nice :cool:

‘description’ has to be changed to ‘|description|’ for Shane’s script to compile in AppleScript Editor.

Comparing the performances of DJ’s script (post #7, in the version present as I write), mine (post #13), and Shane’s (post #26):

¢ With blend3’s example text “0033620340000”, DJ’s is the fastest, consistently just beating mine by a tiny amount. Shane’s takes about four-and-a-half to five-and-a-quarter times as long.

¢ With the pathological text example given with Shane’s script, Shane’s script wins hands down for speed ” BUT, interestingly, it crashes with error number -4960 if it’s tested over too many iterations of the timing loop. It survives being repeated 52 times, it may or may not survive 53 to 55 repeats, it always crashes with 56 or more repeats. With the shorter text, the crash point’s somewhere between 5000 and 10000 iterations. (Only the call to the handler is included in the repeat in my tests.)

It is awkward not to be fully fluent in ASOC, because Shane’s first handler, can be shortened in Objective-C. But I have no idea if this is possible with ASOC.

Interesting !

I will stay tuned because I have a script using ASObjC in a library which issue the same error numbers -4960 at first run then behave flawlessly at 2nd and higher attempts.

Yvan KOENIG (VALLAURIS, France) mardi 11 mars 2014 17:43:10

I’m not surprised there’s a big difference. Assuming you’re incrementing by 1 in a repeat loop, 90% of cases will never need to look beyond the last character, whereas the ASObjC method will take pretty much the same time regardless – “0999999999999” and “000000000000” will take the same time. So 90% of the time there’s a lot of unnecessary overhead. If I add one line like this at the beginning:

	if character -1 of numberString is in "012345678" then return (text 1 thru -2 of numberString & (((character -1 of numberString) as integer) + 1))

the times drop drastically, because only 10% of numbers get any further. A second line for character -2 would reduce it to 1%.

Very odd. I just ran this with no problem:

use theLib : script "NSNumbers"
set myVar to "0999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
repeat 100000 times
	set myVar to theLib's incrementString:myVar
end repeat

Seems to work fine. :expressionless:

But you’re hard-coding the length with the 12 there. And if you use a variable to build the format string, you’re going to get compiler warnings about what a dangerous thing that is to do.

It was just a quick demo, so I didn’t specify an argument for the length, a * is a placeholder for the length in a format string, so, it could look like something like below, in order to handle variable length. (The intLen, could be passed in as a parameter for the length.) I’m not sure if how you do this, if you take the integerValue from the passed in number, or whatever, in order to make this to work with ASOC. :rolleyes:

Edit
I use -Wevertyhing, and I don’t get any compiler warnings with clang (when compiling as Objective-C).

Hi everybody,

So why did it crash after 52 loops?