Lists within lists & one line fun

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

This handler, is so sweet, when you have “indexes” with unique items.
I don’t know how fast it is as it stands below, but it saves me the trouble of having to maintain the inverted table.
So it saves time, and room for errors! :slight_smile: It is the handler in post #20, reworked from post #12 slightly reworked.


(* -- http://macscripter.net/viewtopic.php?pid=155040#p155040
set the_list to {{"Me", "Red"}, {"You", "Blue"}, {"They", "Green"}}

set rests to getSingelton(the_list, "They") --> "Green"

set rests to getSingelton(the_list, "Blue") --> "You"

set rests to getSingelton(the_list, "NO") --> null

set rests to getSingelton(the_list, "Red") --> "Me"

set rests to getSingelton(the_list, "you") --> "Blue"
*)

to getSingelton(the_list, item_a)
	local p, q, tids
	” Foundation by Nigel Garvey
	set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, return}
	
	set the_list_as_string to the_list as text
	set AppleScript's text item delimiters to item_a
	try
		if text item 1 of the_list_as_string = "" then
			set q to paragraph 2 of text item 2 of the_list_as_string
		else
			set p to (count paragraphs of text item 1 of the_list_as_string) mod 2
			set q to paragraph (p * 4 - 2) of text item (p + 1) of the_list_as_string
		end if
		set AppleScript's text item delimiters to tids
	on error
		set AppleScript's text item delimiters to tids
		return null
	end try
	if q is item 1 of last item of the_list then
		if item_a is item 2 of last item of the_list then
			return q
		else
			return null
		end if
	else
		return q
	end if
end getSingelton


Hi!

I added some code to make it a tad more robust, when the item sought isn’t in the list.
How fast that code I added is is questionable.

Hello

I found a small drawback in the given handler.
If a second term embed a return or a linefeed, only the beginning of the string will be returned.

This is why I added some instructions.



on get_second_string_from_sublist(the_list, first_string_in_sublist)
	set fakeReturn to character id 0
	set fakeLineFeed to character id 1
	set fakeDelim to character id 3
	try
		set astid to AppleScript's text item delimiters
# Additions
		set AppleScript's text item delimiters to fakeDelim
		set t to the_list as string
		set AppleScript's text item delimiters to return
		set l to text items of t
		set AppleScript's text item delimiters to fakeReturn
		set t to l as text
		set AppleScript's text item delimiters to linefeed
		set l to text items of t
		set AppleScript's text item delimiters to fakeLineFeed
		set t to l as text
		set AppleScript's text item delimiters to fakeDelim
		set the_list to text items of t
# Back to original code
		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
# additions
		set AppleScript's text item delimiters to fakeReturn
		set l to text items of second_string_in_sublist
		set AppleScript's text item delimiters to return
		set second_string_in_sublist to l as text
		set AppleScript's text item delimiters to fakeLineFeed
		set l to text items of second_string_in_sublist
		set AppleScript's text item delimiters to linefeed
		set second_string_in_sublist to l as text
		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

--=====

