Subsorting a list first by kind then modification date

Hi guys

The goal is to sort an icon list first by kind and then group each kind-category by modification date.

set iconList to sort every item of (path to desktop) by kind

If I sort this list by kind, the sorting goes by default first by kind and then each kind-category is subsorted by name.

Is there an easy way to sort this by kind and “subsort” each kind-category instead of default name by modification date? Or do you have to break it down from the start completely into sorting parts or even recursive list-sorting - and how would you then get the sorting by modification date?

If someone could give me a starting point, I would try from there. Thanks a lot!


tell application "Finder"
	set iconList to sort (sort items of desktop by modification date) by kind
end tell

Embarrassingly easy :smiley: if you know it. Thanks once again to the pro KniazidisR. It helps a lot!

Unfortunately, this gives the same result as simply sorting by kind. You’ve either got to then go through each kind and subsort by modification date or do a customised sort which handles both at the same time. Another complication is that sorting items in an application or on disk (both here!) by their properties can take ages, because the properties have to be fetched again every single time two of the items are compared in the sort. So if you want to use the Finder, it’s best to prefetch all the relevant properties just once and keep them in a list or record.

The solution below uses the customisable sort listed here, which (if you decide to use it) should be compiled and saved in /Users/(yourHomeFolder)/Library/Script Libraries/ as “Custom Iterative Ternary Merge Sort.scpt”. It’ll then be possible to compile the script below.

The script can still take a few seconds to execute, but most of the time is the Finder doing the initial fetch of data from the disk. It would undoubtedly be faster to use ASObjC, but then you’d have to learn ASObjC. :slight_smile: And anyway, I don’t know what kind of results are required in the sorted list.

use AppleScript version "2.3.1" -- Mac OS X 10.9 (Mavericks) or later.
use sorter : script "Custom Iterative Ternary Merge Sort" -- <https://macscripter.net/viewtopic.php?pid=194430#p194430>
use scripting additions

-- Get a list of three parallel lists containing the items' kinds, modification dates, and the items themselves.
tell application "Finder" to set theInfo to {kind, modification date, it} of every item of desktop

-- Pair the kinds and modification dates as a list of records for sorting.
set sortingCategories to {}
set itemCount to (count item 1 of theInfo)
repeat with i from 1 to itemCount
	set end of sortingCategories to {kind:item i of item 1 of theInfo, modDate:item i of item 2 of theInfo}
end repeat
-- And set a variable to the list containing the items.
set iconList to item 3 of theInfo

-- Set up a customised item comparer for the sort.
-- Its handler returns true or false according to whether it thinks
-- item a from the list being sorted should go after item b.
script o
	on isGreater(a, b)
		-- a and b will here be two records from the 'sortingCategories' list.
		-- If their 'kind' properties are the same, return whether or not a's 'modDate' comes after b's.
		-- (Change 'comes after' to 'comes before' if you want the more recent dates first.)
		if (a's kind = b's kind) then return (a's modDate comes after b's modDate)
		-- Otherwise return whether a's 'kind' comes after b's.
		return (a's kind comes after b's kind)
	end isGreater
end script
-- Sort 'sortingCategories' using the above custom comparer and rearranging 'iconList' in parallel.
tell sorter to sort(sortingCategories, 1, itemCount, {comparer:o, slave:{iconList}})
-- iconList is now sorted by kind and subsorted by modification date.
return iconList

Yeah I was too happy, that it would go with a simple oneliner-code. In my later test it would also not work (and result as simply sorting by kind) cause of what you stated Nigel. Here is the full sleak code (by peavine) that i try to make work for sorting by kind and then group the columns by modification date. Thanks for answering Nigel. As a Beginner this is quite a steep hill but let’s see if i can manage.

on main()
   set firstIconPosition to {1850, 150} -- x and y coordinates of first icon
   set iconSpace to 120 -- vertical space between icons
   set columnSpace to -120 -- make negative for right-to-left column ordering
   set columnIconCount to 6 -- maximum number of icons in a column
   
   set iconCount to 0
   set nextIconPosition to firstIconPosition
   
   tell application "Finder"
