It’s too bad you’re dealing with a list of records (where kai’s technique is awesome), because I found a “one line” solution for a list of lists such as the first example you gave. But 1) it’s ugly, 2) it’s a fake one-liner because it’s really several lines converted to one by a “run script” macro-like beast, and 3) it (at least in its current version) doesn’t take into account the possibility of non-unique values in the list. On the other hand, 1) it works, 2) it doesn’t use repeat loops (using a text item delimiter trick instead), and 3) it finds the desired value about 40 times faster (on the average) than a conventional repeat loop:
set the_list to {{"Me", "Red"}, {"You", "Blue"}, {"They", "Green"}}
run script ("on run {the_list, item_a}" & return & "set text item delimiters to \"```\"" & return & "set the_list_as_string to the_list as string" & return & "set text item delimiters to \"\"" & return & "text 1 thru ((offset of \"```\" in (text ((offset of (item_a & \"```\") in the_list_as_string) + (count item_a) + 3) thru -1 of the_list_as_string)) - 1) of (text ((offset of (item_a & \"```\") in the_list_as_string) + (count item_a) + 3) thru -1 of the_list_as_string)" & return & "end run") with parameters {the_list, "They"} -- > "Green" !!
I compared it’s speed vs repeat looping by creating the following list of 2000 lists and searching for the 1000th list of paired values:
set the_list to {}
repeat with i from 1 to 2000
set end of the_list to {"A" & (i as string), "B" & (i as string)}
end repeat
-- > {{"A1", "B1"}, {"A2", "B2"}, ...}
---------------------------------------------------------------------------------------------------------------
repeat with i from 1 to length of the_list
if item 1 of (item i of the_list) is "A1000" then
set the_result to item 2 of (item i of the_list)
exit repeat
end if
end repeat
-- > the_result = "B1000", execution time = 17.977 seconds
---------------------------------------------------------------------------------------------------------------
set the_result to run script ("on run {the_list, item_a}" & return & "set text item delimiters to \"```\"" & return & "set the_list_as_string to the_list as string" & return & "set text item delimiters to \"\"" & return & "text 1 thru ((offset of \"```\" in (text ((offset of (item_a & \"```\") in the_list_as_string) + (count item_a) + 3) thru -1 of the_list_as_string)) - 1) of (text ((offset of (item_a & \"```\") in the_list_as_string) + (count item_a) + 3) thru -1 of the_list_as_string)" & return & "end run") with parameters {the_list, "A1000"}
-- > the_result = "B1000", execution time = 0.432 seconds (41.6 times faster)
To be fair, the conventional repeat loop can be made to execute even faster than the “o------------n-------------e liner” with the “list as reference” or “list as property” tricks:
set the_list to {}
set the_list_as_reference to a reference to the_list
repeat with i from 1 to 2000
set end of the_list to {"A" & (i as string), "B" & (i as string)}
end repeat
-- > {{"A1", "B1"}, {"A2", "B2"}, ...}
---------------------------------------------------------------------------------------------------------------
repeat with i from 1 to length of the_list
if item 1 of (item i of the_list) is "A1000" then
set the_result to item 2 of (item i of the_list)
exit repeat
end if
end repeat
-- > Conventional repeat loop: the_result = "B1000", execution time = 16.433 seconds
---------------------------------------------------------------------------------------------------------------
repeat with i from 1 to length of the_list
if item 1 of (item i of the_list_as_reference) is "A1000" then
set the_result to item 2 of (item i of the_list_as_reference)
exit repeat
end if
end repeat
-- > List as reference: the_result = "B1000", execution time = 0.167 seconds
---------------------------------------------------------------------------------------------------------------
repeat with i from 1 to length of the_list
if item 1 of (item i of my the_list) is "A1000" then
set the_result to item 2 of (item i of my the_list)
exit repeat
end if
end repeat
-- > List as property: the_result = "B1000", execution time = 0.102 seconds
---------------------------------------------------------------------------------------------------------------
set the_result to run script ("on run {the_list, item_a}" & return & "set text item delimiters to \"```\"" & return & "set the_list_as_string to the_list as string" & return & "set text item delimiters to \"\"" & return & "text 1 thru ((offset of \"```\" in (text ((offset of (item_a & \"```\") in the_list_as_string) + (count item_a) + 3) thru -1 of the_list_as_string)) - 1) of (text ((offset of (item_a & \"```\") in the_list_as_string) + (count item_a) + 3) thru -1 of the_list_as_string)" & return & "end run") with parameters {the_list, "A1000"}
-- > Run script: the_result = "B1000", execution time = 0.419 seconds
But don’t try to mix those speed-enhancing list tricks with “run script” – > no matter what permutations of properties or references I tried, it just keeps on breaking. It’s as if “list as property”, “list as reference”, and “run script” are dipping into the same speed-enhancing Koolaid, and only one is allowed to drink at a given time! So it’s either multi-line + list trick, or “one” line + run script!!
But given that a ‘run script’ multi-line one-liner is acceptable, it’s possible to shorten it a little. Like bmose, I’ve assumed that the idea is to retrieve the second item in a sublist when you’re given the first. :
set the_list to {{"Me", "Red"}, {"You", "Blue"}, {"They", "Green"}}
run script ("on run {the_list, item_a}" & return & "set text item delimiters to return" & return & "set the_list_as_string to the_list as string" & return & "set text item delimiters to item_a" & return & "paragraph 2 of text item 2 of the_list_as_string" & return & "end run") with parameters {the_list, "They"} -- > "Green" !!
As a matter of technical interest, the failure to reset the TIDs in the ‘run script’ doesn’t affect the state of Script Editor’s TIDs at all. To make the TIDs case-insensitive, the ‘as string’ should be changed to ‘as Unicode text’.
Just to be clever [*], this returns the other item in the list, given either of them:
set the_list to {{"Me", "Red"}, {"You", "Blue"}, {"They", "Green"}}
run script ("on run {the_list, item_a}" & return & "set text item delimiters to return" & return & "set the_list_as_string to the_list as string" & return & "set text item delimiters to item_a" & return & "set p to (count paragraphs of text item 1 of the_list_as_string) mod 2" & return & "paragraph (p * 4 - 2) of text item (p + 1) of the_list_as_string" & return & "end run") with parameters {the_list, "Blue"} -- > "You" !!
[*] Really clever people don’t get hooked on one-liners, of course.
Which began life, I’m sure, as something like this:
script O
on run {the_list, item_a}
set text item delimiters to return
set the_list_as_string to the_list as string
set text item delimiters to item_a
set p to (count paragraphs of text item 1 of the_list_as_string) mod 2
paragraph (p * 4 - 2) of text item (p + 1) of the_list_as_string
end run
end script
set the_list to {{"Me", "Red"}, {"You", "Blue"}, {"They", "Green"}}
run script O with parameters {the_list, "They"} --> "Green"
Is there inherently anything “better” about on-liners versus “the long way?”
I prefer to keep my own code done “the long way” so it’s easy to scan and decipher when I have to revisit it 18 months later, since it’s easy to comment it to death. But I can’t help wonder if I’m taking a huge speed hit or something.
But let me say, better or not, watching Nigel take dozens of lines of code and reduce it to mere dozens of characters and a single line is like watching David Copperfield make the Statue of Liberty disappear. I feel so…unworthy.
I feel the same way Calvin they smash my code down all the time =)
Now as for advantages… it really varies. There are times where one liners are more advantageous (always string shell commands into one step if you can) than long drawn out code. The question though is; is that speed worth it? After all who says you’re goint to easily dechiper it in a week… in a month… or the poor sap who has to change it after you’ve gone.
I personally when playing with peoples scripts here typically try to do things as short as possible… it’s my own personal challenge to myself… though I will admit perhaps not the best for people new to AppleScript.
In a work environment I tend to find myself fighting an internal battle… I love to read comments, but hate to write them. My failure to do so often leaves me writing short, but complex code… regardless of my medium.
I personally have no objection to even the most grotesque one-liners, so long as they’re presented in the context of moderately experienced scripters having fun amongst themselves. They’re entertaining and good for creativity and imagination. They’re also occasionally the best and neatest way to handle particular tasks. I do have a problem when people start to believe that one-liners are inherently better or faster than the more conventionally arranged, multi-line equivalents. They’re not. (Necessarily.) And they’re complete non-starters when offered on AppleScript fora to obvious beginners who want to understand the answers to their scripting difficulties.
kai’s scripts are brilliant because he is and because he has a thorough understanding of what commands and processes to use to produce effective and efficient scripts. He has (in my view) a rather unfortunate coding style, but it does sort of go with the way he thinks. But the style isn’t the substance. Anyone wanting to emulate him should (again, in my view) emulate his understanding rather than just his propensity for cramming a lot onto one line.
set the_list to {{"Me", "Red"}, {"You", "Blue"}, {"They", "Green"}}
run script ("on run {the_list, item_a}" & return & "set text item delimiters to return" & return & "set the_list_as_string to the_list as string" & return & "set text item delimiters to item_a" & return & "paragraph 2 of text item 2 of the_list_as_string" & return & "end run") with parameters {the_list, "They"}
“set text item delimiters to return” – > “set text item delimiters to item_a”
Beautiful, Nigel. Using “item_a” as the text item delimiter after creating paragraphs of the list items is ethereal.
This code is beautiful and efficient enough to start using on a regular basis. I believe a list of lists has advantages (at least in AppleScript) over a list of records for managing record-like data. And this effective means of extracting items may be just the tool that was previously lacking.
Thanks for the compliments, bmose. But of course it’s the algorithm rather than the implementation that’s “beautiful and efficient”. When the code’s rerendered as a pre-compiled handler, it’s about 155 times as fast (on my machine). The method itself is only really useful when both items in each sublist are some form of text, and the class of the result is governed by the coercion written into the script.
on get_second_string_from_sublist(the_list, item_a)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to return
set the_list_as_string to the_list as string
set AppleScript's text item delimiters to item_a
set item_b to paragraph 2 of text item 2 of the_list_as_string
set AppleScript's text item delimiters to astid
return item_b
end get_second_string_from_sublist
set the_list to {{"Me", "Red"}, {"You", "Blue"}, {"They", "Green"}}
get_second_string_from_sublist(the_list, "You")
Your welcome. Your approach is really along the lines of what I was looking for in the first place. For those who would use this handler, I would suggest one final modification so as not to end up with altered text item delimiters if the handler were to encounter an error during execution:
on get_second_string_from_sublist(the_list, first_string_in_sublist)
try
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to return
set the_list_as_string to the_list as string
set AppleScript's text item delimiters to first_string_in_sublist
set second_string_in_sublist to paragraph 2 of text item 2 of the_list_as_string
set AppleScript's text item delimiters to astid
return second_string_in_sublist
on error error_message
set AppleScript's text item delimiters to astid
display dialog "The following error was encountered during execution of \"get_second_string_from_sublist\":" & return & return & error_message
end try
end get_second_string_from_sublist