Applescript seems, at first sight, to be rather poorly equipped for sorting:
- It lacks the fast and flexible built-in array sorts of JavaScript (see Sorting anything with Execute JavaScript actions )
- Home-made sort handlers are certainly possible, but take some work, and may not scale or perform very well.
- Even if we install the Satimage 3rd party library, its flexibility turns out to be limited - we can’t for example, sort a list of records by particular keys, or by functions of more than one key, or sort simple values by derived properties like the length of strings, or their final rather than initial letters.
We do, however have access to some useful speed and flexibility through the interface to ObjC NSArray sorting methods.
Here is a sortOn(f, boolUp, xs) function which takes, (reading the arguments right to left):
- xs a list of records, lists or simple atomic AppleScript values
- boolUp (true for ascending sorts, false for descending)
- f a function which takes as input an item of whatever type of data the list contains, and returns some orderable value (number, string, date) derived from such an item.
A simple example of f if we wanted to sort a list of words by their length rather than their lexical order would be:
-- stringLength :: String -> Int
on stringLength(item)
length of item
end
Draft AppleScript source for sortOn, with some examples of sorting strings and records by derived or default properties
(Note that it requires the incantation
use framework "Foundation"
use scripting additions
and a couple of generic helper functions also listed below – map and mReturn).
NB (Version 0.2 in a later post in this thread has a different argument pattern, and is more flexible)
use framework "Foundation"
use scripting additions
-- SORT ON ANY PROPERTY (VALUES OF RECORD KEYS,
-- STRING LENGTH, DERIVED PROPERTIES)
-- sortOn :: Ord b => (a -> b) -> Bool -> [a] -> [a]
on sortOn(f, boolUp, xs)
set ca to current application
script dec
property g : mReturn(f)
on |λ|(x)
set nsDct to (ca's NSMutableDictionary's ¬
dictionaryWithDictionary:{a:g's |λ|(x)})
nsDct's setValue:x forKey:("b")
nsDct
end |λ|
end script
script undec
on |λ|(x)
b of x
end |λ|
end script
map(undec, ((ca's NSArray's arrayWithArray:map(dec, xs))'s ¬
sortedArrayUsingDescriptors:{ca's NSSortDescriptor's ¬
sortDescriptorWithKey:"a" ascending:boolUp}) as list)
end sortOn
-- TESTS ------------------------------------------------------------------
-- cityData :: () -> [Record]
on cityData()
{{city:"Shanghai", pop:24.3, country:"China", capital:false}, ¬
{city:"Beijing", pop:21.5, country:"China", capital:true}, ¬
{city:"Delhi", pop:11.0, country:"India", capital:true}, ¬
{city:"Lagos", pop:16.0, country:"Nigeria", capital:true}, ¬
{city:"Karachi", pop:14.9, country:"Pakistan", capital:false}, ¬
{city:"Dhaka", pop:14.5, country:"Bangladesh", capital:true}, ¬
{city:"Guangzhou", pop:14.0, country:"China", capital:false}, ¬
{city:"Istanbul", pop:14.0, country:"Turkey", capital:false}, ¬
{city:"Tokyo", pop:13.5, country:"Japan", capital:true}}
end cityData
-- population :: Record -> Real
on population(x)
pop of x
end population
-- country :: Record -> String
on country(x)
country of x
end country
-- greekAlphabet :: () -> [String]
on greekAlphabet()
["alpha", "beta", "gamma", "delta", "epsilon", "zeta", ¬
"eta", "theta", "iota", "kappa", "lambda", "mu"]
end greekAlphabet
-- stringLength :: String -> Int
on stringLength(s)
length of s
end stringLength
-- romanAlpha :: String -> String
on romanAlpha(x)
x
end romanAlpha
on run
sortOn(stringLength, true, greekAlphabet())
--> {"mu", "eta", "beta", "zeta", "iota", "alpha", "gamma",
-- "delta", "theta", "kappa", "lambda", "epsilon"}
sortOn(stringLength, false, greekAlphabet())
--> {"epsilon", "lambda", "alpha", "gamma", "delta", "theta",
-- "kappa", "beta", "zeta", "iota", "eta", "mu"}
sortOn(romanAlpha, true, greekAlphabet())
--> {"alpha", "beta", "delta", "epsilon", "eta", "gamma",
-- "iota", "kappa", "lambda", "mu", "theta", "zeta"}
sortOn(romanAlpha, false, greekAlphabet())
--> {"zeta", "theta", "mu", "lambda", "kappa", "iota",
-- "gamma", "eta", "epsilon", "delta", "beta", "alpha"}
sortOn(population, true, cityData())
--> {{capital:true, country:"India", city:"Delhi", pop:11.0},
-- {capital:true, country:"Japan", city:"Tokyo", pop:13.5},
-- {capital:false, country:"China", city:"Guangzhou", pop:14.0},
-- {capital:false, country:"Turkey", city:"Istanbul", pop:14.0},
-- {capital:true, country:"Bangladesh", city:"Dhaka", pop:14.5},
-- {capital:false, country:"Pakistan", city:"Karachi", pop:14.9},
-- {capital:true, country:"Nigeria", city:"Lagos", pop:16.0},
-- {capital:true, country:"China", city:"Beijing", pop:21.5},
-- {capital:false, country:"China", city:"Shanghai", pop:24.3}}
sortOn(population, false, cityData())
--> {{capital:false, country:"China", city:"Shanghai", pop:24.3},
-- {capital:true, country:"China", city:"Beijing", pop:21.5},
-- {capital:true, country:"Nigeria", city:"Lagos", pop:16.0},
-- {capital:false, country:"Pakistan", city:"Karachi", pop:14.9},
-- {capital:true, country:"Bangladesh", city:"Dhaka", pop:14.5},
-- {capital:false, country:"China", city:"Guangzhou", pop:14.0},
-- {capital:false, country:"Turkey", city:"Istanbul", pop:14.0},
-- {capital:true, country:"Japan", city:"Tokyo", pop:13.5},
-- {capital:true, country:"India", city:"Delhi", pop:11.0}}
sortOn(country, true, cityData())
--> {{capital:true, country:"Bangladesh", city:"Dhaka", pop:14.5},
-- {capital:false, country:"China", city:"Shanghai", pop:24.3},
-- {capital:true, country:"China", city:"Beijing", pop:21.5},
-- {capital:false, country:"China", city:"Guangzhou", pop:14.0},
-- {capital:true, country:"India", city:"Delhi", pop:11.0},
-- {capital:true, country:"Japan", city:"Tokyo", pop:13.5},
-- {capital:true, country:"Nigeria", city:"Lagos", pop:16.0},
-- {capital:false, country:"Pakistan", city:"Karachi", pop:14.9},
-- {capital:false, country:"Turkey", city:"Istanbul", pop:14.0}}
end run
-- GENERIC FUNCTIONS ---------------------------------------------------------
-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
tell mReturn(f)
set lng to length of xs
set lst to {}
repeat with i from 1 to lng
set end of lst to |λ|(item i of xs, i, xs)
end repeat
return lst
end tell
end map
-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
if class of f is script then
f
else
script
property |λ| : f
end script
end if
end mReturn