Lists within lists & one line fun

Okay another can this be done in one line question. Assuming the following list

set some_list to {{"Me", "Red"}, {"You", "Blue"}}

My question is in a single line if I know the value “Me” how would I return “Red” ? Basically something along the lines of this…

set x to item 2 of some_list's item whose item 1 contains "Me"

I’ve tried multiple forms of this including trying to tell the list itself to do some trickery like this:

tell some_list to tell (it's item whose item 1 contains "Me") to set x to it's item 2

but have been unable to find the answer if it’s there.

Not sure what you’re doing, James, but the normal way to do that is with a record:

set myRec to {_me:"Red", You:"Blue"}
_me of myRec --> "Red"

Otherwise I think you’re stuck with;


set L to {{"Me", "Red"}, {"You", "Blue"}}
repeat with p in L
	if item 1 of p is "You" then
		return item 2 of p
		exit repeat
	end if
end repeat

Yep, but assume you have a list of records… If you know half the pair of a record in a list how do you find the other pair in a single line?

So if you had

set some_list to {{w:"Me", c:"Red"}, {w:"You", c:"Blue"}}

How would you find “Red” when you know “Me”? I know this will work

repeat with aRec in some_list
	if w of aRec = "Me" then set x to c of aRec
end repeat

but I want to do something more along the lines of this if possible…

set x to c of some_list's record whose w contains "Me"

James:

Check out the explanation(s) by kai in this thread.

I miss myndcraft…

Thanks for the link Craig!

Well now that Im not modding/helping what have you anymore you all are welcome to change it back to myndcraft :smiley:

James,

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)

bmose

That is the longest stretch to a one-liner I’ve ever seen, bmose. :lol:

holy christ… okay see you fellas in a month after i’ve sorted through that “one liner” … and apdated it for everything LOL

James,

Be careful what you ask for!! :slight_smile:

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!!

bmose

It’s also a cheat! :wink:

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. :wink:

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"

Bonus point for Nigel. :cool:

Agree (although I confess to having done it :rolleyes:) and when Kai posts one of his, I enjoy untangling them :slight_smile:

As a lurker, I just have an odd question:

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. :wink:

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. :wink:


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.

bmose

Thanks for the compliments, bmose. :slight_smile: 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")

Nigel,

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

bmose