-- i tried here to use it
       set iconList to sort every item of (path to desktop) by kind
       if iconList = {} then error number -128
       
       set desktop position of (item 1 of iconList) to firstIconPosition
       set firstIconKind to kind of (item 1 of iconList)
       
       repeat with anIcon in (rest of iconList)
           set iconCount to iconCount + 1
           set nextIconKind to kind of anIcon
           if firstIconKind ≠ nextIconKind or (iconCount mod columnIconCount) = 0 then
               set nextIconPosition to {(item 1 of nextIconPosition) + columnSpace, item 2 of firstIconPosition}
               set iconCount to 0
           else
               set nextIconPosition to {item 1 of nextIconPosition, (item 2 of nextIconPosition) + iconSpace}
           end if
           set desktop position of anIcon to nextIconPosition
           set firstIconKind to nextIconKind
       end repeat
   end tell
end main

main()

Damn this Finder with its weirdness. The fact that my code does not give the correct result means that the Finder performs some actions that it knows only when it is executed - something that no one asks of it.

Anyway, Nigel Garvey offered a great solution to the problem.

It does seem rather strange. It’s natural to assume that the Finder would do a “stable” sort.

But maybe it does. If its scripting implementation is an automation of what it does normally in its GUI, which is what scripting it is for, it seems likely that each ‘sort’ command sorts firstly by name and then, stably, by the specified property. Since no two items in the folder will have the same name, the sort on name will wipe out the effect of any previous sort by modification date.

The script contained below takes a different approach in that it arranges the columns by file extension (folders first). This isn’t what the OP requested but perhaps it will do the job. This script has the advantage of speed and simplicity, although, it does break if it encounters a file with no extension (plus it’s a bit kludgey).

A better approach might be:

  1. Have Finder create an alias list without any sorting.
  2. Use ASObjC to sort the alias list by file extension and to subsort by modification date.
  3. Have Finder arrange the icons on the desktop, starting a new column when a new file extension is encountered.

I only have a basic knowledge of ASObjcC sort routines, so I didn’t try to code this approach to see if it would work.

use framework "AppKit"
use framework "Foundation"
use scripting additions

on main()
	set firstIconPosition to {1850, 150} -- x and y coordinates of first icon
	set iconSpace to 120 -- vertical space between icons
	set columnSpace to -120 -- make negative for right-to-left column ordering
	set columnIconCount to 6 -- maximum number of icons in a column
	
	set iconCount to 0
	set nextIconPosition to firstIconPosition
	
	tell application "Finder"
		set iconList to sort every item of (path to desktop) by modification date
		if iconList = {} then error number -128
		repeat with anItem in iconList
			set contents of anItem to anItem as alias
		end repeat
		set iconList to my sortByType(iconList)
		
		set desktop position of item (item 1 of iconList) to firstIconPosition
		set firstIconKind to kind of item (item 1 of iconList)
		
		repeat with anIcon in (rest of iconList)
			set iconCount to iconCount + 1
			set nextIconKind to kind of item anIcon
			if firstIconKind ≠ nextIconKind or (iconCount mod columnIconCount) = 0 then
				set nextIconPosition to {(item 1 of nextIconPosition) + columnSpace, item 2 of firstIconPosition}
				set iconCount to 0
			else
				set nextIconPosition to {item 1 of nextIconPosition, (item 2 of nextIconPosition) + iconSpace}
			end if
			set desktop position of item anIcon to nextIconPosition
			set firstIconKind to nextIconKind
		end repeat
	end tell
end main

