# Sorting List of List

I need to sort list of list.

Example {{“New York”, “USA”}, {“Paris”, “France”}} specifyng the “column” to sort.

Actually I use QuickSort but the routine works only for single list.

Anyone has the solution? The routine must be the fastest as possible (like QSort…).

Steve H.

Model: MacBook Pro
Browser: Safari 531.9
Operating System: Mac OS X (10.5)

The trick is to split the list of lists into two separate lists in the same order, then keep them registered as you sort on one of them. Unless you’re prepared to do some heavy lifting with QSort, this might suffice. It simple sorts the first argument while keeping the second argument in register (making the same moves). Easy to expand to more lists. Original script by Kai Edwards. If your lists are very long, this is speeded substantially by making the lists part of a script object.

``````to sort2Lists(|sortlist|, SecondList)
tell (count |sortlist|) to repeat with i from (it - 1) to 1 by -1
set s to |sortlist|'s item i
set r to SecondList's item i
repeat with i from (i + 1) to it
tell |sortlist|'s item i to if s > it then
set |sortlist|'s item (i - 1) to it
set SecondList's item (i - 1) to SecondList's item i
else
set |sortlist|'s item (i - 1) to s
set SecondList's item (i - 1) to r
exit repeat
end if
end repeat
if it is i and s > |sortlist|'s end then
set |sortlist|'s item it to s
set SecondList's item it to r
end if
end repeat
return {|sortlist|, SecondList}
end sort2Lists

``````

Regulus6633 has also put together a neat list of lists sorter that you might modify for your use that uses QSort:

``````set initialListOfLists to {{1, 2, 3, 7, 10}, {1, 3, 4, 10, 11}, {9, 3, 5, 11}}

-- first make the list of lists into one list so we can sort it
set combinedList to {}
repeat with aList in initialListOfLists
set combinedList to combinedList & aList
end repeat

-- sort the combined list
Qsort(combinedList, 1, -1)

-- iterate the sorted list and compile the number of times each value occurs
set countOfList to count of combinedList
set i to 1
set finalStats to {}
repeat until i is greater than countOfList
set thisValue to item i of combinedList
if (i + 1) is greater than countOfList then -- this happens if the last value is unique
set end of finalStats to {thisValue, 1}
exit repeat
else
repeat with j from (i + 1) to countOfList
if item j of combinedList is not equal to thisValue then exit repeat
if j is countOfList then -- this happens if the last value is not unique
set j to j + 1
exit repeat
end if
end repeat
end if
set end of finalStats to {thisValue, (j - i)}
set i to j
end repeat

-- sort the final stats and reverse it so the highest number of occurrences is shown first in the finalStats
set sortedFinalStats to sortListofLists(finalStats, 2)
set theReverse to reverse of sortedFinalStats

(*========== SUBROUTINES ============*)
on sortListofLists(array, sortItemNum) --> I modified a bublesort routine to make it work with a list of lists
repeat with i from length of array to 2 by -1 -- go backwards
repeat with j from 1 to i - 1 -- go forwards
if (item sortItemNum of (item j of array)) > (item sortItemNum of (item (j + 1) of array)) then
tell array to set {item j, item (j + 1)} to {item (j + 1), item j}
end if
end repeat
end repeat
return array
end sortListofLists

on Qsort(theList, l, r)
-- Qsort sorts a list
-- the authors of Qsort are Arthur Knapp and Nigel Garvey
-- the script can be found in item 2 of http://bbs.applescript.net/viewtopic.php?id=17340
script o
property cutoff : 10
property p : theList

on qsrt(l, r)
set i to l
set j to r
set v to my p's item ((l + r) div 2)

repeat while (j > i)
set u to my p's item i
repeat while (u < v)
set i to i + 1
set u to my p's item i
end repeat

set w to my p's item j
repeat while (w > v)
set j to j - 1
set w to my p's item j
end repeat

if (i > j) then
else
set my p's item i to w
set my p's item j to u
set i to i + 1
set j to j - 1
end if
end repeat

if (j - l < cutoff) then
else
qsrt(l, j)
end if

if (r - i < cutoff) then
else
qsrt(i, r)
end if
end qsrt

on isrt(l, r)
set x to l
set z to l + cutoff - 1
if (z > r) then set z to r

set v to my p's item x
repeat with y from (x + 1) to z
if (my p's item y < v) then
set x to y
set v to my p's item y
end if
end repeat

tell my p's item l
set my p's item l to v
set my p's item x to it
end tell

set u to my p's item (l + 1)
repeat with i from (l + 2) to r
set v to my p's item i
if (v < u) then
set my p's item i to u
repeat with j from (i - 2) to l by -1
if (v < my p's item j) then
set my p's item (j + 1) to my p's item j
else
set my p's item (j + 1) to v
exit repeat
end if
end repeat
else
set u to v
end if
end repeat
end isrt
end script

set listLen to (count theList)
if (listLen > 1) then -- otherwise the handler will error
-- Translate negative indices
if (l < 0) then set l to listLen + l + 1
if (r < 0) then set r to listLen + r + 1

if (r = l) then
-- No point in sorting just one item
else
-- Transpose transposed indices
if (l > r) then
set temp to l
set l to r
set r to temp
end if

if (r - l < o's cutoff) then
-- Skip the Quicksort if cutoff or less items
else
o's qsrt(l, r)
end if
o's isrt(l, r)
end if
end if

return -- the original name of your list is the returned value
end Qsort

``````

Another approach is to make a list of row numbers of the original list {1, 2, 3, …}
Then sort that list of row numbers using a custom LT (less than) function that determines if one row is “less than” another.
e.g. {“New York”, “USA”} is “less than” {“Paris”, “France”} if we are using column 1, but {“Paris”, “France”} is “less than” {“New York”, “USA”} if we are using column 2

Then use the sorted list of row numbers to build a sortedList from the original.

This example uses a bubble sort (to avoid obscuring the “sort by reference list” concept), but a quick sort could be used to sort orderOfList

``````global unSortedList, orderOfList

set unSortedList to {{"New York", "USA"}, {"Paris", "France"}, {"Minas Tirith", "Gondor"}, {"Black Rock", "Nevada"}}
set comparisonColumn to 2

-- make list of indexes
set orderOfList to {}
repeat with i from 1 to (count of unSortedList)
copy i to end of orderOfList
end repeat

--Bubble Sort the list of indexes
repeat with i from 1 to ((count of orderOfList) - 1)
repeat with j from i + 1 to count of orderOfList
if LT(j, i, comparisonColumn) then
set {item i of orderOfList, item j of orderOfList} to {item j of orderOfList, item i of orderOfList}
end if
end repeat
end repeat

--make sortedlist from original according to custom sorted orderOfList
set sortedList to {}
repeat with i from 1 to count of orderOfList
copy item (item i of orderOfList) of unSortedList to end of sortedList
end repeat
return sortedList

on LT(a, b, columnToCompare)
set aItem to item columnToCompare of (item (item a of orderOfList) of unSortedList)
set bItem to item columnToCompare of (item (item b of orderOfList) of unSortedList)
return aItem < bItem

end LT
``````

My inexerience with AppleScript tripped me up. The indexing list is not needed. (Its a good technique for 2 dimesional arrays, but Applescript’s List class doesn’t support two index arrays.)

A custom LT function will suffice.

The idea of a custom LT function is that at the heart of many sort routines (including Bubble and Quick) is the statment “if A < B then swap A and B”
Instead of using the comparison < , one can write a boolean function LT and replace that line with “if LT(A,B) then swap A and B”

This example uses a bubble sort, but you can substitute LT(A,B) for A < B in your existing QuickSort.

``````set myList to {{"New York", "USA"}, {"Paris", "France"}, {"Minas Tirith", "Gondor"}, {"Black Rock", "Nevada"}}
set comparisonColumn to 1

-- Bubble Sort
repeat with i from 1 to ((count of myList) - 1)
repeat with j from i + 1 to count of myList
if LT(item j of myList, item i of myList, comparisonColumn) then
set {item i of myList, item j of myList} to {item j of myList, item i of myList}
end if
end repeat
end repeat

return myList

-- custom less than function
on LT(aRay, bRay, columnToCompare)
set aTerm to item columnToCompare of aRay
set bterm to item columnToCompare of bRay
return aTerm < bterm
end LT
``````

Presumably, then, your list of lists is quite long. The version of Qsort in Adam’s quote of regulus6633’s script has a sibling called CustomQsort, whose value comparisons are done for it by handlers in a passed script object. For your purpose, where the values are lists, it could be passed handlers that compare particular items of those lists. The sortListsByNthItem handler in the script below sets up the script object and calls CustomQsort. You call it with the list of lists and the relevant index number.

``````-- CustomQsort by Arthur Knapp and Nigel Garvey.
-- Sort items l thru r of theList using the customising handlers in the script object compObj.
on CustomQsort(theList, l, r, compObj)
script o
property cutoff : 10
property p : theList

on qsrt(l, r)
set i to l
set j to r
set v to my p's item ((l + r) div 2)

repeat while (j > i)

set u to my p's item i
repeat while (compObj's isLess(u, v))
set i to i + 1
set u to my p's item i
end repeat

set w to my p's item j
repeat while (compObj's isGreater(w, v))
set j to j - 1
set w to my p's item j
end repeat

if (i > j) then
else
set my p's item i to w
set my p's item j to u

-- Perform any additional actions that may be required after this swap.
compObj's swap(i, j)

set i to i + 1
set j to j - 1
end if
end repeat

if (j - l < cutoff) then
else
qsrt(l, j)
end if
if (r - i < cutoff) then
else
qsrt(i, r)
end if
end qsrt

on isrt(l, r)
set x to l
set z to l + cutoff - 1
if (z > r) then set z to r

set v to my p's item x
set u to v
repeat with y from (x + 1) to z
tell my p's item y
if (compObj's isLess(it, v)) then
set x to y
set v to it
end if
end tell
end repeat

set my p's item x to u
set my p's item l to v

-- Perform any additional actions that may be required after this swap.
compObj's swap(l, x)

set u to my p's item (l + 1)
repeat with i from (l + 2) to r
set v to my p's item i
if (compObj's isLess(v, u)) then
set my p's item i to u
repeat with j from (i - 2) to l by -1
tell my p's item j
if (compObj's isLess(v, it)) then
set my p's item (j + 1) to it
else
set my p's item (j + 1) to v

-- Perform any additional actions that may be required after this insertion.
compObj's shift(j + 1, i)

exit repeat
end if
end tell
end repeat
else
set u to v
end if
end repeat
end isrt
end script

set listLen to (count theList)
if (listLen > 1) then -- otherwise the handler will error
-- Translate negative indices
if (l < 0) then set l to listLen + l + 1
if (r < 0) then set r to listLen + r + 1

if (r = l) then
-- No point in sorting just one item
else
-- Transpose transposed indices
if (l > r) then
set temp to l
set l to r
set r to temp
end if

if (r - l < o's cutoff) then
-- Skip the Quicksort if cutoff or less items
else
o's qsrt(l, r)
end if
o's isrt(l, r)
end if
end if

return -- nothing
end CustomQsort

on sortListsByNthItem(listOfLists, n)
script o
on isGreater(a, b)
(item n of a > item n of b)
end isGreater

on isLess(a, b)
(item n of a < item n of b)
end isLess

-- These two handlers aren't used in the current context,
-- but have to exist to match the calls in CustomQsort.
on swap(i, j)
end swap

on shift(i, j)
end shift
end script

CustomQsort(listOfLists, 1, -1, o)
end sortListsByNthItem

set listOfLists to {{"New York", "USA"}, {"Paris", "France"}}
sortListsByNthItem(listOfLists, 2) -- Sort by second item in each list.
listOfLists --> {{"Paris", "France"}, {"New York", "USA"}}
``````

Hi Nigel,

Again, thanks for the solution. Previous examples posted didn’t solved “exactly” the problem.
You are “Sorting Guru”.

I will study the routine to implement last feature: sort ascending or descending (for numbers and dates) with additional parameter

For now many thanks again.

Steve H.

One of the nice things about CustomQsort is that it’ll do a reverse sort if the contents of the isGreater and isLess handlers are swapped. I don’t know what kind of parameter you want to use to signify ascending or descending sorts. If it was, say, 1 for ascending sorts and -1 for descending sorts, you could modify my sortListsByNthItem handler and the top-level code something like this:

``````-- CustomQsort handler needed too.

on sortListsByNthItem(listOfLists, n, direction)
script o -- Script object for forward (ascending) sort.
on isGreater(a, b)
(item n of a > item n of b)
end isGreater

on isLess(a, b)
(item n of a < item n of b)
end isLess

-- These two handlers aren't used in the current context,
-- but have to exist to match the calls in CustomQsort.
on swap(i, j)
end swap

on shift(i, j)
end shift
end script

script r -- Script object for reverse (descending) sort.
property parent : o -- Inherit all the properties (inc. handlers) of o.
-- Swap the isGreater and isLess handlers.
property isGreater : o's isLess
property isLess : o's isGreater
end script

CustomQsort(listOfLists, 1, -1, item direction of {o, r})
end sortListsByNthItem

set listOfLists to {{"New York", "USA"}, {"Tallinn", "Estonia"}, {"Riga", "Latvia"}, {"Paris", "France"}}
sortListsByNthItem(listOfLists, 2, -1) -- Sort by second item in each list, reverse sort.
listOfLists --> {{"New York", "USA"}, {"Riga", "Latvia"}, {"Paris", "France"}, {"Tallinn", "Estonia"}}
``````

Hi Nigel,

I executed some test and the “magic” routine is very fast. To sort a list of list of 10,000 item (where each item is composed by string, number and date items) take about 2.5 sec (!!!)
Somebody say: “Nigel is excellent for AppleScript speed. Maybe the best in the world.” I confirm.

Below your complete script with a routine to generate a Tab-Text file useful for test speed.

Thanks again.

property listOfLists : missing value – to speedup the access to list generation using “my” term

– Generate the Tab-Textfile to test
set filePathTest to “Leopard:Users:steve:Desktop:tableTest.txt”
set fileRef to open for access file filePathTest with write permission
set eof of fileRef to 0
repeat with j from 1 to 10000
set filePath to “Macintosh HD:Users:steve:Test:image_” & (random number from 10000 to 90000)
set fileSize to random number from 100000 to 500000
set creationDate to (current date) + (random number from 5000 to 500000)
write (filePath & tab & fileSize & tab & creationDate & return) as «class utf8» to fileRef
end repeat
close access fileRef

– Read the Tab-Text file and convert it into List of List
set fileRef to open for access file filePathTest
set listOfLists to read fileRef using delimiter return as «class utf8»
close access fileRef

repeat with j from 1 to count my listOfLists
set AppleScript’s text item delimiters to tab
set textItemsTmp to text items of item j of my listOfLists
set AppleScript’s text item delimiters to “”
set item 2 of textItemsTmp to (item 2 of textItemsTmp) as integer
set item 3 of textItemsTmp to date (item 3 of textItemsTmp)
set item j of my listOfLists to textItemsTmp
end repeat

set t1 to current date
sortListsByNthItem(listOfLists, 3, 1) – {List, column to sort, flag 1 for ascending, -1 for descending}
set t2 to current date
set t3 to t2 - t1

on sortListsByNthItem(listOfLists, n, direction)
script o – Script object for forward (ascending) sort.
on isGreater(a, b)
(item n of a > item n of b)
end isGreater

``````	on isLess(a, b)
(item n of a < item n of b)
end isLess

-- These two handlers aren't used in the current context,
-- but have to exist to match the calls in CustomQsort.
on swap(i, j)
end swap

on shift(i, j)
end shift
end script

script r -- Script object for reverse (descending) sort.
property parent : o -- Inherit all the properties (inc. handlers) of o.
-- Swap the isGreater and isLess handlers.
property isGreater : o's isLess
property isLess : o's isGreater
end script

CustomQsort(listOfLists, 1, -1, item direction of {o, r})
``````

end sortListsByNthItem

on CustomQsort(theList, l, r, compObj)
script o
property cutoff : 10
property p : theList

``````	on qsrt(l, r)
set i to l
set j to r
set v to my p's item ((l + r) div 2)

repeat while (j > i)

set u to my p's item i
repeat while (compObj's isLess(u, v))
set i to i + 1
set u to my p's item i
end repeat

set w to my p's item j
repeat while (compObj's isGreater(w, v))
set j to j - 1
set w to my p's item j
end repeat

if (i > j) then
else
set my p's item i to w
set my p's item j to u

-- Perform any additional actions that may be required after this swap.
compObj's swap(i, j)

set i to i + 1
set j to j - 1
end if
end repeat

if (j - l < cutoff) then
else
qsrt(l, j)
end if
if (r - i < cutoff) then
else
qsrt(i, r)
end if
end qsrt

on isrt(l, r)
set x to l
set z to l + cutoff - 1
if (z > r) then set z to r

set v to my p's item x
set u to v
repeat with y from (x + 1) to z
tell my p's item y
if (compObj's isLess(it, v)) then
set x to y
set v to it
end if
end tell
end repeat

set my p's item x to u
set my p's item l to v

-- Perform any additional actions that may be required after this swap.
compObj's swap(l, x)

set u to my p's item (l + 1)
repeat with i from (l + 2) to r
set v to my p's item i
if (compObj's isLess(v, u)) then
set my p's item i to u
repeat with j from (i - 2) to l by -1
tell my p's item j
if (compObj's isLess(v, it)) then
set my p's item (j + 1) to it
else
set my p's item (j + 1) to v

-- Perform any additional actions that may be required after this insertion.
compObj's shift(j + 1, i)

exit repeat
end if
end tell
end repeat
else
set u to v
end if
end repeat
end isrt
end script

set listLen to (count theList)
if (listLen > 1) then -- otherwise the handler will error
-- Translate negative indices
if (l < 0) then set l to listLen + l + 1
if (r < 0) then set r to listLen + r + 1

if (r = l) then
-- No point in sorting just one item
else
-- Transpose transposed indices
if (l > r) then
set temp to l
set l to r
set r to temp
end if

if (r - l < o's cutoff) then
-- Skip the Quicksort if cutoff or less items
else
o's qsrt(l, r)
end if
o's isrt(l, r)
end if
end if

return -- nothing
``````

end CustomQsort

Using the test data creator above which creates a tab delimited file
with 10,000 records.

The above code sorts on the b or file size column. If you change
the letter to ‘a’ then it will sort on the file path column and
to ‘c’ will sort on the date column.

To reverse the results you add ‘.reverse’ to the end like so.

You can sort on multiple columns. This will sort on the ‘b’
column first and then by the ‘c’ column.

Wow!!!

Even if you “changed” language (at the moment Nigel is the winner using AS) the performances are impressive!!!

But you need to explain me how to call the ruby script from AS. I presume with do shell script… and if the data are returned
And the returned data are string or list of list like AS?

At this point I need to start from my “list of list” that I manage inside ASS and pass the “list of list” to Ruby Script that need to sort and return me the same list of list sorted

I ask you last effort. It’s impressive how few line of code Ruby require!

Steve H.

If you are using this inside ASS then I would stick with Nigel’s version.

BTW, if you are using this list in a table you get sorting for free in ASS.

Hi Craig,

Yes, I know about sorting table using Table View in ASS. But sometimes is useful to have an independent routine to sort list or list of list for others use.
Thanks anyway for the faster Ruby script.

Steve