set the_list to {{"Me", "Red"}, {"You", "Blue
Monk"}, {"They", "Green"}}
get_second_string_from_sublist(the_list, "You")

Yvan KOENIG (VALLAURIS, France) lundi 3 septembre 2012 17:39:14

Of course, you may prefer this alternate scheme :


(*
Borrowed from 
http://macscripter.net/viewtopic.php?id=39219
Code using Shane STANLEY's ASObjC Runner
*)
set ProductList to {¬
	{"Product1,1", "Product Name 1"}, ¬
	{"Product1,2", "Product Name 
	2"}, ¬
	{"Product2,1", "Product Name 3"}, ¬
	{"Product3,1", "Product Name 4 (Extra)"}}
get_second_string_from_sublist(ProductList, "Product1,2")

on get_second_string_from_sublist(the_list, first_string_in_sublist)
	local newLists, theRecords, theResult
	tell application id "au.com.myriad-com.ASObjC-Runner" -- ASObjC Runner.app
		set newLists to modify list the_list with cols to rows
		(* > {{"Product1,1", "Product1,2", "Product2,1", "Product3,1"}, {"Product Name 1", "Product Name 
	2", "Product Name 3", "Product Name 4 (Extra)"}} *)
		set theRecords to link values (item 2 of newLists) with labels (item 1 of newLists)
		(* > {|product1,2|:"Product Name 
	2", |product3,1|:"Product Name 4 (Extra)", |product2,1|:"Product Name 3", |product1,1|:"Product Name 1"} *)
		set theResult to value for label first_string_in_sublist in records theRecords
		(* >{"Product Name 
2"} *)
	end tell
	return theResult
end get_second_string_from_sublist

Yvan KOENIG (VALLAURIS, France) lundi 3 septembre 2012 18:15:40

Hello Yvan!

I have no doubt that your second example is blazingly fast :slight_smile:

The handler I found is ideal for rather small quantities of data, and the beauty of it, is that it is so simple, -you can really just use it, and forget all about it, whatever you put into it as parameter, is the key, and it will return the value.

This means that I can say goodbye to creating lists with the inverse arrangement for lookup, and yes, I have!

I don’t know if there are for any good reason really, but I like to keep things plain, when I can. Your first handler, is really something to consider, when I don’t know what kind of text I have converted to a list I believe. As the data I will get, is mostly properties from applications, I don’t have to consider that for the moment.

This handler, and the whole approach, is very lightweight, and I like that! :slight_smile:

And I am not really processing stuff here, just keeping data ordered. Very simple, pairs with values, that keeps to entities related to each other, and for that simple thing, I find Nigel’s approach superior (Black Magick).

I am not talking about huge quantities of data either, 100 items at max, I should have stated that, and I haven’t even added a script object at the time being, but, the programmer time, is what concerned me for starters, as the approach, effectively halves many things regarding lists and relations between items in two!

This handler, is on the top of my shelf, standing only beside filterer by Matt Neuburg! :smiley:

Hello

I posted the script using ASObjC Runner because it make no difference if a second item may embed a return of if it doesn’t

Some days ago, I posted speed comparisons and if I remember well, a script using tids was faster than the ASObjC Runner one.

I pointed the possible embedded return character because I had to deal with such datas some times ago.

Yvan KOENIG (VALLAURIS, France) lundi 3 septembre 2012 20:50:59

Absolutely Yvan!

It is important to get different solutions, as what may not be the best in one context may certainly be it in another.

If I were to go though thousands of items, I’d go for the Asobj-C Runner solution, which is a fine app indeed!

The nice thing about this handler, is that for it, it doesn’t concern itself about the first or second string, it just delivers the opposite item, in that “pair”.

They beaty of that is that it cuts work in half! No more maintaining inverse lookup tables! :slight_smile: I can feed it a string from its item 1 and I get the corresponding item 2 back, and vice versa! :slight_smile: I can insert at one place, and delete in one place, and there may still be other lists (tables) to update, but no more inverse lookup tables!

One handler fits all! :smiley:

Hi.
I added some more code for the case that the first element held the key, as then the first text item would be empty and so on. For the handler in post #21. :slight_smile:

Yvan’s second solution is the best. I never used and never liked the code because it’s very, very buggy.

For instance

set the_list to {{"yours", "Red"}, {"You", "Blue"}, {"They", "Green"}}

When you want ‘you’ it’s return “Red” and not “Blue”

That’s why I’m using a grep solution.

set the_list to {{"Yours", "undefined"}, {"You", "Blue"}, {"They", "Green"}}
get_second_string_from_sublist(the_list, "You")
on get_second_string_from_sublist(the_list, first_string_in_sublist)
	set AppleScript's text item delimiters to linefeed
	set the_list_as_string to the_list as string
	set AppleScript's text item delimiters to ""
	try
		return paragraph 2 of (do shell script "/bin/echo -n" & quoted form of the_list_as_string & " | grep --after-context=1 ^" & quoted form of first_string_in_sublist & "$")
	on error
		return missing value
	end try
end get_second_string_from_sublist

edit or even a better solution using awk. Code above fails when you give up a key that is in a record before as it’s value; returns the key of the next record. Solved with awk


set the_list to {{"Yours", "undefined"}, {"You", "Blue"}, {"They", "Green"}}
get_second_string_from_sublist(the_list, "You")
on get_second_string_from_sublist(the_list, first_string_in_sublist)
	set AppleScript's text item delimiters to linefeed
	set the_list_as_string to the_list as string
	set AppleScript's text item delimiters to ""
	return do shell script "/bin/echo -n" & quoted form of the_list_as_string & " | awk '{if ($0 == \"" & first_string_in_sublist & "\") {getline;print $0;}  else {getline;}}'"
end get_second_string_from_sublist

Hi!

That must have been an earlier version, if it was the script in post #21, because I just tested it, and I got “Blue” back, when I fed it "You".

The solution as it stand is bug free! I have taken care of the two edge cases, Nigels Magick holds for whats in between. This is said in the context that you don’t feed it a list with return or linefeed in it. (Agnostic paragraphs with regards to line endings). Though I trust it will behave totally all right, if each list entry consists of several words, and the items consist of text. And the list must contain unique items.

And then you have the cheapest sweetest little key - value returner through all times. You can’t really call this a dictionary, but you get the functionality, and even more, it works both ways! :smiley:

Here is your code and doesn’t work. You should have test it :slight_smile:

set the_list to {{"Yours", "Red"}, {"You", "Blue"}, {"They", "Green"}}


getSingelton(the_list, "You") --> "Red" while should be Blue


to getSingelton(the_list, item_a)
	local p, q, tids
	-- Foundation by Nigel Garvey
	set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, return}
	
	set the_list_as_string to the_list as text
	set AppleScript's text item delimiters to item_a
	try
		if text item 1 of the_list_as_string = "" then
			set q to paragraph 2 of text item 2 of the_list_as_string
		else
			set p to (count paragraphs of text item 1 of the_list_as_string) mod 2
			set q to paragraph (p * 4 - 2) of text item (p + 1) of the_list_as_string
		end if
		set AppleScript's text item delimiters to tids
	on error
		set AppleScript's text item delimiters to tids
		return null
	end try
	if q is item 1 of last item of the_list then
		if item_a is item 2 of last item of the_list then
			return q
		else
			return null
		end if
	else
		return q
	end if
end getSingelton

This returns “Blue”

set the_list to {{"Yours", "undefined"}, {"You", "Blue"}, {"They", "Green"}}
get_second_string_from_sublist(the_list, "You")
on get_second_string_from_sublist(the_list, first_string_in_sublist)
	set AppleScript's text item delimiters to linefeed
	set the_list_as_string to the_list as string
	set AppleScript's text item delimiters to ""
	return do shell script "/bin/echo -n" & quoted form of the_list_as_string & " | awk '{if ($0 == \"" & first_string_in_sublist & "\") {getline;print $0;}  else {getline;}}'"
end get_second_string_from_sublist

edit:

Modified version of Nigel’s solution, now with an exact key match instead of contains. Still containing the same bugs as withmy Grep solution. When the given key is also in the values it will return the key of the next record.


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 return & item_a & return
	set item_b to paragraph 1 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 {{"Yours", "Red"}, {"You", "Blue"}, {"They", "Green"}}
get_second_string_from_sublist(the_list, "You")

Hello!

Yes, I see that the code can’t make a difference of “Yours” and “You”.

That is rather contrived for my usage. But thanks for showing me the limitiations of this approach.

This is no problem with keys and values of equal length.

I am going to use pairs of values as items anyway. And I would have had when I see this result!

It is really great when keeping relations, and to assert that relations are unique, that is the approach to take anyway.

With single valued items I guess this can be avoided by using a rigid length of the items, Using such a trick so that keys of shorter length are padded with some character. (Spaces works great!) Then the bug you have revealed will be non-existant! :slight_smile: It is not really a bug, it is more of a ramification!

I’m not just being stubborn here, this script, removes the need for the reverse lookup tables, and having to use two handlers to get the opposite value and so on. And I think a regexp, could easily fail the same way, if you don’t instruct it to use word boundaries.

A pad script/handler for such cases:

script pad
	property whsp : "                                       " ” 40 spaces or so.
	to spaces(astr)
		return text 1 thru 40 of (astr & my whsp)
	end spaces
end script

set mnm to "mcusr"

set nwnm to pad's spaces(mnm)
--> "mcusr                                   "

(The spaces disappears between the tags)

And whitespace can be removed swiftly:


-- http://macscripter.net/viewtopic.php?pid=154062#p154062
	to stripwhSpace from astring
		-- 09/08/12 Tested!
		script o
			property aList : missing value
			
		end script
		set oldDelims to AppleScript's text item delimiters
		set AppleScript's text item delimiters to {" ", "	"} -- space, tab 
		
		set o's aList to astring's text items
		
		repeat with i from 1 to (get count o's aList)
			if item i of o's aList is "" then set item i of o's aList to missing value
		end repeat
		set astring to o's aList's text as text
		set AppleScript's text item delimiters to oldDelims
		return astring
	end stripwhSpace

Just to clear up any confusion here, the given in all the original 2007 posts was that each sub-list contained two one-word strings. None of the scripts was intended to handle text containing line endings and any failure to do so can’t justifiably be described as a “drawback”.

My “other item” script worked perfectly at the time it was written ” as I’ve just confirmed on my Tiger system. But it now has a problem if the search term is the first word in the first list. This is because empty texts are now considered to have zero paragraphs instead of the one which was the case back them.

DJ is perfectly right that in the TID approach, the search term should have a line ending at each end to distinguish it from other words which may contain it. But then so should the list as string!