I’ve got an application that always returns a 2-layer (never deeper) list of lists to Applescript, where I just want all the items in the second level lists as a single list.
so given:
{{1, 2, 3}, {4, 5, 6}}
I just want:
{1, 2, 3, 4, 5, 6}
Obviously, I can do it with repeat loops.
set delisted to {}
repeat with i in lol
repeat with j in i
copy the contents of j to the end of delisted
end repeat
end repeat
I was just surprised I couldn’t quickly hack together some syntax to get it in one line of Applescript.
I thought something like:
set lol to {{1, 2, 3}, {4, 5, 6}}
set delisted to the contents of every item of lol
or
set lol to {{1, 2, 3}, {4, 5, 6}}
set delisted to the items of the items of lol
would work, but no luck. Anybody want to show off their AS knowledge?
Oh, and yes, I’m sure I could do it in one line with a “do shell script,” that doesn’t count.
I made that simplified list to post, but the program actually returns references, so coercing them to another class destroys their usefulness. Also, the text of the coerced references each contain multiple words and spaces, so {“characters of”, “words of”, “paragraphs of” } are no-gos for separating them.
Nigel,
I know, of course. I was just surprised I couldn’t quickly do it, and was curious if it could be done.
Robert,
Thanks. That approach is faster by… a factor of [divide by 0].
Just for the heck of it, I checked:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
set numberOfSublists to 50
set masterList to {}
repeat numberOfSublists times
set subsetCount to random number from 5 to 400
set seedNumber to random number from 1 to 1000
set stepSize to random number from 1 to 10
set sublist to {}
repeat subsetCount times
set seedNumber to seedNumber + stepSize
copy seedNumber to end of sublist
end repeat
copy sublist to end of masterList
end repeat
set t1 to (time of (current date))
set delistedRobertFern to {}
repeat with i in masterList
set delistedRobertFern to delistedRobertFern & i
end repeat
set t2 to (time of (current date))
set delistedTspoon to {}
repeat with i in masterList
repeat with j in i
copy the contents of j to the end of delistedTspoon
end repeat
end repeat
set t3 to (time of (current date))
set totalItemCount to count of delistedTspoon
if t2 - t1 < t3 - t2 then
set endDialogText to "The single loop approach was faster by" & t3 - t2 / t2 - t1 * 100 & "%"
else
set endDialogText to "The double loop approach was faster by" & t2 - t1 / t3 - t2 * 100 & "%"
end if
display dialog "The Main List contained " & numberOfSublists & " and " & totalItemCount & " total items." & return & "The single loop approach took " & t2 - t1 & " seconds to run." & return & "The double loop approach took " & t3 - t2 & " seconds to run." & return & endDialogText
And it actually turns out that you don’t want to set any value for “number of sublists” that returns a time > 0 seconds for your loop, because if you do, you won’t want to wait around to get a result out of my loop.
You can speed up the double loop considerably by using references to the list variables and, to a lesser extent, by using ‘set’ instead of ‘copy’. The single loop still usually seems to be faster, though. The subtractions in the maths for the dialog text need to be done before the division and the multiplication, ie. (t3 - t2) / (t2 - t1) * 100, not t3 - t2 / t2 - t1 * 100. But I’ve changed the calculation and the wording below because in my understanding of English, the calculation for a “faster by” percentage would be (t3 - t2) / (t2 - t1) * 100 - 100. I’ve also allowed for the possibility of the two results being the same or the lower one 0.
--use AppleScript version "2.4" -- The code actually works with any version of AppleScript likely to be still in use.
--use scripting additions
set numberOfSublists to 200
local o, delistedRobertFern
script
property |sublist| : missing value
property masterList : missing value
property delistedTspoon : missing value
end script
set o to result
set o's masterList to {}
repeat numberOfSublists times
set subsetCount to random number from 5 to 400
set seedNumber to random number from 1 to 1000
set stepSize to random number from 1 to 10
set o's |sublist| to {}
repeat subsetCount times
set seedNumber to seedNumber + stepSize
set end of o's |sublist| to seedNumber
end repeat
set end of o's masterList to o's |sublist|
end repeat
set t1 to (time of (current date))
set delistedRobertFern to {}
repeat with i from 1 to (count o's masterList)
set delistedRobertFern to delistedRobertFern & (item i of o's masterList)
end repeat
set t2 to (time of (current date))
set o's delistedTspoon to {}
repeat with i from 1 to (count o's masterList)
set o's |sublist| to item i of o's masterList
repeat with j from 1 to (count o's |sublist|)
set end of o's delistedTspoon to item j of o's |sublist|
end repeat
end repeat
set t3 to (time of (current date))
set totalItemCount to count o's delistedTspoon
set singleLoopTime to t2 - t1
set doubleLoopTime to t3 - t2
if (singleLoopTime < doubleLoopTime) then
if (singleLoopTime > 0) then
set endDialogText to "The single loop approach was " & doubleLoopTime / singleLoopTime & " times as fast."
else
set endDialogText to "The single loop approach was faster!"
end if
else if (doubleLoopTime < singleLoopTime) then
if (doubleLoopTime > 0) then
set endDialogText to "The double loop approach was " & singleLoopTime / doubleLoopTime & " times as fast."
else
set endDialogText to "The single loop approach was faster!"
end if
else
set endDialogText to "The time difference between to the two approaches was too close to call."
end if
display dialog "The Main List contained " & numberOfSublists & " and " & totalItemCount & " total items." & return & "The single loop approach took " & t2 - t1 & " seconds to run." & return & "The double loop approach took " & t3 - t2 & " seconds to run." & return & endDialogText
use AppleScript version "2.4"
use framework "Foundation"
set lol to {{1, 2, 3}, {4, 5, 6}}
set delisted to ((current application's class "NSArray"'s arrayWithArray:(lol))'s valueForKeyPath:("@unionOfArrays.self")) as list
--> {1, 2, 3, 4, 5, 6}
I wasn’t forgetting about ASObjC, I just don’t know it… I was sort of expecting someone to pop in with an ASObjC solution to doing it in 1-line. I was interested to see it, so I didn’t include ASObjC with my line about not using “do shell script.”
Interested though I am to see it, like using terminal, it’s doesn’t address my surprise that I couldn’t find a short, elegant Applescript way to do that.
Thanks for the corrections on the speed test.
I was also surprised that the Applescript divide by 0 didn’t get caught by the compiler or cause a crash, that it instead returned a very high value.
After than I messed around a bit with divide by zero, and everything I tried did return a “Can’t divide by 0” Applescript execution error. So I’m not sure exactly what it takes to trick it into trying it.
In Script Debugger, it does appear that the time calculations that return results under 1 second really do have the variable assigned to exactly the number “0,” that there is no sub-second resolution going on that gets rounded for the dialog, or anything like that. That Applescript really is trying to do a divide by 0 there, and returns a numeric value.
Incidentally, I just thought I’d mention that I’ve used plenty of other forums, and MacScripter has the greatest collection of knowledgable, helpful, friendly people I’ve ever come across online.
I didn’t know enough about ASObjC to know if it could maintain references when passing them back and forth between AS & ObjC, so I didn’t know if an ASObjC solution was possible or not.
And since the entire exercise is just for the fun of it (the actual run time on my original horrendously inefficient double repeat loop was still a hundredth of a second when processing the actual data in question), I was still interested to see it.
You’re fine with text, integers and booleans. Reals are fine in 10.11 and later; before that, they can lose precision. Dates are also fine in 10.11 and later.