NSDictionary sort according to a list

What’s the method to sort an array of dictionaries according to a list?

Let’s say the dictionaries contains a key named “category”.
The values can be {“bird”,“fish”,“dog”,“cat”}.
I want the array sorted in this order an not in alphabetical.

There isn’t such an animal.

:joy: and :disappointed:

With this sort saved in a Script Libraries folder as “Custom Iterative Ternary Merge Sort.scpt”:

use framework "Foundation"
use sorter : script "Custom Iterative Ternary Merge Sort"

local dictionaryArray, sortedArray

set dictionaryArray to current application's class "NSArray"'s arrayWithArray:({{category:"fish", thing:"guppy"}, {category:"dog", thing:"chihuahua"}, {category:"fish", thing:"hammerhead shark"}, {category:"cat", thing:"tiger"}, {category:"bird", thing:"cuckoo"}, {category:"dog", thing:"husky"}, {category:"cat", thing:"wildcat"}, {category:"dog", thing:"border collie"}, {category:"bird", thing:"bald eagle"}})
set sortedArray to my sortDictionaryArray:dictionaryArray withCategoryOrder:{"bird", "fish", "dog", "cat"}

(* Sort an array of category/thing dictionaries with the categories in a specified order and the things sorted naturally in them. *)
on sortDictionaryArray:dictionaryArray withCategoryOrder:categoryList
	-- Script object containing a custom comparison handler for the sort.
	script o
		property categoryOrder : categoryList
		property categoryCount : (count categoryList)
		
		on isGreater(a, b) -- Return true if a should be moved to after b, otherwise false.
			set categoryA to a's category
			set categoryB to b's category
			
			if (categoryA = categoryB) then
				-- Are the categories are the same with a's thing coming after b's thing?
				return (a's thing > b's thing)
			else
				-- Does a's category come after b's in the specified order?
				repeat with i from 1 to categoryCount
					if (item i of my categoryOrder is categoryA) then exit repeat
				end repeat
				repeat with j from 1 to categoryCount
					if (j < i) then
						if (item j of my categoryOrder is categoryB) then return true
					else
						return false
					end if
				end repeat
			end if
		end isGreater
	end script
	
	-- Coerce the dictionary array to a list of records.
	set recordList to dictionaryArray as list
	-- Sort items 1 thru -1 of the list using the comparison script above.
	tell sorter to sort(recordList, 1, -1, {comparer:o})
	
	-- Return an array version of the sorted list.
	return current application's class "NSArray"'s arrayWithArray:(recordList)
end sortDictionaryArray:withCategoryOrder:

@NigelGarvey

Very impressive!
I think it will take me some time to understand all that!

What matters for now is that everything works well, and fast.
Thank you for your help.

:slight_smile:

If you wanted to stay in ASObjC, you could perhaps do it this way:

use framework "Foundation"

local dictionaryArray, subsortedArray, sortedArray, sortOnThing, filter

set dictionaryArray to current application's class "NSArray"'s arrayWithArray:({{category:"fish", thing:"guppy"}, {category:"dog", thing:"chihuahua"}, {category:"fish", thing:"hammerhead shark"}, {category:"cat", thing:"tiger"}, {category:"bird", thing:"cuckoo"}, {category:"dog", thing:"husky"}, {category:"cat", thing:"wildcat"}, {category:"dog", thing:"border collie"}, {category:"bird", thing:"bald eagle"}})

-- Initially sort on the 'thing' values.
set sortOnThing to current application's class "NSSortDescriptor"'s sortDescriptorWithKey:("thing") ascending:(true)
set subsortedArray to dictionaryArray's sortedArrayUsingDescriptors:({sortOnThing})
-- Then extract dictionary arrays in the required 'category' order and stick them together.
set sortedArray to current application's class "NSMutableArray"'s new()
repeat with thisCategory in {"bird", "fish", "dog", "cat"}
	set filter to current application's class "NSPredicate"'s predicateWithFormat_("category == %@", thisCategory)
	tell sortedArray to addObjectsFromArray:(subsortedArray's filteredArrayUsingPredicate:(filter))
end repeat
sortedArray

You are reading my mind!

Thank you again.

:slight_smile: