Working through Records

Is there a better, more efficient, way to loop my Aarons_Stores list / records? Often times I will only need the store_number from the list and just want to make sure I am doing it correctly.

set Aarons_Stores to {¬
{store_number:“F0346”, city:“Grand Junction”, region:“R0002”}, ¬
{store_number:“F0394”, city:“Cortez”, region:“R0002”}, ¬
{store_number:“F0657”, city:“Vernal”, region:“R0001”}, ¬
{store_number:“F0739”, city:“Rock Springs”, region:“R0001”}, ¬
{store_number:“F0858”, city:“Riverton”, region:“R0001”}, ¬
{store_number:“F0910”, city:“Rifle”, region:“R0002”}, ¬
{store_number:“F0935”, city:“Ontario”, region:“R0003”}, ¬
{store_number:“F1132”, city:“Evanston”, region:“R0001”}, ¬
{store_number:“TM001”, city:“Cortez”, region:“R0002”} ¬
}

repeat with x from 1 to length of Aarons_Stores
set Store to store_number of item x of Aarons_Stores
set region to region of item x of Aarons_Stores
display dialog “Store #” & x & " is " & Store & " and is aligned in Region " & region
end repeat

You may try :

set Aarons_Stores to {¬
	{store_number:"F0346", city:"Grand Junction", region:"R0002"}, ¬
	{store_number:"F0394", city:"Cortez", region:"R0002"}, ¬
	{store_number:"F0657", city:"Vernal", region:"R0001"}, ¬
	{store_number:"F0739", city:"Rock Springs", region:"R0001"}, ¬
	{store_number:"F0858", city:"Riverton", region:"R0001"}, ¬
	{store_number:"F0910", city:"Rifle", region:"R0002"}, ¬
	{store_number:"F0935", city:"Ontario", region:"R0003"}, ¬
	{store_number:"F1132", city:"Evanston", region:"R0001"}, ¬
	{store_number:"TM001", city:"Cortez", region:"R0002"} ¬
		}
repeat with x from 1 to count Aarons_Stores
	set {theStore, theCity, theRegion} to {store_number, city, region} of item x of Aarons_Stores
	--log "Store #" & x & " is " & theStore & " in city " & theCity & " and is aligned in Region " & theRegion
	display dialog "Store #" & x & " is " & theStore & " and is aligned in Region " & theRegion
end repeat

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) samedi 2 mars 2019 09:56:59

Hi.

What you’ve got is good enough for a short list like that. In general, though, if you need to access a fetched value (such as a record in your list) more than once, it’s more efficient to fetch it just once and store it in a variable, from which it can be accessed more quickly:

set Aarons_Stores to {¬
	{store_number:"F0346", city:"Grand Junction", region:"R0002"}, ¬
	{store_number:"F0394", city:"Cortez", region:"R0002"}, ¬
	{store_number:"F0657", city:"Vernal", region:"R0001"}, ¬
	{store_number:"F0739", city:"Rock Springs", region:"R0001"}, ¬
	{store_number:"F0858", city:"Riverton", region:"R0001"}, ¬
	{store_number:"F0910", city:"Rifle", region:"R0002"}, ¬
	{store_number:"F0935", city:"Ontario", region:"R0003"}, ¬
	{store_number:"F1132", city:"Evanston", region:"R0001"}, ¬
	{store_number:"TM001", city:"Cortez", region:"R0002"} ¬
		}

repeat with x from 1 to length of Aarons_Stores
	set thisRecord to item x of Aarons_Stores -- Fetch from the list just once and store in a variable
	set Store to store_number of thisRecord -- Read from the variable.
	set region to region of thisRecord -- Ditto.
	display dialog "Store #" & x & " is " & Store & " and is aligned in Region " & region
end repeat

There are also ways to read several properties of a record with just one command — although I’m not sure how “one command” they are beneath the bonnet!

repeat with x from 1 to length of Aarons_Stores
    set {Store, region} to {store_number, region} of item x of Aarons_Stores
    display dialog "Store #" & x & " is " & Store & " and is aligned in Region " & region
end repeat

Or:

repeat with x from 1 to length of Aarons_Stores
    set {store_number:Store, region:region} to item x of Aarons_Stores
    display dialog "Store #" & x & " is " & Store & " and is aligned in Region " & region
end repeat

When posting AppleScript code here, it would be great if you could wrap it in MacScripter’s special [applescript] and [/applescript] tags. These make it appear as above when posted, in a box with a clickable link which opens it it people’s default editors. There’s an “Applescript” button for them just above the text field in posting pages.

For a long list it would be efficient to drop the use of repeat with x from 1 to length of Aarons_Stores

set Aarons_Stores to {¬
	{store_number:"F0346", city:"Grand Junction", region:"R0002"}, ¬
	{store_number:"F0394", city:"Cortez", region:"R0002"}, ¬
	{store_number:"F0657", city:"Vernal", region:"R0001"}, ¬
	{store_number:"F0739", city:"Rock Springs", region:"R0001"}, ¬
	{store_number:"F0858", city:"Riverton", region:"R0001"}, ¬
	{store_number:"F0910", city:"Rifle", region:"R0002"}, ¬
	{store_number:"F0935", city:"Ontario", region:"R0003"}, ¬
	{store_number:"F1132", city:"Evanston", region:"R0001"}, ¬
	{store_number:"TM001", city:"Cortez", region:"R0002"} ¬
		}
set x to 0
repeat with aStore in Aarons_Stores
	set {theStore, theCity, theRegion} to {store_number, city, region} of aStore
	set x to x + 1
	--log "Store #" & x & " is " & theStore & " in city " & theCity & " and is aligned in Region " & theRegion
	display dialog "Store #" & x & " is " & theStore & " and is aligned in Region " & theRegion
end repeat

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) samedi 2 mars 2019 11:11:15

If the list is fairly long, I wondered if there would be any speed difference between:

repeat with x from 1 to length of Aarons_Stores

and

set listCount to length of Aarons_Stores
repeat with x from 1 to listCount

My thought was that getting the number of list items on every loop might slow things. Thanks.

A repeat loop only calculates the value of “length of Aarons_Stores” once at the beginning of the loop

here is an example

set mycount to 4

repeat with i from 1 to mycount
	display alert i giving up after 1
	set mycount to mycount - 2 --  recalculating mycount 
end repeat
display alert mycount giving up after 1

As you can see, even though I’m recalculating “mycount”, it still runs the repeat 4 times as this was the value of “mycount” at the start of the repeat loop

Thanks robertfern for the explanation and example, which makes clear what’s happening.

This is likely negated by having x + 1 evaluated within each iteration of your repeat loop (i.e. set x to x + 1).

If speed is important, and where there are a large number of items in a list (in the thousands), I might suggest looping through a reference to the list instead:

repeat with Store in (a reference to Aarons_Stores)
		log the store_number of the Store
end repeat

or:

repeat with Store in my Aarons_Stores
		log the store_number of the Store
end repeat

To do this with item-based access, you can put the list inside a script object, via which you would then access each item:

script Aarons_Stores
		property list : {"a", 2, pi, ...} -- a large list
end script

repeat with i from 1 to the length of the list of Aarons_Stores
		set Store to item i in the list of Aarons_Stores
		log the Store
end repeat

Have you made tests with a long list before posting that ?

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 4 mars 2019 17:38:23

I did. I read your initial claim regarding the efficiencies of the two repeat methods, and was motivated at this point to draft a simple script that wasn’t intending to give a conclusive performance evaluation in any way, but just provide a general sense of the truthfulness of that claim.

The result averaged over several runs was enough to accept your initial statement to have at least favourable odds of being true, which is all I need in this instance. (On the assumption that your statement is true, do you happen to have an explanation to account for the disparity? I’d be genuinely interested to know this.)

After this, I used the same script with the addition of a line that performs a single addition operation in each iteration, namely set x to x + 1. The result averaged similarly over several runs was consistent in the way it obliterated any previous advantage one style of loop had over another, equalising their execution times.

I deleted my test script, but the premise was to iterate through a 1000-items list of integers (which were the numbers 1 to 1000), and simply perform a log command on each element. This was, itself, put inside a repeat loop of the form repeat 100 times, to simulate an approximation of traversing a 100,000-items list that wouldn’t be practical to declare at compile time. This was done for each of the two styles of loop.

As I intimated already, this method is neither rigorous nor watertight, so I’m perfectly willing to accept a different result should anyone wish to perform a demonstrably superior test (which wouldn’t be hard) that yields a result with enough confidence to refute my supposition.

@CK
You are right.

I made new tests with the script :

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"


property longList1 : {}
on run
	my Germaine()
end run

