increment a string that has leading zeros

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?

There’s probably more than one factor involved. In my test script, the repeat timing your method followed on from those timing the other two methods.

The above by itself always errors (on my machine) when run immediately after compiling, but not on subsequent runs without recompiling. Modifying it so that it returns the number of the iteration on which it errors .

use theLib : script "NSNumbers"
set myVar to "0999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
repeat with i from 1 to 100000
	try
		set myVar to (theLib's incrementString:myVar)
	on error
		return i
	end try
end repeat

. I’ve had these iteration results for five consecutive compile/runs: 12894, 13518, 13481, 13471, and 13713. But runs not preceded by recompiling have gone on to complete and return the correct answer in roughly one minute and fifty-six seconds.

An obvious feature of the script is that ‘myVar’ loses all those "9"s after the first iteration! If I delete ‘set myVar to’ inside the repeat, I get much earlier failures on runs immediately following compilations: eg. 1819, 1858, 1858, 1870, and 1871. On runs not following compilations, the script’s still going after five minutes. Stopping it on the five minute mark on two occasions has returned iteration numbers of 36495 and 35776.

I won’t change it anymore, it’s good enough for me :). I just updated the script 4 times to avoid 3 extra unnecessary posts.

Objective/Cocoa’s formatted strings uses C’s printf (or at least follows this standardization) so you can use C formatting formatting in Objective-C without any problem. Dynamic length if printing numbers is one of them. Because printf in bash follows the same printf standardization so you can use it there too.

do shell script "printf '%s %0*.0F' hello 30 43278"

OK, I get the same thing here – but only in AppleScript Editor. In Script Debugger and ASObjC Explorer, there’s no problem. Very odd. I also tried recompiling, exporting as an applet, and running that – no problem there, either.

I get similar here (although I get to about double the number of iterations) – but again, only in AppleScript Editor. In the others, it works fine.

It doesn’t work in ASObjC, though. I think the problem is that in the absence of guidance in the form of method signatures, the scripting bridge converts to/from NSNumbers rather than primitive types.

the appropriate Objective-C way is


 NSString *aString = @"0033620340000" ;
 NSUInteger stringLength = [aString length];
 NSDecimalNumber *decimalNumber = [[NSDecimalNumber decimalNumberWithString:aString] decimalNumberByAdding: [NSDecimalNumber one]];
 NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
 [formatter setPaddingCharacter:@"0"];
 [formatter setMinimumIntegerDigits:stringLength];
 [formatter stringFromNumber:decimalNumber];
 NSLog(@"%@", [formatter stringFromNumber:decimalNumber]);

which can be easily converted to ASOC


set aString to "0033620340000"
set stringLength to length of aString
set decimalNumber to current application's NSDecimalNumber's decimalNumberWithString:aString
set incrementedNumber to decimalNumber's decimalNumberByAdding:(current application's NSDecimalNumber's one())
set formatter to current application's NSNumberFormatter's alloc()'s init()
formatter's setPaddingCharacter:"0"
formatter's setMinimumIntegerDigits:stringLength
set incrementedNumberString to formatter's stringFromNumber:incrementedNumber
log incrementedNumberString as text

Hello.

I don’t understand why applying a NSDecimalNumber should be more appropriate than using NSString’s stringWithFormat, especially since we are dealing with numbers that are well within the ascii range of character sets, and are non-localized.

There isn’t much that can go wrong here, in fact the only exception that can be thrown, is if the format string is nil.
I don’t see any problems with my method memory managment-wise either, when it is all embedded in a method.

In all fairness, you could of course make a class method, and do the NSNumberFormatter stuff once, and assign it to a static, so you can reuse it, but there will still be overhead.

All I see is a lot of overhead, at least when we are speaking of Objective-C, now I you can’t do it differently in ASOC, then that is a totally different matter, then the solution is of course viable. :slight_smile: