Lists within lists & one line fun

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!

This is going to to work perfectly!

It is by now no confusion at all. For the tid approach to work as expected, All items should be unique, both in both parts of the pair, that is, one value should just appear in one item in a pair, once in a list. And, the values, should have equal length for it to work properly, like most indexing software use anyway internally. However, each value can consist of several space delimited words.

Thanks for a great handler Nigel! :slight_smile: This is really a timesaver!

Here’s a reworking of the “other item” process:

on get_other_list_item_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 return & the_list & return
	set AppleScript's text item delimiters to return & item_a & return
	if (the_list_as_string contains result) then
		set p to (count paragraphs of text item 1 of the_list_as_string)
		if (p is 0) then set p to 1 -- Catch modern paragraph count for empty text.
		set p to p mod 2
		set otherItem to paragraph (p * 2 - 1) of text item (p + 1) of the_list_as_string
		
		set AppleScript's text item delimiters to astid
		
		return otherItem
	else
		return null
	end if
end get_other_list_item_from_sublist

set the_list to {{"Yours", "Red"}, {"You", ""}, {"They", "Green"}}
get_other_list_item_from_sublist(the_list, "Pink")

Nigel, I know we should stick with the wishes of the TS and not our own wishes. Out of curiosity, do you return the first key when the given key matches a value before it matches a key on purpose?

like this (the keys are unique):

set the_list to {{"Yours", "Red"}, {"You", "Blue"}, {"They", "Green"}, {"Red", "Black"}}
get_other_list_item_from_sublist(the_list, "Red") --returns "Yours" while I expect "Black"

Hello!

This is just brilliant Nigel!

( I’m lazy at times, but now, I’ll sit down and figure it all out!)

You have no idea what kind of mess this handler can save you from, or maybe you have, if you ever have had to maintain relational tables with Apple Script,taking care of the updating of them, by the insertion and deletion of elements, and so on. :slight_smile:

@ D J Bazzie Wazzie:
This is a constraint, I tried to express earlier, that a value/key can only appear once, in one sublist, as every item in that list is a key, like one half in a 1-1 relation.

Nigel’s handler works great within those limitations. It is also fun to study it! :slight_smile:

Hi, DJBW.

As McUsr’s noted, the assumption is that the search term only occurs once in the entire list of lists. My handler returns the other item from the list in which it occurs, which was originally just an aside to the fun discussion about getting the second item from the relevant list in one line of code. McUsr’s found the idea useful, so I’ve updated and improved it. Given unique first items, a straightforward handler to get an associated second item will have no problem with the situation you describe.

The altertnative to this handler, is to relate lists by indexes, and if you then need to retain a two way street of a relation, so that given one, you need to go to the other, you will have to code a whole lot.

You really can’t maintain such a relation, without updating the inverse_table, as you insert, delete and change items. (Change being the easy one.) In order to keep such relations, all the indexes, (as integers) will need updating, and that is quite some job, I can just mention the reindexing, that this handler so brilliantly bypasses!

I looked over some code earlier today, and I realize I can at least toss out 100-200 lines of code, that are kind of complex, and simplify table managment a whole lot, due to this one handler.

So even if I have to preparate the keys a little, or rework the concept of a key; that is still a very small price to pay for the benefits of this handler. With regards to labour, speed, and complexity. :slight_smile:

When talking about key value pairs it’s not possible to get the associated key by it’s value. So my misinterpretation of this list was thinking in key value pairs but this is actually a key key pair list instead. Now I definitely can see that this can be very useful in some scenarios, like a list of opposites :D. Get me the opposites of “Good” will return “Bad” mind blowing fast (for AS) and vice versa.

Thanks for the clarification Nigel!