Elegant flattening of list-of-lists

This is 100% unimportant, just for the fun of it.

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.

How about:

set lol to characters of ({{1, 2, 3}, {4, 5, 6}} as text)

What matters ultimately is that it works, not how many lines it takes. :slight_smile:

Here’s an effort for general use: http://macscripter.net/viewtopic.php?pid=140475#p140475

Only need one repeat loop

set clist to {{1, 2, 3}, {4, 5, 6}}
set delisted to {}
repeat with i in clist
	set delisted to delisted & i
end repeat

Adam,

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.

CAUTION

The variable named sublist conflicts with Satimage’s function named sublist too.

Yvan KOENIG running Sierra 10.12.3 in French (VALLAURIS, France) vendredi 17 mars 2017 10:05:29

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

We were of course forgetting about ASObjC:

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.

Yes – although this suggested to me it might not be so useful:

But maybe I misunderstood Tom.

Ah. Yes. So we didn’t so much forget about ASObjC as instinctively didn’t consider it. :rolleyes:

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.