Searching list of lists for an exact list match instead of this

Is there an easier method to finding an exact match to a list then combining predicates?
I have shortened the list for testing purposes…
This is where I’m at and I would prefer to find exact match of the list instead.

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

set theListOfLists to {{"A", "Upland Camo", "", "Richardson"}, {"ARB", "Arctic Blue", "PMS 2925 C", "ascolour"}, {"AZB", "Azure Blue", "PMS 2171 C", "Cotton Heritage"}, {"AB", "Aruba Blue", "", "Richardson"}, {"ABBI", "Split Aruba Blue/Birch", "", "Richardson"}, {"ABNKH", "Alternate Brown/Khaki", "", "Richardson"}, {"ACHHG", "Alternate Charcoal/Heather Grey", "", "Richardson"}, {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}, {"AGB", "Alternate Gold/Black", "", "Richardson"}, {"AGN", "Troutdale Split Amber Gold/Navy", "", "Richardson"}, {"AGN", "Alternate Gold/Navy", "", "Richardson"}}
set theFilter to {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}

set thePreds to {}
repeat with each in theFilter
	set end of thePreds to (current application's NSPredicate's predicateWithFormat:" (self CONTAINS %@)" argumentArray:{each})
end repeat
set theArray to (current application's NSArray's arrayWithArray:theListOfLists)
set theListOfLists to (theArray's filteredArrayUsingPredicate:(current application's NSCompoundPredicate's andPredicateWithSubpredicates:thePreds)) as list

I’ve included a suggestion below.

use framework "Foundation"

set theListOfLists to {{"A", "Upland Camo", "", "Richardson"}, {"ARB", "Arctic Blue", "PMS 2925 C", "ascolour"}, {"AZB", "Azure Blue", "PMS 2171 C", "Cotton Heritage"}, {"AB", "Aruba Blue", "", "Richardson"}, {"ABBI", "Split Aruba Blue/Birch", "", "Richardson"}, {"ABNKH", "Alternate Brown/Khaki", "", "Richardson"}, {"ACHHG", "Alternate Charcoal/Heather Grey", "", "Richardson"}, {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}, {"AGB", "Alternate Gold/Black", "", "Richardson"}, {"AGN", "Troutdale Split Amber Gold/Navy", "", "Richardson"}, {"AGN", "Alternate Gold/Navy", "", "Richardson"}}
set theFilter to {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}

set theArrayofArrays to current application's NSArray's arrayWithArray:theListOfLists
set thePredicate to current application's NSPredicate's predicateWithFormat_("self == %@", theFilter)
set theListOfLists to (theArrayofArrays's filteredArrayUsingPredicate:thePredicate) as list
return theListOfLists --> {{"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}}

If the number of exact matches doesn’t matter, then the containsObject method can be used.

use framework "Foundation"

set theListOfLists to {{"A", "Upland Camo", "", "Richardson"}, {"ARB", "Arctic Blue", "PMS 2925 C", "ascolour"}, {"AZB", "Azure Blue", "PMS 2171 C", "Cotton Heritage"}, {"AB", "Aruba Blue", "", "Richardson"}, {"ABBI", "Split Aruba Blue/Birch", "", "Richardson"}, {"ABNKH", "Alternate Brown/Khaki", "", "Richardson"}, {"ACHHG", "Alternate Charcoal/Heather Grey", "", "Richardson"}, {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}, {"AGB", "Alternate Gold/Black", "", "Richardson"}, {"AGN", "Troutdale Split Amber Gold/Navy", "", "Richardson"}, {"AGN", "Alternate Gold/Navy", "", "Richardson"}}
set theFilter to {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}

set theArrayofArrays to current application's NSArray's arrayWithArray:theListOfLists
set matchFound to theArrayofArrays's containsObject:theFilter --> true
if matchFound is true then return theFilter --> {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}

I don’t understand the logic in doing either of the above–perhaps “exact list match” doesn’t mean what I take it to mean?

Thank you! You understood perfectly.

I’m of the mind that using AppleScriptObj-C should only be used if it does something that can’t be done in plain AppleScript, or if it does it much faster.

There is a lot of overhead in calling Obj-C methods from AppleScript, that in a lot of times negates any speedup you think your gonna get.

In this case, I used script geek to run the script above versus my plain AppleScript below.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

property theListOfLists : missing value
property theFilter : {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}
property myListofLists : {{"A", "Upland Camo", "", "Richardson"}, {"ARB", "Arctic Blue", "PMS 2925 C", "ascolour"}, {"AZB", "Azure Blue", "PMS 2171 C", "Cotton Heritage"}, {"AB", "Aruba Blue", "", "Richardson"}, {"ABBI", "Split Aruba Blue/Birch", "", "Richardson"}, {"ABNKH", "Alternate Brown/Khaki", "", "Richardson"}, {"ACHHG", "Alternate Charcoal/Heather Grey", "", "Richardson"}, {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}, {"AGB", "Alternate Gold/Black", "", "Richardson"}, {"AGN", "Troutdale Split Amber Gold/Navy", "", "Richardson"}, {"AGN", "Alternate Gold/Navy", "", "Richardson"}}
property returnList : {}

set my theListOfLists to my myListofLists
repeat 4 times
	set my theListOfLists to my theListOfLists & my theListOfLists
end repeat
set returnList to {}
set cf to count theFilter
repeat with aList in my theListOfLists
	set flag to cf = (count aList)
	if flag then
		repeat with anItem in my theFilter
			if anItem is not in aList then
				set flag to false
				exit repeat
			end if
		end repeat
	end if
	if flag then
		set end of my returnList to aList
	end if
end repeat
set my theListOfLists to {}
return my returnList

Mine was almost twice as fast. And that’s not even been sped up by using script objects for the lists.
(** EDIT ** - I modified to use ‘my’ prefix, now it’s even faster)

It is surprisingly fast. I tried AppleScript first but want to check if a complete list is in the list of lists not by looping thru items and couldn’t find a solution without converting lists to strings which was a slow down. Mine was slower than your’s though by far. Although the columns are unique, I wanted to know how to solve determining if the list was in list of lists in case the columns info changed to have info found in another column. If there’s a way, someone will break the code changing the data if they can. At some point searching for a list inside list of lists stopped working in AppleScript early on from what I can tell if it ever worked. I’ve been playing with the ‘my’ prefix in loops lately and it does speed things up significantly. It’s crazy how a small tweak could do that.

The following is another suggestion using basic AppleScript. Depending on the size of the list of lists, this code may be sufficiently fast on its own, and it’s very concise (if that’s important):

set theListOfLists to {{"A", "Upland Camo", "", "Richardson"}, {"ARB", "Arctic Blue", "PMS 2925 C", "ascolour"}, {"AZB", "Azure Blue", "PMS 2171 C", "Cotton Heritage"}, {"AB", "Aruba Blue", "", "Richardson"}, {"ABBI", "Split Aruba Blue/Birch", "", "Richardson"}, {"ABNKH", "Alternate Brown/Khaki", "", "Richardson"}, {"ACHHG", "Alternate Charcoal/Heather Grey", "", "Richardson"}, {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}, {"AGB", "Alternate Gold/Black", "", "Richardson"}, {"AGN", "Troutdale Split Amber Gold/Navy", "", "Richardson"}, {"AGN", "Alternate Gold/Navy", "", "Richardson"}}
set theFilter to {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}

repeat with aList in theListOfLists
	if contents of aList is not equal to theFilter then set contents of aList to missing value
end repeat
set theListOfLists to lists of theListOfLists --> {{"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}}

BTW, @robertfern’s and my scripts operate differently, and this goes back to the meaning of “exact list match.”

1 Like

Nice.

I used your script to speed up mine.
I always forget you can compare lists directly
Now it’s super-duper faster

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

property theListOfLists : missing value
property theFilter : {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}
property myListofLists : {{"A", "Upland Camo", "", "Richardson"}, {"ARB", "Arctic Blue", "PMS 2925 C", "ascolour"}, {"AZB", "Azure Blue", "PMS 2171 C", "Cotton Heritage"}, {"AB", "Aruba Blue", "", "Richardson"}, {"ABBI", "Split Aruba Blue/Birch", "", "Richardson"}, {"ABNKH", "Alternate Brown/Khaki", "", "Richardson"}, {"ACHHG", "Alternate Charcoal/Heather Grey", "", "Richardson"}, {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}, {"AGB", "Alternate Gold/Black", "", "Richardson"}, {"AGN", "Troutdale Split Amber Gold/Navy", "", "Richardson"}, {"AGN", "Alternate Gold/Navy", "", "Richardson"}}
property returnList : {}

set my theListOfLists to my myListofLists
repeat 4 times -- make the list 16 times larger
	set my theListOfLists to my theListOfLists & my theListOfLists
end repeat
set returnList to {}
repeat with aList in my theListOfLists
	if contents of aList = theFilter then set end of my returnList to contents of aList
end repeat
set my theListOfLists to {}
return my returnList
2 Likes

Headslap…I can’t believe I missed that contents of would work matching a list!

You can use the NSPredicate NSComparisonPredicateModifierType:
NSDirectPredicateModifier
NSAllPredicateModifier
NSAnyPredicateModifer
ANY LHS IN RHS will return true if any of the elements of LHS are IN RHS
ALL LHS IN RHS will return true if all of the elements of LHS are IN RHS
ALL will be great as it will ignore the order vs a direct compare LHS == RHS

set theAnyPred to (current application's NSPredicate's predicateWithFormat:"ANY self IN %@", theFilter)
set theExactPred to (current application's NSPredicate's predicateWithFormat:"ALL self IN %@", theFilter)
set theArray to (current application's NSArray's arrayWithArray:theListOfLists)
set theAnyMatch to (theArray's filteredArrayUsingPredicate:theAnyPred) as list
set theExactMatch to (theArray's filteredArrayUsingPredicate:theExactPred) as list
2 Likes

Thank you for the great examples!

technomorph. I tested your suggestions but they didn’t work on my Ventura computer. A minor change in syntax fixed that.

Your suggestions perform two useful functions, and I’ve included working examples below FWIW. Thanks for the great suggestions.

use framework "Foundation"

set theListOfLists to {{"A", "Upland Camo", "", "Richardson"}, {"ARB", "Arctic Blue", "PMS 2925 C", "ascolour"}, {"AZB", "Azure Blue", "PMS 2171 C", "Cotton Heritage"}, {"AB", "Aruba Blue", "", "Richardson"}, {"ABBI", "Split Aruba Blue/Birch", "", "Richardson"}, {"ABNKH", "Alternate Brown/Khaki", "", "Richardson"}, {"ACHHG", "Alternate Charcoal/Heather Grey", "", "Richardson"}, {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}, {"AGB", "Alternate Gold/Black", "", "Richardson"}, {"AGN", "Troutdale Split Amber Gold/Navy", "", "Richardson"}, {"AGN", "Alternate Gold/Navy", "", "Richardson"}}

-- will  return any match in sublists of theListOfLists
set theFilter to {"aaa", "AGA", "bbb", "Arctic Blue"}
set theArrayofArrays to current application's NSArray's arrayWithArray:theListOfLists
set thePredicate to current application's NSPredicate's predicateWithFormat_("ANY self IN %@", theFilter)
set theListOfLists to (theArrayofArrays's filteredArrayUsingPredicate:thePredicate) as list
--> {{"ARB", "Arctic Blue", "PMS 2925 C", "ascolour"}, {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}}

-- must match all items in sublists of theListOfLists but does not enforce order of items in the sublists
set theFilter to {"Agave", "PMS 2178 C", "Cotton Heritage", "AGA"}
set theArrayofArrays to current application's NSArray's arrayWithArray:theListOfLists
set thePredicate to current application's NSPredicate's predicateWithFormat_("ALL self IN %@", theFilter)
set theListOfLists to (theArrayofArrays's filteredArrayUsingPredicate:thePredicate) as list
--> {{"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}}
1 Like

And yet, plain vanilla AppleScript is way faster.

Do you have “Script Geek”.

I’ve tested this with large lists

try these 2 script in “Script Geek”

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

property theListOfLists : missing value
property theFilter : {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}
property myListofLists : {{"A", "Upland Camo", "", "Richardson"}, {"ARB", "Arctic Blue", "PMS 2925 C", "ascolour"}, {"AZB", "Azure Blue", "PMS 2171 C", "Cotton Heritage"}, {"AB", "Aruba Blue", "", "Richardson"}, {"ABBI", "Split Aruba Blue/Birch", "", "Richardson"}, {"ABNKH", "Alternate Brown/Khaki", "", "Richardson"}, {"ACHHG", "Alternate Charcoal/Heather Grey", "", "Richardson"}, {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}, {"AGB", "Alternate Gold/Black", "", "Richardson"}, {"AGN", "Troutdale Split Amber Gold/Navy", "", "Richardson"}, {"AGN", "Alternate Gold/Navy", "", "Richardson"}}
property returnList : missing value

set my theListOfLists to my myListofLists
repeat 12 times -- this generates a list of 32768 items (LARGE)
	set my theListOfLists to my theListOfLists & my theListOfLists
end repeat
set theArrayofArrays to current application's NSArray's arrayWithArray:theListOfLists
set thePredicate to current application's NSPredicate's predicateWithFormat_("ALL self IN %@", theFilter)
set returnList to (theArrayofArrays's filteredArrayUsingPredicate:thePredicate) as list
return my returnList

and this one

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

property theListOfLists : missing value
property theFilter : {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}
property myListofLists : {{"A", "Upland Camo", "", "Richardson"}, {"ARB", "Arctic Blue", "PMS 2925 C", "ascolour"}, {"AZB", "Azure Blue", "PMS 2171 C", "Cotton Heritage"}, {"AB", "Aruba Blue", "", "Richardson"}, {"ABBI", "Split Aruba Blue/Birch", "", "Richardson"}, {"ABNKH", "Alternate Brown/Khaki", "", "Richardson"}, {"ACHHG", "Alternate Charcoal/Heather Grey", "", "Richardson"}, {"AGA", "Agave", "PMS 2178 C", "Cotton Heritage"}, {"AGB", "Alternate Gold/Black", "", "Richardson"}, {"AGN", "Troutdale Split Amber Gold/Navy", "", "Richardson"}, {"AGN", "Alternate Gold/Navy", "", "Richardson"}}
property returnList : missing value

set my theListOfLists to my myListofLists
repeat 12 times -- this generates a list of 32768 items (LARGE)
	set my theListOfLists to my theListOfLists & my theListOfLists
end repeat
set returnList to {}
set cf to count theFilter
repeat with aList in my theListOfLists
	set aList to contents of aList
	if aList = theFilter then
		set end of my returnList to aList
	end if
end repeat
set my theListOfLists to {}
return my returnList
1 Like

No. The repeat loop is the same in both scripts on purpose, so as to not bias the results.
The loop is the part that creates the large list for both scripts. Since they are the same, the difference in execution can be attributed to the search portion of each script. Which is what we are testing

I’m not sure why you are fixated on the list creation when it is not the part of the script that is relavent. Ergo it don’t matter it’s not optimized, it just matters that they are the same in each script, as the part we are interested in is the search functional part

I removed the line set my theListOfLists to my myListofLists and @robertfern’s runs 4 times faster. I dont think that’s needed or know why it’s there. However the repeat loop is definitely slowing it down. I changed 12x to 120x and blew up Script Debugger…oops

I ran @Fredrik71 version and switched repeat to 120 times and it is nuts fast and doesn’t blow up Script Debugger. I always enjoy maxing out the speedometer… The addObject is something I am going to explore in my repeat loops churning through CSVs and large reads/loops. @Fredrik totally unrelated, but how are you parsing the data into lists using ASObjc.? The loops always slow me down.

Regardless if just looking at repeat/filter vs ASObjc the ASObjc is faster as long as the voodoo predicates are perfect but often getting the exact terminology is tough for me. Haven’t found a cheat sheet or anything to help with it.

1 Like

The loop takes a list of 9 sub lists and doubles it, and doubles again 12-times to make a very large list so the rest of the script has a large list to search in. I’m not sure why no one can grasp this?

If both scripts don’t have the same list creation portions than the speed tests are incorrect and irrelevant.

Robert. FWIW, the two scripts you are comparing perform somewhat different tasks. Your script returns sublists in theListOfLists that are identical to theFilter list. My revision of technomorph’s script returns sublists in theListOfLists that contain the same items as the items in theFilter list (without regard to their order).

BTW, I agree that comparing and manipulating lists with basic AppleScript made faster with implicit and explicit script objects tend to be faster than ASObjC. I use both approaches depending on which best accomplishes the task at hand.

1 Like

I totally grasp it and your AppleScript solution is a really fast method of looping using my. It bewilders me how it works and makes me want to slip in my throughout the many scripts I have in use.

The original question which was using predicates for an exact match of a list in list of lists.

Just when I think ONLY use ASObjc when AppleScript is too slow, @Fredrik71 shows off with theListOfLists’s addObject:myListofLists and it just takes the speed of things to ridiculous.

@peavine can you elaborate on the implicit and explicit script objects?

A long time ago, someone in this forum discovered that using a script object in a handler made working with lists significantly faster. Nigel once explained the reason for this, but I can’t remember all the details.

Although you don’t see it, there is also a top-level script object that implements the overall script you are working on. The use of the word “my” references this implicit, top-level script object and makes working with lists faster.

You can also make lists faster by use of a reference to operator. This is documented here in the AppleScript Language Guide, although people seldom use it.