Efficient way to set disparate variables to a single value

I stumbled upon an efficient construct for setting multiple variables to a single value. It is likely old ground for experienced scripters, but since I couldn’t find it by searching, I thought it might be a useful tidbit to share.

Let’s say you have ten disparate variables x1, x2, …, x10 that you would like to zero. The most obvious way is to set each variable to 0 explicitly:

set {x1, x2, x3, x4, x5, x6, x7, x8, x9, x10} to {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

-- then when it is time to zero them:

set {x1, x2, x3, x4, x5, x6, x7, x8, x9, x10} to {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

The problem with this approach is that if you add or subtract variables to the zeroing assignment statement, you must be sure that the number of zeros matches the number of variables (simple, but I’ve goofed up with that too many times in the real world.)

Packaging the variables into a list and processing the list in a repeat loop in the standard fashion doesn’t do the trick:

set {x1, x2, x3, x4, x5, x6, x7, x8, x9, x10} to {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

set theList to {x1, x2, x3, x4, x5, x6, x7, x8, x9, x10}
repeat with currItem in theList
	set currItem's contents to 0
end repeat

{x1, x2, x3, x4, x5, x6, x7, x8, x9, x10} --> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

Instead, package references to the variables in a list, and process those references in a repeat loop:

set {x1, x2, x3, x4, x5, x6, x7, x8, x9, x10} to {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

set theList to {a reference to x1, a reference to x2, a reference to x3, a reference to x4, a reference to x5, a reference to x6, a reference to x7, a reference to x8, a reference to x9, a reference to x10}
repeat with currItem in theList
	set currItem's contents's contents to 0
end repeat

{x1, x2, x3, x4, x5, x6, x7, x8, x9, x10} --> {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

Actually, it doesn’t matter if there are more values than variables: the surplus ones are just ignored. But trying to set more variables than there are values gives an error.

All those 'a reference to’s may seem like a lot of typing, but you only need to type ‘ref’ into AppleScript Editor and it will automatically be expanded to ‘a reference to’ when you compile the script.

By the way, the most “efficient” way to reset the variables would be to set each one explicitly in its own line. :slight_smile:

Thank you for the pointer about ‘ref’. To think of all the keystrokes I could have saved over the years…

Perhaps “non-repetitive” would have been a better word than “efficient”. :slight_smile:

I also like this trick for creating lists:

set myList to words of (do shell script "echo {1..100}")

or like your example:

set {x1, x2, x3, x4, x5, x6, x7, x8, x9, x10} to words of (do shell script "printf '%.0f ' 0.0{0..9}")

or

set {x1, x2, x3, x4, x5} to words of (do shell script "echo {1..5}")

There are a few more here: http://www.catonmat.net/blog/bash-one-liners-explained-part-two/

A detour from the original topic, but…

I wasn’t familiar with brace expansion {1…100}. Very nice.

Using “words of” yields a list of consecutive numbers as text strings. Drawing on the bash one-liners you referred to, here’s a “one”-liner that creates a list of consecutive integers:

run script (do shell script "echo {$(printf ',%d' {1..100})}") --> {1, 2, ..., 100}
-- or more generally:
run script (do shell script "echo {$(printf ',%d' {Nstart..Nend})}") --> {Nstart, Nstart + 1, ..., Nend}

And here’s a “one”-liner that creates a list of integer or real numbers all of the same value:

run script (do shell script "echo {$(printf ', 0 %.s' {1..100})}") --> {0, 0, ..., 0} --> 0 replicated 100 times
-- or more generally:
run script (do shell script "echo {$(printf ', X %.s' {1..N})}") --> {X, X, ..., X} --> X replicated N times

The latter method provides an alternative way of addressing the original topic of this post, namely setting a list of disparate variables to a single value:

set {x1, x2, x3, x4, x5, x6, x7, x8, x9, x10} to {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

set theList to {x1, x2, x3, x4, x5, x6, x7, x8, x9, x10}
set {x1, x2, x3, x4, x5, x6, x7, x8, x9, x10} to run script (do shell script "echo {$(printf ', 0 %.s' {1.." & (count theList) & "})}")

{x1, x2, x3, x4, x5, x6, x7, x8, x9, x10} --> {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

One limitation of the above shell scripts is that they fail with a “stack overflow” error if the number of list items is too great (>8078 on my computer.)

Hello.

This is another approach, by using a handler, where you’ll supply the length of the list as an argument, which is not a totally good approach, but you can supply the length dynamically, which leads to less editing.

set {yr, mnt, dy, hr, min, sec} to {1987, 11, 5, 12, 20, 21}
set l to {yr, mnt, dy, hr, min, sec}
set {yr, mnt, dy, hr, min, sec} to zeroOut((length of l))
set l to zeroOut(length of l)
log "" & yr
on zeroOut(l)
	local nl
	set nl to {}
	repeat l times
		set end of nl to 0
	end repeat
	return nl
end zeroOut

The approach below saves a lot of iterations, but you’ll have to preparate some, up front, assuming you want to keep the list of variables in a pristine condition.

set {yr, mnt, dy, hr, min, sec} to {1987, 11, 5, 12, 20, 21}
set l to {yr, mnt, dy, hr, min, sec}
set len to length of l
set l to items 1 thru len of zeroOut(len)
log "" & yr

on zeroOut(l)
	local nl
	set nl to {}
	repeat ((l div 10) + 1) times
		set nl to nl & {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	end repeat
	return nl
end zeroOut

This also makes it (even) nicer to work with matrices in AppleScript. :slight_smile:

# you'll have to do it differently with matrices.
# Although, you'll clear a matrix by variables in
# a list.

set matr to {¬
	{1, 2, 3}, ¬
	{4, 5, 6}, ¬
	{7, 8, 9}}

# nice way to initialize lots of variables to zero.
set {a, b, c, d, e, f, g, h, i} to items 1 thru 9 of zeroout(9)

set {¬
	{a, b, c}, ¬
	{d, e, f}, ¬
	{g, h, i}} to matr

set vars to {¬
	{a, b, c}, ¬
	{d, e, f}, ¬
	{g, h, i}}

log "" & vars

set {¬
	{a, b, c}, ¬
	{d, e, f}, ¬
	{g, h, i}} to clear3X3Matr()

set posed to T3X3(vars)

# Clears contents of a matrix.
on clear3X3Matr()
	return {¬
		{0, 0, 0}, ¬
		{0, 0, 0}, ¬
		{0, 0, 0}}
end clear3X3Matr

on T3X3(mat)
	tell mat
		return ({¬
			{item 1 of item 1, item 1 of item 2, item 1 of item 3}, ¬
			{item 2 of item 2, item 2 of item 2, item 2 of item 3}, ¬
			{item 3 of item 1, item 3 of item 2, item 3 of item 3}})
	end tell
end T3X3

on zeroout(l)
	local nl
	set nl to {}
	repeat ((l div 10) + 1) times
		set nl to nl & {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	end repeat
	return nl
end zeroout

One simplification I might suggest in your first method, which has the appeal of being a “standard” Applescript approach, is to let the zeroing handler calculate the list length:

set {yr, mnt, dy, hr, min, sec} to {1987, 11, 5, 12, 20, 21}
set {yr, mnt, dy, hr, min, sec} to zeroOut({yr, mnt, dy, hr, min, sec})

log {yr, mnt, dy, hr, min, sec} --> {0, 0, 0, 0, 0, 0}

on zeroOut(l)
	set nl to {}
	repeat (length of l) times
		set end of nl to 0
	end repeat
	return nl
end zeroOut

The original solution I submitted is the only one that doesn’t require the list to be coded twice. If for some reason I don’t want to use a handler call, I might favor this approach to decrease code clutter:


set {yr, mnt, dy, hr, min, sec} to {1987, 11, 5, 12, 20, 21}
repeat with currItem in {a reference to yr, a reference to mnt, a reference to dy, a reference to hr, a reference to min, a reference to sec}
	set currItem's contents's contents to 0
end repeat

log {yr, mnt, dy, hr, min, sec} --> {0, 0, 0, 0, 0, 0}

Hello.

Your referencing, is so complicated, it has to stick! :slight_smile: Agreeing to what you said about length I ended up with this:

on zeroOut(ll)
	local nl, l
	set l to length of ll
	set nl to {}
	repeat ((l div 10) + 1) times
		set nl to nl & {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	end repeat
	return items 1 thru l of nl
end zeroOut

It is as self contained as it can be, still accurate in the number of items, and reasonably fast as well.

The difference is that McUsr needs to create/process a new list and assign them to to associated variables after each change whereas bmose can change an item in the list and the variable is changed with it as well. That you need to set multiple variables in a single line for efficiency is a bit outdated. The speed difference is hardly noticable so for better readability I would use:

set {yr, mnt, dy, hr, min, sec} to {1987, 11, 5, 12, 20, 21}

set refList to {}
set end of refList to a reference to yr
set end of refList to a reference to mnt
set end of refList to a reference to dy
set end of refList to a reference to hr
set end of refList to a reference to min
set end of refList to a reference to sec

set contents of item 3 of refList to 6

return dy

Hello.

As far as I know, there are two ways to update variables in a list, either have a list with references, or through direct list assignment, that is set {a,b,c} to {1,2,3}, otherwise, only the values in the list are updated. (Lots of formatting avoided here, as the previous paragraph would have looked like a christmas tree!.)

I just wrote this post to remark, that in the code below, is the way to assign a single variable in a list a new value.
(Should you ever need to do just that.)

set {y, m, d} to {1987, 1, 1}
set {y, m, d} to {y, m, 2}
log "" & d

Hi.

I mentioned above (in post #2) that it’s OK for the value list to be longer than the variable list. With that in mind, you can set up a global or property with a list containing a reasonable number of zeros ” say 20 ” and as long as you don’t set more than that number of variables in any one command, you can keep using the list over and over again:

property zeros : {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

set {a, b, c, d, e, f, g, h, i, j, k, l, m} to zeros
set {p, q, r} to zeros
set {z} to zeros -- or: 'set z to 0'!

The obvious advantages are that it greatly simplifies the in-line code and, since it’s the same list and the same zeros every time, it potentially saves time and memory.

Nigel, of all the variants described, I like yours the most by far. It is simple (of the kind that makes one say, “Why didn’t I think of that?”), non-repetetive, uncluttered, and efficient. One must simply be sure that the zeros list is longer than any list that might need to be zeroed, a minor task. Thank you.

P.S. A word of caution to any who might be inclined to use the “one”-liner run script/do shell script solutions mentioned earlier (other than the fact that they are hack-ish). Quick testing reveals that they run between 20 and 200 times slower than plain Applescript solutions. For large lists on the order of a thousand or more numbers, the execution time can become significant.

Why do you use run script in front of do shell script?

To eval the string “{1, 2, 3, …, 98, 99, 100}” into an AppleScript list

I see…


set xxx to "{1,2,3}"
run script xxx