looping faster

I am sure you know that this following script (which takes 12 sec to run):

set L to {}
set total to 0
set bignum to 20000
repeat with i from 1 to bignum
	set end of L to i
end repeat
repeat with i from 1 to bignum
	set total to total + (item i of L)
end repeat

can be much faster if written like this:

set L to {}
set refL to a reference to L
set total to 0
set bignum to 20000
repeat with i from 1 to bignum
	set end of L to i
end repeat
repeat with i from 1 to bignum
	set total to total + (item i of refL)
end repeat
total -- 12502500, and it took less than a second

But why I cannot implement it into my nested-loop below (which is a simplified version of a loop I need, which is a bit too slow. My script recursively compare one element of a list with all the others, and then it moves to the next. It started from the bottom, and at the end of each loop, it deletes the element just checked, to avoid repetition):

set my_Looped_List to {}
set ref_my_Looped_List to a reference to my_Looped_List
repeat with a from 1 to 300
	set the end of my_Looped_List to a as string
end repeat

set my_text2 to ""
repeat with i from (length of ref_my_Looped_List) to 1 by -1
	set my_item_outerloop to item i of ref_my_Looped_List
	repeat with x from (length of ref_my_Looped_List) to 1 by -1
		set my_item_innerloop to item x of ref_my_Looped_List
		set my_text2 to my_text2 & (my_item_outerloop & " " & my_item_innerloop) & return
	end repeat
	set ref_my_Looped_List to reverse of rest of reverse of ref_my_Looped_List
end repeat

display dialog my_text2

Why I cannot use the “reference” trick in my script?
Thanks !

Hi.

The value of ref_my_Looped_List is a reference to the variable my_Looped_List. At the end of the outer repeat, you reset it to the result of reversing the rest of the reverse of the list, so it no longer contains the reference to the variable. To maintain the reference, you need to set the list variable to the result instead of the variable containing the reference to it:

set my_Looped_List to reverse of rest of reverse of ref_my_Looped_List

But what’s taking the time in your script is the building of the text. Each concatenation creates a new text which is longer than the last, so the first time round the inner repeat …

set my_text2 to my_text2 & (my_item_outerloop & " " & my_item_innerloop) & return

… produces the strings:

“300”
"300 "
“300 300”
"300 300
"

… and sets my_text2 to the last one. The second time round the inner repeat, the strings are:

“300 300
300”
"300 300
300 "
“300 300
300 299”
"300 300
300 299
"

And so on. By the end of the process, the script has generated many thousands of intermediate strings, each longer than the previous one. It would be better to collect just strings representing complete lines, in a list (using a reference, of course :slight_smile: ), and coerce that to text at the end.

Also, you don’t need to create three new lists each time at the end of the outer repeat. You can simply reduce the repeat range of the inner repeat:

set my_Looped_List to {}
set ref_my_Looped_List to a reference to my_Looped_List
repeat with a from 1 to 300
	set the end of my_Looped_List to a as string
end repeat

-- A referenced list to collect the line strings:
set my_list2 to {}
set ref_my_list2 to a reference to my_list2

repeat with i from (length of ref_my_Looped_List) to 1 by -1
	set my_item_outerloop to item i of ref_my_Looped_List
	-- Start the inner repeat at the current value of i.
	repeat with x from i to 1 by -1
		set my_item_innerloop to item x of ref_my_Looped_List
		-- Set the end of the collector list to just the two numbers separated by a space.
		set end of ref_my_list2 to (my_item_outerloop & " " & my_item_innerloop)
	end repeat
end repeat

-- Coerce the collected lines into a single text using a return as a delimiter.
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to return
set my_text2 to my_list2 as text
set AppleScript's text item delimiters to astid


display dialog my_text2

Thanks Nigel.

The second part of your replay is beautiful, and I now see the advantage of using a list (as container) rather then thousands of intermediate strings.

But still it is not clear to me why using the “reference” in my script (even with your correction) does not improve its speed. While it does for the example provided in the Apple Guide.

Thanks again!

Hi.

It does actually improve the speed. But because 300 items isn’t a very long list, the difference isn’t very much on today’s machines. Only a tenth of a second or so on my iMac. In your script, this was being drowned out by the time taken to build the string.

In my version of the script, the list used to collect the strings ends up containing 45150 items. If you use my_list2 instead of ref_my_list2 in the line which adds the strings to the list, you’ll see it takes a lot longer.

Thanks for the clarification!

But, how do you know that the string ends up containing 45150 items? How do you monitor that?

By simply adding a line to the end of the script telling it to return the information. :slight_smile:

return length of my_list2 -- Or return length of ref_my_list2

Or:

return (count my_list2) -- Or return (count ref_my_list2)

Whether or not you actually see this result depends on how you have your script windows set up in Script Editor! I have mine set to show the Result pane at the bottom. (“View” menu → “Show Result”.) You can customise Script Editor’s default script window display to your own liking by setting up a script window the way you want it and selecting “Save as Default” from the “Window” menu. This saves the current window’s position, size, and panel configuration to be used when you open new script windows.


By the way, I’ve just noticed that in the repeat which populates the original 300-item list, we could have used the reference variable there too instead of the list variable.

set the end of ref_my_Looped_List to a as string

While you’re interested in references to list variables, you may like to know that an alternative to having the reference in a variable is to incorporate it into the script code itself. So instead of …

set my_Looped_List to {}
set ref_my_Looped_List to a reference to my_Looped_List
repeat with a from 1 to 300
	set the end of ref_my_Looped_List to a as string
end repeat

… you could have:

set my_Looped_List to {}

repeat with a from 1 to 300
	set the end of my my_Looped_List to a as string -- NB. the keyword 'my'.
end repeat

The two are directly equivalent. In both cases, the “reference” is to a variable belonging to the script.

Since a referenced variable has to “belong” to a script, it’s not possible to set up a references to local variables, such as those used in handlers, because they’re just temporary entities. You can only have references to globals, properties, and “top level” (or “run handler”) variables. In your script, all the code’s in the script’s implicit ‘run’ handler, so there’s no problem using references to the variables.

One trick you may sometimes see inside handlers is to use a script object to hold the list variable. While the script object itself may be in a local variable (‘o’ in the example below), it is a script and its property ‘lst’ can be referenced through it:

on make_list(n)
	script o -- The script label can be whatever you like. It's doesn't have to be 'o'.
		property lst : {}
	end script
	
	repeat with a from 1 to n
		set end of o's lst to a as string -- Reference to property 'lst' of script 'o'.
	end repeat
	
	return o's lst
end make_list

set my_Looped_List to make_list(300)

I hope this isn’t too confusing. :slight_smile: It is useful to know.

This thread blew my mind. Nigel Garvey writes Applescript in Script Editor?

I thought anyone so deeply Applescript-y would use Script Debugger.

Occasionally when a user has an error on their computer that I can’t reproduce on mine, I end up trying to debug in Script Editor, and lord is it painful to try go back.

Usually I can just install the free trial of Script Debugger on their machine, but occasionally it’s a machine where someone’s already done that.

  • Tom.

My car has keyless ignition, a satnav, a multimedia entertainment system, numerous automatic safety systems, countless personalisation settings, automatic handbrake, automatic engine cut-off in stationary traffic, cruise control, speed limiter, blind-spot monitoring, proximity sensors, panoramic windscreen, three power outlets, and lights on the self-folding wing mirrors. It can display photographs on the control screen, steer itself into parking spaces, and carry five people and their luggage in comfort. But I walk to the local shops.

:wink:

A lot of interesting concepts to absorb ! Thanks!

But yet I don’t understand why “duplicating” a variable by “referencing to variable”, which I guess use extra RAM, make script faster.
Clearly it works amazingly, like in the original top script.

It’s an AppleScript quirk that I’ve never fully understood myself. The last I heard, the speed difference was something to do with safety checks (such as ensuring that the list doesn’t contain itself) which are carried out when a list variable’s used directly but not, for some reason, when a reference is used to that variable. It’s one of the things in AppleScript you learn to stop trying to understand after a while. You just make a mental note that things are how they are and write your scripts accordingly. :wink:

You can count me in, I don’t use SD either. I often run SE in Xcode’s debugger but that’s to set break points in osaxen or play with StandardAddition.

AppleScript’s far too high level to think in ram memory. Memory manager in AppleScript is probably closer to FileMaker’s database engine.

I think it’s inevitable if an programming language is so weak typed as AS. One of first AS designers said he was expecting a stronger typed language because the paradigm of the current AS syntax is a poor reflection of the real thing. In the first example of ldicroce there is no way the compiler can predict what L will be at runtime, in the second example there is a better prediction like when using an external object using its property. When at compile time the path to an value can’t be predicted at more complex byte code will be generated so the value can be found at runtime.

Therefore by this will be fast too:

set L to {}
set total to 0
set bignum to 20000
repeat with i from 1 to bignum
   set end of L to i
end repeat
repeat with i from 1 to bignum
   set total to total + (item i of L of me)
end repeat

By adding " of me" behind variable L the compiler knows at compile time where L can be found at runtime resulting in more efficient byte code like using an external script object or reference.

Just to knock this one on the head, I haven’t said anywhere in this thread that I either do or don’t use Script Debugger. My mention of Script Editor’s Result pane was to help someone whom I judged was probably using Script Editor and who probably didn’t know about its Result pane, since they were using a dialog to display the 45150-paragraph result of their script.

The underlying message was: Does it even matter? No IDE or SCE improves the qualities of the programmer nor does it improves the quality of the code.

Wise words indeed. :slight_smile:

No, but assuming you aren’t a perfect programmer and at least occasionally have to debug something you wrote, it can save a huge amount of time when debugging.

ROTFALMAO! I have a car with no power windows, no powered rearview mirrors, no ABS brakes, and no key pod. But on the positive side, my “plain Jane” has just that much less to go wrong… :slight_smile: