convert a list to a record

I have this list:


set theList to {{"Adam", "604"}, {"Eddie", "601"}, {"Susie", "602"}, {"Tim", "603"}, {"Steve", "603"}, {"Ben", "608"}, {"Dave", "607"}, {"Jimmy", 609}, {"Ive", "605"}, {"Will", "607"}}

How would I convert this into a record?

It should end up looking like:

{adam: “604”, eddie: “601”, susie: “602”, etc.}

Hi,

One known technique is this:


set theList to {{"Adam", "604"}, {"Eddie", "601"}, {"Susie", "602"}, {"Tim", "603"}, {"Steve", "603"}, {"Ben", "608"}, {"Dave", "607"}, {"Jimmy", 609}, {"Ive", "605"}, {"Will", "607"}}

-- convert the original list to raw list {"Adam", "604", "Eddie", "601", "Susie", "602" ,...}
set rawList to {}
repeat with theItem in theList
	set end of rawList to item 1 of theItem
	set end of rawList to item 2 of theItem
end repeat

-- convert to rawRecord
set rawRecord to {«class usrf»:rawList}

-- A script which forces coercion between AppleEvent and AppleScript later
script x
	on run argv
		return item 1 of argv
	end run
end script

-- final result
set theRecord to run script x with parameters {rawRecord}

Here is an ASObjC NSMutableDictionary solution:


use framework "Foundation"
use scripting additions
property || : current application

set theList to {{"Adam", "604"}, {"Eddie", "601"}, {"Susie", "602"}, {"Tim", "603"}, {"Steve", "603"}, {"Ben", "608"}, {"Dave", "607"}, {"Jimmy", 609}, {"Ive", "605"}, {"Will", "607"}}

set theDict to (||'s NSMutableDictionary)'s |dictionary|()
repeat with currPair in theList
	set {currKey, currValue} to currPair's contents
	(theDict's setObject:currValue forKey:currKey)
end repeat
set theRecord to theDict as record

theRecord --> {Ive:"605", Will:"607", Steve:"603", Eddie:"601", Ben:"608", Susie:"602", Tim:"603", Adam:"604", Dave:"607", Jimmy:609}

Here is a System Events Property List solution:


set theList to {{"Adam", "604"}, {"Eddie", "601"}, {"Susie", "602"}, {"Tim", "603"}, {"Steve", "603"}, {"Ben", "608"}, {"Dave", "607"}, {"Jimmy", 609}, {"Ive", "605"}, {"Will", "607"}}

tell application "System Events"
	set plistFile to make new property list file with properties {contents:(make new property list item with properties {kind:record}), name:"/tmp/MyPlistFile.plist"}
	repeat with currPair in theList
		set {currKey, currValue} to currPair's contents
		make new property list item at end of plistFile's property list items with properties {name:currKey, value:currValue}
	end repeat
	set theRecord to property list file "/tmp/MyPlistFile.plist"'s value
end tell

theRecord --> {Tim:"603", Will:"607", Steve:"603", Dave:"607", Adam:"604", Ben:"608", Eddie:"601", Ive:"605", Jimmy:609, Susie:"602"}

Incidentally, I assume that the integer value for “Jimmy” should be a text string? In any case, I left it as an integer to show that a value’s class will be retained in the newly formed record with either of the above techniques.

Hi bmose,

I guessed that there are other techniques, and thanks for them, although I’ll leave the first for myself.

I compared 3 scripts by speed. As always when I see new alternatives. The result is this: my script (which was invented by another person, DJ Bazzie Wazzie) is 2 times faster than your AsObjC-solution and at least 20 times faster than the System Events-solution.

KniazidisR,

Yours and DJ’s “{«class usrf»:rawList}” method is quite creative. I hadn’t seen it before. I tested it with other value types (integer, real, boolean, date, list, record), and it worked well with those value types as well.

Many ways to skin this cat!

For completeness, here is a serialization method that can handle values of virtually any class:


	script util
		on textValue(theValue)
			try
				|| of {theValue}
			on error m
				set tid to AppleScript's text item delimiters
				try
					set AppleScript's text item delimiters to "{"
					set m to m's text items 2 thru -1 as text
					set AppleScript's text item delimiters to "}"
					set theText to m's text items 1 thru -2 as text
				end try
				set AppleScript's text item delimiters to tid
			end try
			return theText
		end textValue
	end script
	
	set theList to {{"Adam", "604"}, {"Eddie", "601"}, {"Susie", "602"}, {"Tim", "603"}, {"Steve", "603"}, {"Ben", "608"}, {"Dave", "607"}, {"Jimmy", 609}, {"Ive", "605"}, {"Will", "607"}}
	
	set serializedRecord to ""
	repeat with currPair in theList
		set {currKey, currValue} to currPair's contents
		if serializedRecord ≠ "" then set serializedRecord to serializedRecord & ", "
		set serializedRecord to serializedRecord & currKey & ":" & util's textValue(currValue)
	end repeat
	set theRecord to run script ("{" & serializedRecord & "}")

	theRecord --> {Adam:"604", Eddie:"601", Susie:"602", Tim:"603", Steve:"603", Ben:"608", Dave:"607", Jimmy:609, Ive:"605", Will:"607"}

I ran speed tests with 1000 repetitions per method and got slightly different results:

ASObjC method - 0.0007 seconds
«class usrf» method - 0.0009 seconds
System Events method - 0.0025 seconds
serialized record method (this post) - 0.0918 seconds

By these speed tests, the ASObjC and «class usrf» methods are very close to each other in speed, the System Events method is 3-4 times slower, and the serialized record method is quite slow (relatively speaking.)

On first testing (using Script Geek of Shane Stanley) I tested with 5 repetitions. Now, the results with 1000 repetitions is this:

«class usrf» method - 0.018 seconds
ASObjC method ------- 0.061 seconds

Using a reference to for your property (property || : a reference to current application) I got:

«class usrf» method - 0.012 seconds
ASObjC method ------- 0.024 seconds

I use “Run both”. That is, when completion of one script is shorter, the completion of other script is shorter too. But, important is only the result in compare.

Your serialization method that can handle values of virtually any class, is great.

Here’s a faster variation of the ASObjC method you may like to try. Most of the work’s done in vanilla AppleScript:


use framework "Foundation"
use scripting additions
property || : current application

set theList to {{"Adam", "604"}, {"Eddie", "601"}, {"Susie", "602"}, {"Tim", "603"}, {"Steve", "603"}, {"Ben", "608"}, {"Dave", "607"}, {"Jimmy", 609}, {"Ive", "605"}, {"Will", "607"}}

set theKeys to {}
set theValues to {}
repeat with currPair in theList
	set {end of theKeys, end of theValues} to currPair's contents
end repeat
set theRecord to (||'s NSDictionary's dictionaryWithObjects:theValues forKeys:theKeys) as record

Well, Nigel, as I see you’re just an expert in improving the speed of scripts:
With 1000 repetitions:

AsObjC method: ----- 0.006 sec
«class usrf» method - 0.012 sec

I searched little here to improve plain AppleScript solution. It seems, I found it:


set theList to {{"Adam", "604"}, {"Eddie", "601"}, {"Susie", "602"}, {"Tim", "603"}, {"Steve", "603"}, {"Ben", "608"}, {"Dave", "607"}, {"Jimmy", 609}, {"Ive", "605"}, {"Will", "607"}}

set rawList to a reference to {}
repeat with currPair in theList
	tell rawList to set {end, end} to contents of currPair
end repeat

-- Create a record who is similar to records containing only user defined keys in AppleEvents
set theRecord to «class seld» of (record {«class usrf»:rawList} as record)

With 1000 repetitions:

AsObjC method: ----- 0.006 sec
«class usrf» method - 0.006 sec

For completeness, here is a very effective plain-AppleScript solution, when the key names (text class) and the values (any) is given as 2 separate lists:


script o
	property theKeys : {"The Condition", "This Script Runnig Date", "Hi, Susiehi", "Tim", "the Path", "Ben"}
	property theValues : {true, current date, "hello", 1, path to me, "joke"}
end script

-- Create a list containing keys subsequently followed by it's associated value
set rawList to a reference to {}
repeat with i from 1 to count o's theKeys
	tell rawList to set {end, end} to {item i of o's theKeys, item i of o's theValues}
end repeat

-- Create a record who is similar to records containing only user defined keys in AppleEvents
set theRecord to «class seld» of (record {«class usrf»:rawList} as record)

I’m not sure how to explain the discrepancy in speed testing results. Here are the results I get comparing the four fastest solutions with 1000 repetitions each:

Nigel’s NSDictionary method:
0.0002 seconds
My NSMutableDictionary method:
0.0007 seconds
run script x with parameters {{«class usrf»:rawList}}:
0.0009 seconds
«class seld» of (record {«class usrf»:rawList} as record):
0.0009 seconds

Based on these speed test results, I would have to go with Nigel’s NSDictionary method. (I’ve found over the years that when it comes to speed of execution, Nigel’s solutions are virtually always the fastest! :slight_smile: )

Still, I find the two Apple Event-based methods fascinating but mysterious. Can someone kindly explain the innards of how they work?

I decided to do some testing with an expanded list of 1000 items. Some suggestions didn’t work in my test script but the results for the others are shown below. Given the size of the test list, I also modified Nigel’s script to use a script object (see script below).

Nigel from post 9 (script-object enhanced) - 0.062 second

Nigel from post 9 - 0.421 second

KniazidisR from post 10 - 0.427 second

Bmose from post 6 - 0.645 second

Bmose first script from post 3 - 0.837 second

Bmose second script from post 3 - 5.832 seconds

use framework "Foundation"
use scripting additions

-- UNTIMED CODE --

set theList to {}
repeat 1000 times
	set the end of theList to {randomString(6), randomString(4)}
end repeat

on randomString(theStringLength)
	set randomChars to "ABCDEFGHIJKLMNOPQRSTUVWXYS"
	set theString to ""
	repeat theStringLength times
		set theString to theString & some item of randomChars
	end repeat
	return theString
end randomString

-- START TIME --

set startTime to current application's CFAbsoluteTimeGetCurrent()

-- TIMED CODE --

property || : current application

set theKeys to {}
set theValues to {}
repeat with currPair in my theList
	set {end of theKeys, end of theValues} to currPair's contents
end repeat
set theRecord to (||'s NSDictionary's dictionaryWithObjects:theValues forKeys:theKeys) as record

-- ELAPSED TIME

set elapsedTime to (current application's CFAbsoluteTimeGetCurrent()) - startTime
set nf to current application's NSNumberFormatter's new()
nf's setFormat:("0.000")
set elapsedTime to ((nf's stringFromNumber:elapsedTime) as text) & " seconds"

-- RESULT --

elapsedTime -- 0.062 seconds

-- theList # remove comment to view the result returned by theList
-- theRecord # remove comment to view the result returned by theRecord

Here’s another run script method without the raw constant.

set theList to {{"Adam", "604"}, {"Eddie", "601"}, {"Susie", "602"}, {"Tim", "603"}, {"Steve", "603"}, {"Ben", "608"}, {"Dave", "607"}, {"Jimmy", 609}, {"Ive", "605"}, {"Will", "607"}}
set recordz to {}

repeat with anIndex from 1 to count theList
	set recordz's end to my (run script "{|" & (my (theList's item anIndex's item 1)) & "|: " & (quote & my (theList's item anIndex's item 2) & quote) & "}")
end repeat

recordz

Thanks everybody, this was eye opening.