AppleScript – How to Set a Variable to All Numbers in a List

applescript

Lets say I have a variable set to this:

set myVar to {"asdf", "qwerty24", "23", "22", "21abc" "20"}

How can I set a variable to the numbers in myVar, including those with text inside of the item–but without keeping the part that is text.
Each item should only contain one number, if an item were to consist of multiple numbers ie "302 303" then the outputted result would preferably be just combined to "302303". Any spaces in the item need to be removed to, just like the text. If each number needs to be an integer so "0abc.1200" needs to have it's period removed and result with "1200". The result would be preferably listed as {"24", "23", "22", "21", "20"}, instead of being listed as {24, 23, 22, 21, 20}.

Best Answer

Objective: To process an AppleScript list of strings so that each string is filtered to remove any non-digit character followed by all leading zeroes. Resultant empty strings are discarded.

property digits : "0123456789"
property text item delimiters : {}


set L to {"abc", "qwerty.24", "01 abc23xyz", "123abc456", "2.1abc", "20"}

repeat with textItem in L
    set stringOfDigits to filterCharacters from textItem thru digits
    set numericalString to the processedNumericalString(stringOfDigits)

    set the contents of the textItem to the numericalString
end repeat

return the strings in L --> {"24", "123", "123456", "21", "20"}

------------------------------------------------------------------------------------------
# HANDLERS:
to filterCharacters from someText as text thru allowedCharacters as text
    local someText, allowedCharacters

    set tid to my text item delimiters -- to reset them after

    set my text item delimiters to {missing value} & characters in the allowedCharacters
    set illegalCharacters to characters of (text items of someText as text)

    set my text item delimiters to {missing value} & the illegalCharacters
    set filteredText to the text items of someText as text

    set my text item delimiters to tid
    return the filteredText
end filterCharacters


on processedNumericalString(numericalString as text)
    local numericalString

    if the numericalString = "" then return missing value

    try
        the numericalString as number
    on error
        the numericalString
    end try

    return the result as text
end processedNumericalString

Breakdown

property declarations

A property is similar to a variable, except for two differences: properties are persistent, and have global scope; variables are disposed of when a script ends, and default to local scope. I will let you research those terms if you need to, as it's beyond the remit of this answer. I almost never use a property for either of those features I just mentioned. Rather, I use them most often to separate storage of values that I generally intend to remain constant throughout the script (as opposed to variables that can be expected to change at any stage). AppleScript doesn't have a way to declare constants like other languages, so this is just a bastardisation of another construct.

Handlers

Handlers in AppleScript can be thought of to be what other languages refer to as a function, and the actual differences are nuanced and mostly academic, with one practical implication being that handlers aren't self-aware so can't report any information about themselves back to you. But, like functions, they receive arguments (or parameters), do something with them, and spit out a result at the end in most cases.

  • filterCharacters: this is the core of the script in terms of where the heavy lifting is done. It receives a single piece of text you wish to be filtered, and a list of allowed characters, and removes everything else, i.e. it extracts occurrences of allowed characters in the order they occur. It does this by utilising AppleScript's text item delimiters, which you should read about more thoroughly, but essentially deletes all instances of delimiters in the list you supply it, splitting the string into fragments at the points of deletion. So, it's almost performing the opposite function that we want, and can be thought of being a text filter that you supply a list of illegals rather than a list of allowables. Therefore, I use them twice: first to strip out the allowables, leaving only the illegals; then giving that list of illegals back to text item delimiters, which then get stripped from the original piece of text, thus leaving us only with allowables. Very efficient; avoids iterating through characters in the piece of text.

  • processedNumericalString(): This handler is just a cheap and lazy way to strip off the leading zeroes once the first handler has given us a string containing only digits. It would be arguably more correct to iterate through each digit character until reaching the first non-zero character, deleting the ones that are, with a special case to prevent "0" being obliterated. But, instead, I just decided to coerce the string to a number, then back to a string. A hack that works effectively and one assumes more speedily too (though that's not definitive).

The main body of the script

With the two handlers defined at the bottom of the script, all that remains is to iterate through the list of strings, sending each string in turn to each handler. What comes back is the piece of text you desire, which set the contents of... is what lets me replace the original item in the list with the new version. I was mindful not to retain empty strings (""), and the processedNumericalString() handler swaps those out for an AppleScript object called missing value that represents exactly what it says (it's the closest pre-defined AppleScript object to what other languages often call null or nil). Anyway, the important this is it isn't a string, because the final line of the script's run is:

return the strings in L

and this does exactly that: any item in the list that is not a string (or text) class of object is discarded. You can, of course, swap this line for:

set myVar to the strings in L

if you need it assigned to a variable in order to keep using it.


ADDENDUM

The OP added:

The list I described as myVar in the original post is always changing over time, I want the process of taking the items in myVar and isolating all the numbers to repeat indefinitely

OK. Let's bung the main part of the script into another handler:

to formNumericalStringsWithItems from L as list
    local L

    repeat with textItem in L
        set stringOfDigits to filterCharacters from textItem thru digits
        set numericalString to the processedNumericalString(stringOfDigits)

        set the contents of the textItem to the numericalString
    end repeat

    return the strings in L
end formNumericalStringsWithItems

Place this with the other handlers to keep it separate and out of the way now. The main body of the script is reduced to these two lines, where I've used the identifier myVar instead of L just for clarity:

set myVar to {"abc", "qwerty.24", "01 abc23xyz", "123abc456", "2.1abc", "20"}
set myNewAndImprovedVar to formNumericalStringsWithItems from myVar

Now, wherever myVar in your script gets updated/changed, you follow it with a call to this new handler, as I've illustrated here.

Just be clear about how I've delineated the different "parts" of the script: keep the property declarations at the top—don't put them inside your repeat loop; also don't put the handler declarations inside the repeat loop—I put all of mine at the bottom of the script, where I've marked a line and headed a section only for handlers. The "main body" of the script (despite now only being two lines here) is what your code will occupy, so keep things neat and grouped (it's why I separate the property declarations from the main body by two paragraph breaks instead of just one).