I’m wondering if anyone knows how to solve the following:
I’ve a list of POSIX paths (variable: listOfFiles)
Example:
No I want to find out what parent path all these paths have in common!
(In this case “this/is/” would be the answer)
EqualParentPath({"this/is/a/test/path", "this/is/another/test/path", "this/is/a/taste/path", "this/is/a/test/file", "this/is/not/really/a/test/path"})
--Result: {"this/is/",8}
on EqualParentPath(listOfFiles)
--script stuff comes here
return {theEqualPath, lastEqualChar}
end EqualParentPath
Model: iBook G4
AppleScript: 1.10.3
Browser: Safari 412.5
Operating System: Mac OS X (10.4)
I have a commonParent handler that deals with AppleScript paths that I’m sure you could modify to suit your needs.
on commonParent(theseFiles)
if theseFiles is not {} then
set startupDisk to characters 1 thru -2 of (path to startup disk as text) as text
set fList to {}
repeat with thisFile in theseFiles
set end of fList to textToList(thisFile as text, ":")
end repeat
set lastCommonParent to {} -- a list to get all common parent path components
set shouldRecordParent to true
try
set baseCompare to item 1 of fList
repeat with i from 1 to count of baseCompare
set thisParentItem to item i of baseCompare
repeat with j from 2 to count of fList
set subCompareItem to item i of item j of fList
if subCompareItem is not equal to thisParentItem then
set shouldRecordParent to false
end if
end repeat
if shouldRecordParent then set end of lastCommonParent to thisParentItem
end repeat
if startupDisk is in lastCommonParent and (count of lastCommonParent) is 1 then
-- the common parent is the startup disk
-- disallow
log "EXCEPTION@commonParent() : Common parent is the startup disk."
return "Common parent is the startup disk."
else
set commonParentPath to listToText(lastCommonParent, ":") & ":" as alias
end if
on error e
return e
end try
else
return missing value
end if
end commonParent
on textToList(thisText, delim)
set {tid, my text item delimiters} to {my text item delimiters, delim}
try
set textList to every text item of thisText
set my text item delimiters to tid
on error
set my text item delimiters to tid
end try
return textList
end textToList
on listToText(thisList, delim)
set {tid, my text item delimiters} to {my text item delimiters, delim}
try
set textList to every item of thisList as text
set my text item delimiters to tid
on error
set my text item delimiters to tid
return e
end try
return textList
end listToText
I read the man page for dirname to see if I could figure how best to trim the code but I couldn’t come up with anything - though I admit I’m not that clever. What I did notice was my sloppy return values, which led to spotting another major fix, which trimmed the main handler nicely. Here is the corrected main handler:
on commonParent(theseFiles)
set fList to {}
repeat with thisFile in theseFiles
set end of fList to textToList(thisFile as text, "/")
end repeat
set lastCommonParent to {} -- a list to get all common parent path components
set shouldRecordParent to true
try
set baseCompare to item 1 of fList
repeat with i from 1 to count of baseCompare
set thisParentItem to item i of baseCompare
repeat with j from 2 to count of fList
set subCompareItem to item i of item j of fList
if subCompareItem is not equal to thisParentItem then set shouldRecordParent to false
end repeat
if shouldRecordParent then set end of lastCommonParent to thisParentItem
end repeat
end try
return listToText(lastCommonParent, "/") & "/"
end commonParent
Even though I’m still not seeing how I could use dirname in a more efficient algorithm, this is helping me a lot. It would be nice to know if I’m just overlooking something obvious, which has been known to happen.
Anyway, I made a few more tiny modifications to eliminate the listToText handler and the unecessary first loop, and accommodate either standard paths or POSIX paths.
on commonParent(theseFiles)
set delim to ":"
if item 1 of theseFiles as text does not contain ":" then set delim to "/"
set lastCommonParent to {} -- a list to get all common parent path components
set shouldRecordParent to true
try
set baseCompare to textToList(item 1 of theseFiles as text, delim)
repeat with i from 1 to count of baseCompare
set thisParentItem to item i of baseCompare
repeat with j from 2 to count of theseFiles
if item i of textToList(item j of theseFiles as text, delim) is not equal to thisParentItem then set shouldRecordParent to false
end repeat
if shouldRecordParent then set end of lastCommonParent to thisParentItem & delim
end repeat
end try
if delim is "/" then return lastCommonParent as text
return lastCommonParent as text as alias
end commonParent
on textToList(thisText, delim)
set {tid, my text item delimiters} to {my text item delimiters, delim}
try
set textList to every text item of thisText
set my text item delimiters to tid
on error
set my text item delimiters to tid
end try
return textList
end textToList
Joseph, I now wrote one by myself.
(I just didn’t know how to go about doing this when I posted my question)
I wrote my own routine which is about 3x faster than your last one:
on commonParent(fileList)
set delim to ":"
if item 1 of fileList contains "/" then set delim to "/"
set {tid, my text item delimiters} to {my text item delimiters, delim}
set PathToCompare to text items of (item 1 of fileList as text)
set compareUntil to (count PathToCompare)
repeat with i from 2 to count fileList
set comparsionPath to text items of (item i of fileList as text)
repeat with j from 1 to compareUntil
if (item j of PathToCompare) = (item j of comparsionPath) then
else
--this line:
set compareUntil to j - 1
--and this line prevent the handler from wasting extra time n very long paths when difference was found somewhere at the beginning
exit repeat
end if
end repeat
end repeat
try
set common to (items 1 thru compareUntil of PathToCompare) as text
set my text item delimiters to tid
on error
set my text item delimiters to tid
end try
return common
end commonParent
I have some things in mind that could still improve it - I’ll post a optimized version when I’ve checked it.
Hi. If you’re interested, here’s another approach that doesn’t require nested repeats:
on EqualParentPath(listOfFiles)
set astid to AppleScript's text item delimiters
-- It's assumed here that the paths are POSIX paths.
-- Combine the paths into a single text, each path beginning with a 'return' marker.
set AppleScript's text item delimiters to return
set listtext to (return as Unicode text) & listOfFiles
-- Use the first return-marked path as a test source.
set path1 to (return as Unicode text) & item 1 of listOfFiles
-- Count the paths.
set pathCount to (count listOfFiles)
-- Set the TIDs to progressively longer sections of the first path.
-- If there are more text items in the combined text than there are paths, all the paths contain the TIDs value.
-- Otherwise, at least one of the paths does not match. The previous TIDs value is then what we want.
set theEqualPath to "" -- In case there are no matches at all.
set AppleScript's text item delimiters to "/"
repeat with i from 1 to (count path1's text items)
set AppleScript's text item delimiters to "/"
set AppleScript's text item delimiters to (text 1 thru text item i of path1) & "/"
if (count listtext's text items) > pathCount then
set theEqualPath to AppleScript's text item delimiters
else
exit repeat
end if
end repeat
set AppleScript's text item delimiters to astid
-- Lose the leading return from the result
if ((count theEqualPath) > 0) then set theEqualPath to text 2 thru -1 of theEqualPath
return {theEqualPath, (count theEqualPath)}
end EqualParentPath
EqualParentPath({"this/is/a/test/path", "this/is/another/test/path", "this/is/a/taste/path", "this/is/a/test/file", "this/is/not/really/a/test/path"})
--Result: {"this/is/",8}
Nigel, I think we’ve (or better “you’ve”) found the best way to do it!
I chacked them all with fila paths like this one:
your method is the only smart one! The other methods do too much checking than needed if the paths differ somewhere near the beginning.
And it is the fastest one! (I replaced the “as Unicode text” with “as text” to have a fair comparison)
It’s even 3x faster than mine! And therefore 10x faster than the one before.
Thank you all(!) for these great code snippets! It’s you who make Macscripter such a great place!
One problem for me, Nigel’s handler doesn’t handle a list of Finder aliases :(. But Vincent’s does. Getting the index of the last common component and the exit repeat is just what I was looking for for that handler. Thanks.
Two notes about your script, Vincent:
if item 1 of fileList contains "/" then set delim to "/"
Mac file names can contain “/” but can’t contain “:” which is why I test the other way.
set common to (items 1 thru compareUntil of PathToCompare) as text
For a standard path, this would also need the trailing “:”. I know POSIX paths don’t require the trailing “/” but having it doesn’t effect the reference either so the line should have the " & delim" at the end to be safe.
Anyway, any advice on how to accommodate aliases with your method, Nigel?
Hi, Joseph. I think the version below is good for aliases, file specifications, Mac OS paths, and POSIX paths, but not for Finder or System Events references. It assumes that all the items in the list are of the same type. (You may want to develop the input check at the top to ensure that they are.) It returns a POSIX path if the list contains POSIX paths, and a Mac OS path otherwise.
You noted that Mac OS file names can contain “/”, but the POSIX paths of those same files will contain “:” instead! So I’ve used the expedient of trying to coerce one of the paths to file specification. If it works, the action is for Mac OS paths; otherwise it’s assumed they’re POSIX paths.
on EqualParentPath(listOfFiles)
-- This input check needs to be more fully developed.
if not ((class of listOfFiles is list) and ((count listOfFiles) > 0) and (class of item 1 of listOfFiles is in {alias, file specification, string, Unicode text})) then
error
end if
set astid to AppleScript's text item delimiters
-- It's assumed here that the list items are all the same type and are
-- POSIX paths, HFS paths, aliases, or file specifications.
-- Coerce the list to a single text, each item beginning with a 'return' marker.
set AppleScript's text item delimiters to return
set listText to (return as Unicode text) & listOfFiles
-- Use the first return-marked item (path) as a test source.
set path1 to text 1 thru text item 2 of listText
-- If it can be coerced to file specification, its a Mac OS path. Otherwise assume it's a POSIX path.
try
(text item 2 of path1) as file specification
set pathDelim to ":"
on error
set pathDelim to "/"
end try
-- Count the paths.
set pathCount to (count listOfFiles)
-- Set the TIDs to progressively longer sections of the first path.
-- If there are more text items in the combined text than there are paths, all the paths contain the TIDs value.
-- Otherwise, at least one of the paths does not match. The previous TIDs value is then what we want.
set theEqualPath to "" -- In case there are no matches at all.
set AppleScript's text item delimiters to pathDelim
repeat with i from 1 to (count path1's text items)
set AppleScript's text item delimiters to pathDelim
set AppleScript's text item delimiters to (text 1 thru text item i of path1) & pathDelim
if ((count listText's text items) > pathCount) then
set theEqualPath to AppleScript's text item delimiters
else
exit repeat
end if
end repeat
set AppleScript's text item delimiters to astid
-- Lose the leading return from the result
if ((count theEqualPath) > 0) then set theEqualPath to text 2 thru -1 of theEqualPath
return {theEqualPath, (count theEqualPath)}
end EqualParentPath