Convert String of Numbers to List of Numbers

asobjc

(Jim Underwood) #1

I just discovered this very cool, compact script by @ShaneStanley, and just wanted to share it with you guys.

Thanks for a great script, Shane.

(*
REFERENCE:

Topic Title:  How do I convert a string of numbers from text to a list if integers?
URL:    http://macscripter.netviewtopic.php?pid=169183#p169183

Date:    2013-11-28
Author:  Shane Stanley

Shane:  "It's a lot shorter, but takes a lot longer to run."

Note:  Script slightly modified by @JMichaelTX to add a bit of clarity.
*)

--- It seems very fast to me in 2017-06-08, running Script Debugger 6.0.4 (6A198) on macOS 10.11.6 --

set stringOfNumbers to "1, -2, 3.1234, 4, -95.123"
set listOfNumbers to run script ("{" & stringOfNumbers & "}")

-->{1, -2, 3.1234, 4, -95.123}

(Shane Stanley) #2

I don’t know that I’d call any script that resorts to run script as cool – to me, it has a vaguely dirty feel about it. OTOH, in this particular case, it may be a good choice.

But I suspect that in the real world, most of the time the string will contain all reals or all integers, not a mixture of both. In those cases, using text item delimiters and coercing is really much nicer code IMO, as well as faster – and not particularly long.

Alternatively, and faster with longer lists:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set stringOfNumbers to "1, -2, 3.1234, 4, -95.123"
set stringOfNumbers to current application's NSString's stringWithString:stringOfNumbers
set listOfNumbers to ((stringOfNumbers's componentsSeparatedByString:",")'s valueForKey:"doubleValue") as list

(Jim Underwood) #3

But then, “cool” is like “beauty”. I’ll leave it at that.

Perhaps it is with longer lists, but not with short ones.
My test of the above running Script Debugger 6.0.4 (6A198) on macOS 10.11.6:

  • ASOBjC: 0.19 sec
  • Run Script: 0.01 sec

But it’s always good to have speciality tools when you need them.


(Shane Stanley) #4

Including for script timing. Script Debugger’s times are indicative, but the nature of a script editor means they can only ever be very rough. Try something like Script Geek:

https://www.macosxautomation.com/applescript/apps/Script_Geek.html

It’s still not perfect – timing for AppleScript is always dependent on the host – but it eliminates a lot of overhead. Using it and the ASObjC method with a list of 40 entries, I time 100 iterations at 0.031 seconds. That’s about 0.0003 per iteration.


(Jim Underwood) #5

I tested both scripts using your Script Geek. I compiled each and then ran for 10 times:

I then ran both again (for 10 trials), and got identical results:

So, my conclusion has not changed. At least for small lists, there is no penalty for using the original script that used run script, and there may be a small performance advantage. The run script method also offers a very compact solution, and works with both integers and reals in the same list.


(Jim Underwood) #6

Thanks again Shane.

So both us dealt with a list in which all of its items could be converted to a number.
But what about the case of mixed list, containing letters, integers, reals, as in:
{"Some alpha text", "1", "3.14"}

Here’s the best I can do:

set myList to {"Some alpha text", "1", "3.14"}
repeat with  oItem in myList
    set contents of oItem to my textToNum(contents of oItem)
end repeat

on textToNum(pString)
  set numOrText to pString
  try
    set numOrText to numOrText as number
  on error errMsg number errNum
    set x to numOrText
  end try
  return numOrText
end textToNum

--> {"Some alpha text", 1, 3.14}

Is there a better way? Anyone?


(Nigel Garvey) #7

Here’s a stab at an ASObjC solution:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set myList to {"Some alpha text", "1", "-3.14"}

set myArray to current application's class "NSMutableArray"'s arrayWithArray:(myList)
repeat with thisString in myArray
	if ((thisString's rangeOfString:("^[0-9.-]++$") options:(current application's NSRegularExpressionSearch))'s |length|() > 0) then set thisString's contents to thisString as text as number
end repeat
set myList to myArray as list

Or, performing the changes in the original list:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set myList to {"Some alpha text", "1", "-3.14"}

set myArray to current application's class "NSArray"'s arrayWithArray:(myList)
repeat with i from 1 to (count myList)
	if (((myArray's item i)'s rangeOfString:("^[0-9.-]++$") options:(current application's NSRegularExpressionSearch))'s |length|() > 0) then set myList's item i to myList's item i as number
end repeat
return myList

(Shane Stanley) #8

And here’s a minor performance tweak to the original:

on textToNum(pString)
	try
		if "0123456789-." contains character 1 of pString then
			set pString to pString as number
		end if
	end try
	return pString
end textToNum

But I don’t see too much wrong with the original.


(CK) #9

Ah, that is a slick solution to the one issue that arises with the original and the way it coerces an empty string to 0.

My obvious response was to add a line to cater for this exceptional case, but it’s really pleasing when a streamlined solution also covers all the bases.

EDIT: Your version doesn’t cater for a numerical string with leading white space.


(Shane Stanley) #11

Yes, it should probably add that.


(Jim Underwood) #12

Thanks to everyone who responded to this question.
Based on everyone’s input, here is my revised script, which seems to work very fast and handle all cases.

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on textToNum(pString)
  --–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
  (*  VER: 1.1    2018-06-17
    PURPOSE:  
    PARAMETERS:
      • pString    | text |  Source String

    RETURNS:  | number or text or missing value |  
      • Number IF string can be converted;
      • Else source string unchanged.
      • missing value IF empty pString
      
    AUTHOR:  JMichaelTX
  --–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
  *)
  if (length of pString > 0) then
    try
      set numOrText to pString as number
    on error -- errMsg number errNum
      set numOrText to pString
    end try
  else -- IF pString is empty
    set numOrText to missing value
  end if
  
  return numOrText
end textToNum
--~~~~~~~~~~~~~~~~~~~~ END of Handler ~~~~~~~~~~~~~~~~~~~~~~~~~~~~


(Shane Stanley) #13

My comments where more in the context of using run script to avoid a bit of coding — your case is quite different.

It seems to me that we now have a sanctioned library scheme with use script, and although not perfect, it works well enough and is pretty flexible — no more stuff like hard-coding paths. It works fine for me. But then I don’t care that they’re not .applescript files, and I actually prefer my code to give an indication of where handlers come from.


(Stan Cleveland) #14

Greetings,

Am not sure if this adds anything of value to the conversation, but the following handler will load a script library regardless of the AS format in which it’s saved. No hard-coded path is needed, but the library file must be located in a valid “Script Libraries” folder and use the appropriate file extension for its format.

Stan C.

p.s. — If anyone is able extract the library’s path from the “on error” handler without using «class seld» of (offendingObject as record), I’d love to see how they did it.

set myLib1 to loadLibrary("myLib.app")
set myLib2 to loadLibrary("myLib.scpt")
set myLib3 to loadLibrary("myLib.scptd")
set myLib4 to loadLibrary("myLib.applescript")

on loadLibrary(libName)
	try
		-- succeeds if library is compiled (.app, .scpt, or .scptd file)
		set theLib to script libName
	on error errText number errNum from offendingObject
		if errText contains "doesn’t seem to belong" then
			-- library is saved as text (.applescript file)
			set libPath to «class seld» of (offendingObject as record) -- extract path	
			set theLib to run script ("script s" & return & ¬
				(read file libPath as «class utf8») & return & ¬
				"end script " & return & "return s")
		else -- unexpected error, so throw it
			error errText number errNum from offendingObject
		end if
	end try
	return theLib
end loadLibrary

(Nigel Garvey) #15

Hi Stan

In High Sierra (Edit: and El Capitan), you can coerce offendingObject directly to «class furl» or to alias:

on loadLibrary(libName)
	try
		-- succeeds if library is compiled (.app, .scpt, or .scptd file)
		set theLib to script libName
	on error errText number errNum from offendingObject
		if errText contains "doesn’t seem to belong" then
			-- library is saved as text (.applescript file)
			set theLib to run script ("script s" & return & ¬
				(read (offendingObject as «class furl») as «class utf8») & return & ¬
				"end script " & return & "return s")
		else -- unexpected error, so throw it
			error errText number errNum from offendingObject
		end if
	end try
	return theLib
end loadLibrary

(Stan Cleveland) #16

Hi Nigel,

Yes, both of those methods work here, since I’m running El Capitan. I’ve now upgraded the handler to your new version, which uses «class furl». Many thanks for the helpful insights!

Regards,
Stan C.