on Germaine()
	set targetfile to (path to desktop as text) & "datas.txt"
	try
		close file targetfile
	end try
	
	tell application "System Events"
		delete file targetfile
	end tell
	
	set targetfile to targetfile as «class furl»
	set openFile to open for access targetfile with write permission
	
	# Fill a file starting from a blank one
	set i to 0
	set nbTimes to 10000
	repeat nbTimes times
		set i to i + 1
		write ("sample text " & i) & linefeed to openFile starting at eof as «class utf8»
	end repeat
	close access openFile
	
	say "read the file"
	set longList2 to paragraphs of (read targetfile)
	copy longList2 to longList1
	say "reading done"
	
	set stats to ""
	repeat 10 times
		set startDate1 to current application's NSDate's |date|()
		repeat with x from 1 to count longList2
			
			log "record #" & x & " is : " & item x of longList2
			
		end repeat
		set duration1 to -(startDate1's timeIntervalSinceNow()) as real
		set msg1 to "duration1" & tab & duration1 & tab & "seconds"
		say msg1
		
		set startDate2 to current application's NSDate's |date|()
		set x to 0
		repeat with aString in longList2
			set x to x + 1
			log "record #" & x & " is : " & aString
			
		end repeat
		set duration2 to -(startDate2's timeIntervalSinceNow()) as real
		set msg2 to "duration2" & tab & duration2 & tab & "seconds"
		say msg2
		
		set startDate3 to current application's NSDate's |date|()
		repeat with x from 1 to count my longList1
			
			log "record #" & x & " is : " & item x of my longList1
			
		end repeat
		set duration3 to -(startDate3's timeIntervalSinceNow()) as real
		set msg3 to "duration3" & tab & duration3 & tab & "seconds"
		say msg3
		
		set startDate4 to current application's NSDate's |date|()
		set x to 0
		repeat with aString in my longList1
			set x to x + 1
			log "record #" & x & " is : " & aString
			
		end repeat
		set duration4 to -(startDate4's timeIntervalSinceNow()) as real
		set msg4 to "duration4" & tab & duration4 & tab & "seconds"
		say msg4
		set stats to stats & linefeed & (msg1 & linefeed & msg2 & linefeed & msg3 & linefeed & msg4 & linefeed)
		
	end repeat
	
	
	set resultFile to (path to desktop as text) & "result.txt"
	try
		close resultFile
	end try
	tell application "System Events"
		delete file resultFile
	end tell
	set resultFile to resultFile as «class furl»
	set logFile to open for access resultFile with write permission
	write stats to logFile as «class utf8»
	close access logFile
end Germaine

It creates a text file with nbTimes lines.
Then it extract the contents 10 times using 4 schemes

(1) repeat with x from 1 to count longList1 (standard one)
(2) repeat with aString in longList1 (standard one)
(3) repeat with x from 1 to count of my longList2 (a property)
(4) repeat with aString in my longList2 (a property)

The average results with 10000 strings were:
(1) 10.849306084893 seconds
(2) 11.107601480051 seconds
(3) 7.773462458090 seconds
(4) 7.751518282023 seconds

With 100000 strings they were :
(1) 367.86496692354 seconds
(2) 373.629074811935 seconds
(3) 76.010842366652 seconds
(4) 75.601781541651 seconds

The difference between case 1 and 2 or between case 3 and 4 is small.
but the change introduced by the use of a property is serious.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mercredi 6 mars 2019 18:57:18

Nice find with the property. Speeds up the reading of my contacts database quite a lot.
:slight_smile:

I don’t believe the speed improvement is attributable to the use of a property, but rather—if you refer to my first reply in this thread where I suggested using either a reference to or my—is a demonstration of this recommendation in action, by the presence of my when referencing the property:

repeat with x from 1 to count my longList1
           
		log "record #" & x & " is : " & item x of my longList1
           
end repeat

I highly suspect that the speed improvements would reduce or disappear if you reference the property whilst omitting my. I’m not entirely sure whether speed is affected by calling the Germaine() handler with vs without my but I’m fairly positive that there won’t be a difference, and its presence is entirely superfluous in this instance.

The handler Germaine() is not related to speed efficiency.
It’s used so that no variables are stored in the file when the script is used.

Run this script then save it. as a Script

set maybe to (path to desktop as text)

Open the file with TextEdit.
At its very end you will see the path to your Desktop.

Now, run the script :

my Germaine()

on Germaine()
	set maybe to (path to desktop as text)
end Germaine

ans save it.
Open the file with TextEdit. You will see the name of the handler but you will not see the path to your Desktop.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) dimanche 17 mars 2019 12:19:33

If you need only to iterate the store numbers a very efficient way is to extract the values with Foundation’s key-value coding.

  • NSArray’s arrayWithArray creates a Foundation NSArray from the AppleScript list
  • valueForKey returns all values for the given key as array
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set Aarons_Stores to {¬
	{store_number:"F0346", city:"Grand Junction", region:"R0002"}, ¬
	{store_number:"F0394", city:"Cortez", region:"R0002"}, ¬
	{store_number:"F0657", city:"Vernal", region:"R0001"}, ¬
	{store_number:"F0739", city:"Rock Springs", region:"R0001"}, ¬
	{store_number:"F0858", city:"Riverton", region:"R0001"}, ¬
	{store_number:"F0910", city:"Rifle", region:"R0002"}, ¬
	{store_number:"F0935", city:"Ontario", region:"R0003"}, ¬
	{store_number:"F1132", city:"Evanston", region:"R0001"}, ¬
	{store_number:"TM001", city:"Cortez", region:"R0002"} ¬
		}


set storeNumbers to ((current application's NSArray's arrayWithArray:Aarons_Stores)'s valueForKey:"store_number") as list
repeat with aStore in storeNumbers
	display dialog aStore
end repeat

I didn’t say it was, you misread what I wrote. I was querying the relevance of calling the handler using my, which I was speculating would not improve speed the way it does when used to reference variables or properties, thus surmising that it had no need to be there when calling the handler.

In other words, it’s perfectly sensible to just do:

Germaine()

instead of

my Germaine()

in this instance.

I always use My when I call an handler of mine.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mardi 19 mars 2019 09:49:45

Why?

Because some handlers require it and behaving this way I never forget one.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) jeudi 21 mars 2019 10:53:35