That’s fairly difficult — or at least fussy — to do in ASObjC, but fairly simple with any of my customisable vanilla sorts. The Shell sort handler below is already written and can be hidden away in a library. A scripter using it only needs to provide the list and the customising script object to pass to it. The script object I’ve provided assumes that your idea of a “word” is the same as AppleScript’s:
(* Shell sort — customisable version
Algorithm: Donald Shell, 1959.
AppleScript implementation: Nigel Garvey, 2010. Customisation rethought 2016 and 2018
Parameters: (the list, range index 1, range index 2, customisation object)
The range index parameters are integers specifying the range to be sorted in the list. Like the indices in AppleScript range references, they can be positive or negative and don't need to be in a particular order. The values 1 and -1 can be used to indicate the entire list.
The customisation object is a record with optional 'comparer' and/or 'slave' properties.
If given, the 'comparer' value must be a script object containing an isGreater(item1, item2) handler which compares two items passed to it from the list and returns a boolean indicating whether or not the first is "greater than" the second according to its criteria.
If given, the 'slave' value can be a list of lists in which the moves of the main sort are to be duplicated. These lists must of course be long enough to allow the absolute range indices used for the main list. Alternatively, the value can be a script object containing its own rotate(index1, index2) and setStep(stepSize) handlers and any necessary properties. This is for compatibility with scripts written to use an earlier version of this sort and might conceivably be used for other purposes, such as transposing the sort range for the slave list(s) or counting the swaps. The same script object can be used as both the 'comparer' and 'slave' values if it contains all the necessary handlers.
Where the 'comparer' and/or 'slave' properties are omitted, or the customisation parameter isn't a record or a script object, the defaults are direct comparisons and no 'slave' action.
Result returned: None. The passed lists are sorted in place.
*)
on CustomShellSort(theList, rangeIndex1, rangeIndex2, customiser)
script o
property comparer : me
property slave : me
property lst : theList
property slaveLists : {}
property slaveListCount : missing value
property singleSlaveList : missing value
property stepSize : missing value
on shsrt(rangeLeft, rangeRight)
set stepSize to (rangeRight - rangeLeft + 1) div 2
repeat while (stepSize > 0)
-- Tell the slave's setStep() handler the current step size.
tell slave to setStep(stepSize)
-- Compare the current value with the preceding one(s) in its step chain, moving those greater than it up a step and inserting it a step above the first found to be less than or equal to it or, if none, at the first slot in the chain.
repeat with traversalIndex from (rangeLeft + stepSize) to rangeRight
set currentValue to my lst's item traversalIndex
repeat with insertionIndex from (traversalIndex - stepSize) to rangeLeft by -stepSize
tell my lst's item insertionIndex
if (comparer's isGreater(it, currentValue)) then
-- Greater value. Move it up a step.
set my lst's item (insertionIndex + stepSize) to it
else
-- Lesser or equal value. Set the just vacated slot above it as the insertion location.
set insertionIndex to insertionIndex + stepSize
exit repeat
end if
end tell
end repeat
-- Insert the current value at the location determined if different from where it is now.
if (insertionIndex < traversalIndex) then
set my lst's item insertionIndex to currentValue
tell slave to rotate(insertionIndex, traversalIndex)
end if
end repeat
-- Reduce the step size by a half or just over.
set stepSize to (stepSize / 2.2) as integer
end repeat
end shsrt
-- Default comparison and slave handlers.
on isGreater(a, b)
(a > b)
end isGreater
on rotate(a, b)
tell my singleSlaveList's item b
repeat with rotationIndex from (b - stepSize) to a by -stepSize
set my singleSlaveList's item (rotationIndex + stepSize) to my singleSlaveList's item rotationIndex
end repeat
set my singleSlaveList's item a to it
end tell
end rotate
on rotateMultiple(a, b)
repeat with i from 1 to slaveListCount
set my singleSlaveList to item i of my slaveLists
tell my singleSlaveList's item b
repeat with rotationIndex from (b - stepSize) to a by -stepSize
set my singleSlaveList's item (rotationIndex + stepSize) to my singleSlaveList's item rotationIndex
end repeat
set my singleSlaveList's item a to it
end tell
end repeat
end rotateMultiple
on dummy(a, b) -- Do ABSOLUTELY nothing. (Substituted for rotate() when slaveLists is empty to save checking it each time.)
end dummy
on setStep(a) -- No action needed when the default rotate()'s used as it shares stepSize with the main handler.
end setStep
end script
-- Process the input parameters.
set listLen to (count theList)
if (listLen > 1) then
-- Negative and/or transposed range indices.
if (rangeIndex1 < 0) then set rangeIndex1 to listLen + rangeIndex1 + 1
if (rangeIndex2 < 0) then set rangeIndex2 to listLen + rangeIndex2 + 1
if (rangeIndex1 > rangeIndex2) then set {rangeIndex1, rangeIndex2} to {rangeIndex1, rangeIndex2}
-- Supplied or default customisation scripts.
if ((customiser is {}) or (customiser's class is record)) then
-- Use the passed or default comparer script. Get the passed or default slave parameter.
set {comparer:o's comparer, slave:slaveParam} to customiser & {comparer:o, slave:o}
if (slaveParam's class is script) then
-- Passed or default slave script. Use it.
set o's slave to slaveParam
else if (slaveParam's class is list) then
-- Passed list of slave lists. Set the default 'slave' script object's slaveLists property to it.
set o's slaveLists to slaveParam
end if
-- Configure the default 'slave' script object to use the best handlers for the number of slave lists. This makes no difference with a non-default slave.
set o's slaveListCount to (count o's slaveLists)
if (o's slaveListCount is 0) then
set o's rotate to o's dummy
else if (o's slaveListCount is 1) then
set o's singleSlaveList to beginning of o's slaveLists
else
set o's rotate to o's rotateMultiple
end if
end if
-- Do the sort.
tell o to shsrt(rangeIndex1, rangeIndex2)
end if
return -- nothing.
end CustomShellSort
-- User script code:
-- Custom comparer script object. Its isGreater() handler compares strings by their last "words".
-- Any with no words are deemed "greater" than those with them so that the sort will shunt them to the end of the list.
script onLastWord
-- a is "greater" than b if it has no words, or if both strings have words and a's last one's greater than b's.
-- a is not greater than b if it has words and b doesn't, or if both strings have words and a's last one's less than or equal to b's.
on isGreater(a, b)
return (((count a's words) is 0) or (((count b's words) > 0) and (word -1 of a > word -1 of b)))
end isGreater
end script
set ChoiceList to {¬
"Write Address", ¬
"Phone Home", ¬
"Drive Car", ¬
"Read Book"}
-- Sort items 1 thru -1 of ChoiceList in place, using onLastWord's isGreater() handler to do the comparing.
CustomShellSort(ChoiceList, 1, -1, {comparer:onLastWord})
return ChoiceList --> {"Write Address", "Read Book", "Drive Car", "Phone Home"}