Alphabetically ASOC sort array by last word

I would like to sort an array by the last word of each item, employing Foundation’s method

I have a list of two words separated by two tabs. Although I have been able to sort the array by the first character of each item, I cannot find a method to sort by the first character of each item’s last word. My end goal is to use the list in Choos

use framework "Foundation"
use scripting additions

set ChoiceList to {¬
	"Write		Address", ¬
	"Phone		Home", ¬
	"Drive		Car", ¬
	"Read		Book"}

my AlphaSort(ChoiceList)

on AlphaSort(ChoiceList)
	set anNSArray to current application's NSArray's arrayWithArray:ChoiceList
	set anNSListSorted to anNSArray's sortedArrayUsingSelector:"localizedStandardCompare:"
	set SortedListAlphabetically to anNSListSorted as list
end AlphaSort

What method can I call to sort the list by the last word’s first character?

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"}

It’s awful but it does the job.

use framework "Foundation"
use scripting additions

set ChoiceList to {¬
	"Write		Address", ¬
	"Phone		Home", ¬
	"Drive		Car", ¬
	"Read		Book"}

my AlphaSort(ChoiceList)

on AlphaSort(ChoiceList)
	set ChoiceList to my Swap(ChoiceList)
	set anNSArray to current application's NSArray's arrayWithArray:ChoiceList
	set anNSListSorted to anNSArray's sortedArrayUsingSelector:"localizedStandardCompare:"
	set SortedListAlphabetically to anNSListSorted as list
	set SortedListAlphabetically to my Swap(SortedListAlphabetically)
end AlphaSort

on Swap(aList)
	set delim to tab & tab
	repeat with i from 1 to count aList
		set sourceString to (current application's NSString's stringWithString:(aList's item i))
		set splitted to (sourceString's componentsSeparatedByString:delim) as list
		set anArray to (current application's NSArray's arrayWithArray:(reverse of splitted))
		set item i of aList to ((anArray's componentsJoinedByString:delim) as text)
	end repeat
	return aList
end Swap

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 29 avril 2019 16:42:45

Maybe a bit neater.

use framework "Foundation"
use scripting additions

set ChoiceList to {¬
	"Write		Address", ¬
	"Phone		Home", ¬
	"Drive		Car", ¬
	"Read		Book"}

my AlphaSort(ChoiceList)

on AlphaSort(ChoiceList)
	set anNSArray to my swap(current application's NSArray's arrayWithArray:ChoiceList)
	set anNSListSorted to anNSArray's sortedArrayUsingSelector:"localizedStandardCompare:"
	set SortedListAlphabetically to (my swap(anNSListSorted)) as list
end AlphaSort

on swap(anArray)
	set delim to tab & tab
	set newArray to current application's NSMutableArray's arrayWithArray:{}
	set i to 0
	repeat with anItem in anArray
		set splitted to (anItem's componentsSeparatedByString:delim) as list
		set tempArray to (current application's NSArray's arrayWithArray:(reverse of splitted))
		set newItem to (tempArray's componentsJoinedByString:delim)
		(newArray's insertObject:newItem atIndex:i)
		set i to i + 1
	end repeat
	return newArray
end swap

Or, if you are sure that the list doesn’t contain duplicates,

use framework "Foundation"
use scripting additions

set ChoiceList to {¬
	"Write		Address", ¬
	"Phone		Home", ¬
	"Drive		Car", ¬
	"Read		Book"}

my AlphaSort(ChoiceList)

on AlphaSort(ChoiceList)
	set anNSArray to my swap(current application's NSMutableArray's arrayWithArray:ChoiceList)
	set anNSListSorted to anNSArray's sortedArrayUsingSelector:"localizedStandardCompare:"
	set SortedListAlphabetically to (my swap(anNSListSorted)) as list
end AlphaSort

on swap(anArray) # anArray is already a mutable array
	#But we must redefine the array as mutable
	set delim to tab & tab
	set anArray to current application's NSMutableArray's arrayWithArray:anArray
	set i to 0
	repeat with anItem in anArray
		set splitted to (anItem's componentsSeparatedByString:delim) as list
		set tempArray to (current application's NSArray's arrayWithArray:(reverse of splitted))
		set newItem to (tempArray's componentsJoinedByString:delim)
		(anArray's removeObject:anItem)
		(anArray's insertObject:newItem atIndex:i)
		set i to i + 1
	end repeat
	return anArray
end swap

I found no way to remove an object at a given index.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 29 avril 2019 17:48:57

Here’s a different take on Yvan’s idea, using dictionary keys instead of modified strings:

use framework "Foundation"
use scripting additions

set ChoiceList to {¬
	"Write		Address", ¬
	"Phone		Home", ¬
	"Drive		Car", ¬
	"Read		Book"}

sortOnLastWord(ChoiceList)

on sortOnLastWord(ChoiceList)
	-- Build a list of records, each record containing a string from ChoiceList and its last word.
	set keyedChoices to {}
	repeat with thisChoice in ChoiceList
		tell thisChoice to set end of keyedChoices to {fullString:contents, lastWord:last word}
	end repeat
	-- From this, derive a mutable array of dictionaries.
	set keyedChoices to current application's class "NSMutableArray"'s arrayWithArray:(keyedChoices)
	
	-- Sort the array on the values associated with the dictionaries' 'lastWord' keys, ascending, Finder-style.
	set sortDescriptor to current application's class "NSSortDescriptor"'s sortDescriptorWithKey:("lastWord") ascending:(true) selector:("localizedStandardCompare:")
	tell keyedChoices to sortUsingDescriptors:({sortDescriptor})
	
	-- Return the sorted array's 'fullString' values as an AS list.
	return (keyedChoices's valueForKey:("fullString")) as list
end sortOnLastWord

The same but this time it’s able to do the job if the last component is not a single word.

use framework "Foundation"
use scripting additions

set ChoiceList to {¬
	"Write		Address", ¬
	"Phone		Home", ¬
	"Drive		Car", ¬
	"Read		Book", ¬
	"Country		Andhra Pradesh"}

sortOnLastWord(ChoiceList)

on sortOnLastWord(ChoiceList)
	-- Build a list of records, each record containing a string from ChoiceList and its last word.
	set keyedChoices to {}
	repeat with thisChoice in ChoiceList
		set lastName to text ((offset of (tab & tab) in thisChoice) + 2) thru -1 of thisChoice
		tell thisChoice to set end of keyedChoices to {fullString:contents, lastWord:lastName}
	end repeat
	-- From this, derive a mutable array of dictionaries.
	set keyedChoices to current application's class "NSMutableArray"'s arrayWithArray:(keyedChoices)
	
	-- Sort the array on the values associated with the dictionaries' 'lastWord' keys, ascending, Finder-style.
	set sortDescriptor to current application's class "NSSortDescriptor"'s sortDescriptorWithKey:("lastWord") ascending:(true) selector:("localizedStandardCompare:")
	tell keyedChoices to sortUsingDescriptors:({sortDescriptor})
	
	-- Return the sorted array's 'fullString' values as an AS list.
	return (keyedChoices's valueForKey:("fullString")) as list
end sortOnLastWord
--> {"Write		Address", "Country		Andhra Pradesh", "Read		Book", "Drive		Car", "Phone		Home"}

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mardi 30 avril 2019 09:27:09

If you are only sorting plain text (and I may not fully understand your intention…was it to use the text in “Choose list”?), using shell would avoid all these complicated machinations with ASOC:

echo “your list” | sort -k 2

you may need to adjust the field to sort by depending on the number of tabs/fields, see ‘man sort’.