Well, perhaps worth trying, though Applescript memory allocations are quite constrained. AppleScript makes a serviceable penknife for a picnic or lunch-break, but after a few hundred records I would personally be reaching for a different instrument.
A modified function below with slightly different arguments, ( and one additional generic helper function –unzip )
sortOn(f, xs) has two arguments – reading from right to left:
- xs a list of items to be sorted. (The items can be records, lists, or simple values).
- f a single function – an Applescript handler of type (Item -> simple orderable value),
or a list of such functions.- If the f argument is a list, any function in it can optionally be followed by a Bool.
(false means descending sort). - Any subgrouping {sub-bracketing} in the list is optional and ignored.
- The sequence of key functions and optional direction bools defines primary to N-ary sort keys.
- If the f argument is a list, any function in it can optionally be followed by a Bool.
The examples below are the same as before with the exception of the last.
The last example sorts by primary, secondary and tertiary keys:
sortOn({country, {capital, false}, {population, false}}, cityData())
- The primary key (ascending String) is the name of the country, so
Bangladesh
comes first, followed by three records in which the country isChina
- the secondary key is a boolean value for capital (descending Bool, true before false) so Beijing, which is the capital of China, comes before Shanghai and Guangzhou
- the tertiary key is population (descending Real), so Shanghai, with a larger population, comes before Guangzhou.
use framework "Foundation"
use scripting additions
-- Ver 0.2 Rob Trew 2017
-- SORT ON ANY PROPERTY (VALUES OF RECORD KEYS,
-- STRING LENGTH, DERIVED PROPERTIES)
-- ARGUMENTS:
-- xs: List of items to be sorted.
-- (The items can be records, lists, or simple values).
--
-- f: A single (a -> b) function (Applescript handler),
-- or a list of such functions.
-- if the argument is a list, any function can
-- optionally be followed by a bool.
-- (False -> descending sort)
--
-- (Subgrouping in the list is optional and ignored)
-- Each function (Item -> Value) in the list should
-- take an item (of the type contained by xs)
-- as its input and return a simple orderable value
-- (Number, String, or Date).
--
-- The sequence of key functions and optional
-- direction bools defines primary to N-ary sort keys.
-- sortOn :: Ord b => ((a -> b) | [((a -> b), Bool)]) -> [a] -> [a]
on sortOn(f, xs)
script keyBool
on |λ|(x, a)
if class of x is boolean then
{asc:x, fbs:fbs of a}
else
{asc:true, fbs:({{x, asc of a}} & fbs of a)}
end if
end |λ|
end script
set {fs, bs} to unzip(fbs of foldr(keyBool, ¬
{asc:true, fbs:{}}, flatten({f})))
set intKeys to length of fs
set ca to current application
script dec
property gs : map(my mReturn, fs)
on |λ|(x)
set nsDct to (ca's NSMutableDictionary's ¬
dictionaryWithDictionary:{val:x})
repeat with i from 1 to intKeys
(nsDct's setValue:((item i of gs)'s |λ|(x)) ¬
forKey:(character id (96 + i)))
end repeat
nsDct as record
end |λ|
end script
script descrip
on |λ|(bool, i)
ca's NSSortDescriptor's ¬
sortDescriptorWithKey:(character id (96 + i)) ¬
ascending:bool
end |λ|
end script
script undec
on |λ|(x)
val of x
end |λ|
end script
map(undec, ((ca's NSArray's arrayWithArray:map(dec, xs))'s ¬
sortedArrayUsingDescriptors:map(descrip, bs)) 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
-- capital :: Record -> Bool
on capital(x)
capital of x
end capital
-- 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, 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, 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, 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, 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}}
sortOn({country, {capital, false}, {population, false}}, cityData())
--> {{capital:true, country:"Bangladesh", city:"Dhaka", pop:14.5},
-- {capital:true, country:"China", city:"Beijing", pop:21.5},
-- {capital:false, country:"China", city:"Shanghai", pop:24.3},
-- {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 ---------------------------------------------------------
-- concatMap :: (a -> [b]) -> [a] -> [b]
on concatMap(f, xs)
set acc to {}
tell mReturn(f)
repeat with x in xs
set acc to acc & |λ|(contents of x)
end repeat
end tell
return acc
end concatMap
-- flatten :: Tree a -> [a]
on flatten(t)
if class of t is list then
my concatMap(my flatten, t)
else
t
end if
end flatten
-- foldr :: (a -> b -> b) -> b -> [a] -> b
on foldr(f, startValue, xs)
tell mReturn(f)
set v to startValue
set lng to length of xs
repeat with i from lng to 1 by -1
set v to |λ|(item i of xs, v, i, xs)
end repeat
return v
end tell
end foldr
-- 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
-- unzip :: [(a,b)] -> ([a],[b])
on unzip(xys)
set xs to {}
set ys to {}
repeat with xy in xys
set {x, y} to xy
set end of xs to x
set end of ys to y
end repeat
return {xs, ys}
end unzip