How to compare lists that contain sublists
Comparing two lists is a simple task, well documented in the AppleScript Language Guide. However, the simplicity breaks down when the lists contain sublists, and the Language Guide doesn’t tell you about this special case. Here, we explain how to compare lists of lists without error. In essence, one must remember that two lists containing sublists can properly be compared only if the outer list brackets are represented on both sides of the comparison operator. Let’s see what this means.
First, ordinary lists: The Language Guide explains that the Equal and Is Not Equal To operators report that two lists are equal if each item in the list to the left of the operator evaluates to the same value as the item in the corresponding position in the list to the right of the operator. The example given is:
{(1 + 1), (4 > 3)} is equal to {2, true} --> true
The Guide explains that the Contains and Is Contained By operators report that one list is in another if it is a sublist of the other; that is, if its items not only evaluate to the same values but appear in the same order in both lists. An example is:
{"this", "is", 1 + 1, "cool"} contains {"is", 2} --> true
If you want to know whether a list contains a single item, you can optionally omit the list brackets, because AppleScript will coerce the single item to a list containing that item before making the comparison. For example,
{"this", "is", 1 + 1, "cool"} contains {2} --> true
is equivalent to:
{"this", "is", 1 + 1, "cool"} contains 2 --> true
The latter is the more natural usage to English speakers, but, as we shall see, this will lead us into a fatal error when we encounter lists containing sublists.
What happens when you apply the comparison rules to lists of lists? Take the first example, above, and change the first item in the list on the left, “(1 + 1)”, to a sublist, “{1 + 1}”, so that the comparison reads as follows:
{{1 + 1}, (4 > 3)} is equal to {2, true} --> false
The comparison now returns false. To make it true, the first item in the list on the right must also be a list, like so:
{{1 + 1}, (4 > 3)} is equal to {{2}, true} --> true
This much may seem obvious, since {1 + 1} evaluates to {2}, not to 2. AppleScript is unable to coerce items in multi-item lists the way it can coerce between single items and single-item lists, as is more or less documented in the Language Guide.
The surprise comes with the containment comparison operators. You would expect this comparison to be false, and it is, for the reason described above:
{{1 + 1}, (4 > 3)} contains 2 --> false
But wouldn’t you expect this comparison to return true?
{{1 + 1}, (4 > 3)} contains {2} --> false
Well, it doesn’t. Instead, you have to use double list brackets on the right, like so:
{{1 + 1}, (4 > 3)} contains {{2}} --> true
Why? It appears that, as a general rule, AppleScript requires the outer list brackets to be represented on both sides of the operator when there is a sublist within the lists, even when the operator is Contains. AppleScript will not coerce {2} to {{2}}. Thus,
{{1 + 1}, (4 > 3)} contains {2} --> false
is not true because the first item in the list on the left is a sublist within a list, whereas the matter to the right of the operator is a simple list, namely, the number 2 in a list, and it cannot be coerced to a sublist within a list. The sense of this is more obvious when you use the Equals operator, where:
{{1 + 1}} is equal to {2} --> false
returns false, as an English speaker would expect, while:
{{1 + 1}} is equal to {{2}} --> true
returns true as expected. The confusion is caused solely by the fact that the everyday English term, “contains”, implies that the matter to the right of the operator should be written as a subset of the matter to the left, that is, without the outer list brackets, while the same AppleScript operator counter-intuitively requires the outer list brackets on both sides of the comparison with both Equal and Contains. This is a case where the much-vaunted “plain English” quality of AppleScript breaks down.
Just to make this all the more puzzling, note that using the “as list” coercion cannot be substituted for the outer list brackets. If the last example is written in this seemingly equivalent form, it does not return true:
{{1 + 1}} is equal to ({2} as list) --> false
Apparently, AppleScript sees that {2} is already a list, so the “as list” coercion does nothing. However, this obviously does return true:
{{1 + 1}} is equal to {2 as list} --> true
You understand why, now, don’t you?
All of this has real-world consequences when you are trying to program comparisons using variables, where the variables might involve lists of lists. I discovered the principles discussed here while trying to compare colors of style runs in the Style application by Marco Piovanelli, for my Script2HTML script application. Style returns certain colors as constants, such as red, green and blue. However, it returns colors in the rest of the spectrum as RGB color values, which are lists of three integers showing the intensities of the red, green and blue components of the color, such as {13943, 19384, 64953}. You can’t know in advance whether your script will encounter a document containing colors in the one form or the other. The following code snippet works correctly if the text in document 1 happens to use colors for which Style supplies constants and you want to ascertain whether the document contains one of them:
tell document 1 of application "Style"
set theColors to color of every style run -->{red, black}
set myColor to red
theColors contains myColor --> true
end tell
But the same script fails miserably if the colors of interest are represented as RGB color values ”and I can tell you from personal experience that the syntax error is very hard to find if you aren’t aware of this gotcha:
tell document 1 of application "Style"
set theColors to color of every style run -->{{27202, 56683, 56143}, black}
set myColor to {27202, 56683, 56143}
theColors contains myColor --> false
end tell
Adding the outer list brackets to the right of the comparison operator makes it work correctly:
tell document 1 of application "Style"
set theColors to color of every style run -->{{27202, 56683, 56143}, black}
set myColor to {27202, 56683, 56143}
theColors contains {myColor} --> true
end tell
The outer brackets also work with the constant, so we have a general solution:
tell document 1 of application "Style"
set theColors to color of every style run -->{{27202, 56683, 56143}, black}
set myColor to black
theColors contains {myColor} --> true
end tell
The moral? To be safe with all list comparisons, always make sure the outer brackets are represented on both sides of the operator just in case your list may include a sublist or two.