on sortByType(iconList)
	set selectedFiles to current application's NSArray's arrayWithArray:iconList
	set theDescriptor to current application's NSSortDescriptor's sortDescriptorWithKey:"pathExtension" ascending:true selector:"localizedStandardCompare:"
	return (selectedFiles's sortedArrayUsingDescriptors:{theDescriptor}) as list
end sortByType


main()

This ASObjC version is fast, if not necessarily simple. :slight_smile:

use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
use scripting additions

set desktopPath to POSIX path of (path to desktop)

set |⌘| to current application
-- Get the desktop items as an array of NSURLs, including the path, mod date, and localized type keys.
set desktopURL to |⌘|'s class "NSURL"'s fileURLWithPath:(desktopPath)
set fileManager to |⌘|'s class "NSFileManager"'s defaultManager()
set resourceKeys to {|⌘|'s NSURLPathKey, |⌘|'s NSURLContentModificationDateKey, |⌘|'s NSURLLocalizedTypeDescriptionKey}
set itemURLs to fileManager's contentsOfDirectoryAtURL:(desktopURL) includingPropertiesForKeys:(resourceKeys) ¬
	options:(|⌘|'s NSDirectoryEnumerationSkipsHiddenFiles) |error|:(missing value)

-- Get dictionaries containing the keys from the returned URLs.
set sortingDictionaries to {}
repeat with thisURL in itemURLs
	set end of sortingDictionaries to (thisURL's resourceValuesForKeys:(resourceKeys) |error|:(missing value))
end repeat
set sortingDictionaries to |⌘|'s class "NSMutableArray"'s arrayWithArray:(sortingDictionaries)

-- Sort the dictionaries on localized type, subsorting on mod date.
set kindDescriptor to |⌘|'s class "NSSortDescriptor"'s sortDescriptorWithKey:(|⌘|'s NSURLLocalizedTypeDescriptionKey) ¬
	ascending:(true) selector:("localizedStandardCompare:")
set modDateDescriptor to |⌘|'s class "NSSortDescriptor"'s sortDescriptorWithKey:(|⌘|'s NSURLContentModificationDateKey) ¬
	ascending:(false) -- or true to get ascending modification dates.
tell sortingDictionaries to sortUsingDescriptors:({kindDescriptor, modDateDescriptor})

-- Extract the (POSIX) path values and convert them to the required specifier type.
set sortedItems to (sortingDictionaries's valueForKey:(|⌘|'s NSURLPathKey)) as list
repeat with thisItem in sortedItems
	set itemSpecifier to thisItem as POSIX file as alias
	-- tell application "Finder" to set itemSpecifier to item itemSpecifier -- Uncomment if Finder specifiers needed.
	set thisItem's contents to itemSpecifier
end repeat

return sortedItems

It isn’t fast, but very very fast. :smiley: So, I deleted my slow script. It was slower about 150 times than yours.

Nigel. I tested your script and it works great.

I inserted your code in the script the OP included in post 5 and ran some timing tests, comparing it with my script in post 8. With 25 desktop icons, your script took 187 milliseconds and my script 365 milliseconds. Plus, your script does just what the OP wants and will be very reliable.

Wow, next level pro exchange! One line of code made quite some trouble. I could’t have done this in a billion years - but that forum is just too crazy good. Thanks to peavine, KniazidisR and Nigel!! Here is the whole script as a combination. The case has been brilliantly closed.

use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
use scripting additions

-- start Nigels magic sauce

on main()
	set desktopPath to POSIX path of (path to desktop)
	
	set |⌘| to current application
	-- Get the desktop items as an array of NSURLs, including the path, mod date, and localized type keys.
	set desktopURL to |⌘|'s class "NSURL"'s fileURLWithPath:(desktopPath)
	set fileManager to |⌘|'s class "NSFileManager"'s defaultManager()
	set resourceKeys to {|⌘|'s NSURLPathKey, |⌘|'s NSURLContentModificationDateKey, |⌘|'s NSURLLocalizedTypeDescriptionKey}
	set itemURLs to fileManager's contentsOfDirectoryAtURL:(desktopURL) includingPropertiesForKeys:(resourceKeys) ¬
		options:(|⌘|'s NSDirectoryEnumerationSkipsHiddenFiles) |error|:(missing value)
	
	-- Get dictionaries containing the keys from the returned URLs.
	set sortingDictionaries to {}
	repeat with thisURL in itemURLs
		set end of sortingDictionaries to (thisURL's resourceValuesForKeys:(resourceKeys) |error|:(missing value))
	end repeat
	set sortingDictionaries to |⌘|'s class "NSMutableArray"'s arrayWithArray:(sortingDictionaries)
	
	-- Sort the dictionaries on localized type, subsorting on mod date.
	set kindDescriptor to |⌘|'s class "NSSortDescriptor"'s sortDescriptorWithKey:(|⌘|'s NSURLLocalizedTypeDescriptionKey) ¬
		ascending:(true) selector:("localizedStandardCompare:")
	set modDateDescriptor to |⌘|'s class "NSSortDescriptor"'s sortDescriptorWithKey:(|⌘|'s NSURLContentModificationDateKey) ¬
		ascending:(false) -- or true to get ascending modification dates.
	tell sortingDictionaries to sortUsingDescriptors:({kindDescriptor, modDateDescriptor})
	
	-- Extract the (POSIX) path values and convert them to the required specifier type.
	set sortedItems to (sortingDictionaries's valueForKey:(|⌘|'s NSURLPathKey)) as list
	repeat with thisItem in sortedItems
		set itemSpecifier to thisItem as POSIX file as alias
		-- tell application "Finder" to set itemSpecifier to item itemSpecifier -- Uncomment if Finder specifiers needed.
		set thisItem's contents to itemSpecifier
	end repeat

-- start peavines magic powder 
	
	set firstIconPosition to {1850, 150} -- x and y coordinates of first icon
	set iconSpace to 120 -- vertical space between icons
	set columnSpace to -120 -- make negative for right-to-left column ordering
	set columnIconCount to 6 -- maximum number of icons in a column
	
	set iconCount to 0
	set nextIconPosition to firstIconPosition
	
	tell application "Finder"
		-- the begin of a beautiful handshake iconList becomes kind sorted and modification date grouped
		set iconList to sortedItems
		if iconList = {} then error number -128
		
		set desktop position of (item 1 of iconList) to firstIconPosition
		set firstIconKind to kind of (item 1 of iconList)
		
		repeat with anIcon in (rest of iconList)
			set iconCount to iconCount + 1
			set nextIconKind to kind of anIcon
			if firstIconKind ≠ nextIconKind or (iconCount mod columnIconCount) = 0 then
				set nextIconPosition to {(item 1 of nextIconPosition) + columnSpace, item 2 of firstIconPosition}
				set iconCount to 0
			else
				set nextIconPosition to {item 1 of nextIconPosition, (item 2 of nextIconPosition) + iconSpace}
			end if
			set desktop position of anIcon to nextIconPosition
			set firstIconKind to nextIconKind
		end repeat
	end tell
end main

main()

Hi again.

I’m not sure how much difference it would make to performance, but since the ‘sortingDictionaries’ variable further up-script already contains the ‘kinds’ in sorted order, I’d be tempted to use the information from there instead of using the Finder to read it again from disk for each individual item. It would involve inserting this just above the “-- start peavines magic powder” comment:

-- Extract the 'kind' values.
set kindList to (sortingDictionaries's valueForKey:(|⌘|'s NSURLLocalizedTypeDescriptionKey)) as list

And changing this …

set firstIconKind to kind of (item 1 of iconList)

repeat with anIcon in (rest of iconList)
	set iconCount to iconCount + 1
	set nextIconKind to kind of anIcon

… to this:

set firstIconKind to item 1 of kindList

repeat with i from 2 to (count iconList) 
	set anIcon to item i of iconList
	set iconCount to iconCount + 1
	set nextIconKind to item i of kindList

Nigel. I ran some new timing tests.

I took NewtonsLaws’ script from post 12 and made the changes you suggest–I put your code in a handler just to simplify matters. I next tested NewtonsLaws’ script in post 12 exactly as written and, just as a basemark, my script in post 8. My desktop contained 25 icons and, between tests, I ran another script that arranged all desktop icons by name. I did this to insure that the tested scripts were actually moving icons.

Your suggestion significantly improved the timing result of the script:

  • From Post 12 with Nigel’s suggestion - 211 milliseconds

  • From Post 12 as written - 285 milliseconds

  • Peavine’s script from Post 8 - 382 milliseconds

The tested script which incorporated your suggestion is:

use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
use scripting additions

on main()
	set firstIconPosition to {1850, 150} -- x and y coordinates of first icon
	set iconSpace to 120 -- vertical space between icons
	set columnSpace to -120 -- make negative for right-to-left column ordering
	set columnIconCount to 6 -- maximum number of icons in a column
	
	set iconCount to 0
	set nextIconPosition to firstIconPosition
	
	set {iconList, kindList} to getIconInfo()
	if iconList = {} then error number -128
	
	tell application "Finder"
		set desktop position of (item 1 of iconList) to firstIconPosition
		set firstIconKind to item 1 of kindList
		
		repeat with i from 2 to (count iconList)
			set anIcon to item i of iconList
			set iconCount to iconCount + 1
			set nextIconKind to item i of kindList
			
			if firstIconKind ≠ nextIconKind or (iconCount mod columnIconCount) = 0 then
				set nextIconPosition to {(item 1 of nextIconPosition) + columnSpace, item 2 of firstIconPosition}
				set iconCount to 0
			else
				set nextIconPosition to {item 1 of nextIconPosition, (item 2 of nextIconPosition) + iconSpace}
			end if
			set desktop position of anIcon to nextIconPosition
			set firstIconKind to nextIconKind
		end repeat
	end tell
end main

on getIconInfo()
	set desktopPath to POSIX path of (path to desktop)
	
	set |⌘| to current application
	
	set desktopURL to |⌘|'s class "NSURL"'s fileURLWithPath:(desktopPath)
	set fileManager to |⌘|'s class "NSFileManager"'s defaultManager()
	set resourceKeys to {|⌘|'s NSURLPathKey, |⌘|'s NSURLContentModificationDateKey, |⌘|'s NSURLLocalizedTypeDescriptionKey}
	set itemURLs to fileManager's contentsOfDirectoryAtURL:(desktopURL) includingPropertiesForKeys:(resourceKeys) ¬
		options:(|⌘|'s NSDirectoryEnumerationSkipsHiddenFiles) |error|:(missing value)
	
	set sortingDictionaries to {}
	repeat with thisURL in itemURLs
		set end of sortingDictionaries to (thisURL's resourceValuesForKeys:(resourceKeys) |error|:(missing value))
	end repeat
	set sortingDictionaries to |⌘|'s class "NSMutableArray"'s arrayWithArray:(sortingDictionaries)
	
	set kindDescriptor to |⌘|'s class "NSSortDescriptor"'s sortDescriptorWithKey:(|⌘|'s NSURLLocalizedTypeDescriptionKey) ¬
		ascending:(true) selector:("localizedStandardCompare:")
	set modDateDescriptor to |⌘|'s class "NSSortDescriptor"'s sortDescriptorWithKey:(|⌘|'s NSURLContentModificationDateKey) ¬
		ascending:(false) -- or true to get ascending modification dates.
	tell sortingDictionaries to sortUsingDescriptors:({kindDescriptor, modDateDescriptor})
	
	set sortedItems to (sortingDictionaries's valueForKey:(|⌘|'s NSURLPathKey)) as list
	repeat with thisItem in sortedItems
		set itemSpecifier to thisItem as POSIX file as alias
		set thisItem's contents to itemSpecifier
	end repeat
	
	set kindList to (sortingDictionaries's valueForKey:(|⌘|'s NSURLLocalizedTypeDescriptionKey)) as list
	
	return {sortedItems, kindList}
end getIconInfo

main()

Hi peavine.

Thanks for trying out the new suggestion and posting the results. Nice to know it does actually make a difference. :slight_smile:

Your desktop sounds less cluttered than mine. :wink:

Thanks Nigel and peavine for further improving the script! To polish and finish the script, it would be great to work on all our different screen resolutions, no matter where you use it - on laptops or big screens. I tried this and it works great for me. Of course it’s a bit clumsy, compared to you guys script skills, so feel free to refine it if you feel like. I’m sure there are better ways than my own.

tell application "Finder" to set getRezolution to get bounds of window of desktop
-- space from right is 7 % of the total screen rez
	set screendynamic to round ((item 3 of getRezolution) / 100 * 7)
	set iconSpace to 120 -- vertical space between icons
	set columnSpace to -140 -- make negative for right-to-left column ordering
	set firstIconPosition to {(item 3 of getRezolution) - screendynamic, 150} -- x and y coordinates of first icon
-- check how many icons fit in vertical row 
	set columnIconCount to round ((item 4 of getRezolution) / iconSpace) - 2 -- maximum number of icons in a column

NewtonsLaws. Your code appears to do two things. First, the wider the display, the greater the distance from the first column of icons to the right side of the screen. This is primarily a matter of personal preference, but the code seems to work as you want, and I don’t know of a way to make it significantly better. Personally, I would just deduct 70 (or whatever figure I want) from the screen width, but there’s no right or wrong about this.

Second, the code calculates the number of icons that will fit vertically on the screen, and I do think a correction may be required that factors in the vertical distance from the top of the screen to the first icon. Also, the div operator effectively rounds down to the nearest whole number, which seems desirable when calculating the number of icons that will fit vertically on the screen, and it eliminates the need for the round command.

Taken together:

tell application "Finder" to set getRezolution to bounds of window of desktop
set iconSpace to 120 -- vertical space between icons
set columnSpace to -140 -- make negative for right-to-left column ordering
set firstIconPosition to {((item 3 of getRezolution) - 70), 150} -- x and y coordinates of first icon
set columnIconCount to ((((item 4 of getRezolution) - (item 2 of firstIconPosition)) div iconSpace) - 2)

Thanks so much peavine! What you state makes all of course perfect sense. The code is again way better like that. I learned a lot on all of this, your script is really a so much fun and very cool to use. Many thanks for all your time on this.